Netty(1)
官网的介绍,Netty 是一个高性能、异步事件驱动的 NIO 框架,它提供了对 TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的,通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果。
作为当前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得了广泛的应用,一些业界著名的开源组件也基于 Netty 的 NIO 框架构建。
传统的阻塞I/O模型(BIO)
特点
-
采用阻塞式 I/O 模型获取输入数据;
-
每个连接都需要
独立的线程
完成数据输入,业务处理,数据返回的完整操作。
问题
-
当并发数较大时,需要创建
大量线程来
处理连接,系统资源占用较大; -
连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在 Read 操作上,造成线程资源浪费。
普通的小项目,个人开发者可以用这种玩一玩,但是在大量并发的面前,最终会耗尽资源。
事件驱动模型
事件驱动⽅式, 发⽣事件,主线程把事件放⼊事件队列,在另外线程不断循环消费事件列表中的事
件,调⽤事件对应的处理逻辑处理事件。事件驱动⽅式也被称为消息通知⽅式,其实是设计模式中
观察者模式
的思路。
O'Reilly ⼤神关于事件驱动模型解释图
事件驱动模型主要包括 4 个基本组件:
事件队列(event queue): 接收事件的入口,存储待处理事件。
分发器(event mediator): 将不同的事件分发到不同的业务逻辑单元。
事件通道(event channel): 分发器与处理器之间的联系渠道。
事件处理器(event processor): 实现业务逻辑,处理完成后会发出事件,触发下⼀步操作。
相对传统轮询模式,事件驱动有如下优点:
可扩展性好, 分布式的异步架构,事件处理器之间高度解耦,可以方便扩展事件处理逻辑。
高性能, 基于队列暂存事件,能方便并行异步处理事件。
Netty高效的 Reactor 线程模型
Reactor单线程模型
Reactor
单线程模型,使用异步非阻塞I/O
,理论上一个线程可以独立处理所有 IO 相关的操作
从架构层面看,一个 NIO
线程确实可以完成其承担的职责。例如,通过 Acceptor
接收客户端的 TCP
连接请求消息,链路建立成功之后,通过Dispatch
将对应的ByteBuffer
派发到指定的Handler
上进行消息解码。用户 Handler
可以通过NIO
线程将消息发送给客户端。
小规模场景,Reactor
单线程可以解决,但是对于高负载
,大并发
的场景缺不适合。
- 一个NIO 线程同时处理成百上千的链路性能上无法支撑,即便 NIO 线程的 CPU 负荷达到 100%,也无法满足海量消息的编码、解码、读取和发送;
- 当NIO线程满载或者过载,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这种负反馈效应会使得系统负担加重,结果就是消息积压和处理超时。
- 一旦NIO线程出现问题,无法处理I/O,会使得整个系统的通信崩溃,无法接收和处理消息,可靠性太差。
Reactor多线程模型
多线程的演化,解决了单线程中的一些问题,虽然还是以一个NIO Acceptor线程处理监听服务端,接收客户端TCP链接,但是后续的I/O读写,则交给了Reactor线程池处理
-
线程池负责业务处理,消息读取,encode,decode
-
一个NIO线程可以处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,防止发生并发操作问题
虽然多线程解决了部分问题,但是负责监听和链接的依然是一个NIO线程,在高并发场景下依然存在风险。
Reactor主从多线程模型
多线程的Reactor可以满足大多数场景,但是如果面对高并发,就会暴露短板。
于是出现了Reactor主从多线程
负责接收客户端连接的从一个线程,变成了的Main Reactor Pool,所有的客户端请求经过main accept pool处理后,将新建的Channel注册到sub reactor pool,这样main pool负责了auth,login,shakehand,SLA等,而链路建立成功后就会有sub pool来负责I/O。
Netty特性
多路复用模型
Netty在NioEventLoop内封装了Selector来实现了I/O的多路复用。
在服务端可以同时并发处理大量的客户端请求,同事由于读写操作都是异步非阻塞,因此大大提高了I/O的效率,避免由于阻塞导致的线程挂起和资源浪费。
数据零拷贝
linux将系统程序和用户程序的资源隔离,将系统分为了内核态和用户态。
这样用户的应用程序只能在用户态,无法直接获取内核资源,就只能从系统接口获取。
普通的数据拷贝会有几次处理:
-
磁盘到内核的buffer
-
内核buffer到用户buffer
-
用户buffer到内核的socket buffer
-
socket buffer到网卡的buffer
在传统的处理方式就会经历以上4步,但是这样会非常影响系统性能。
而Netty的数据接收,发送直接使用堆外直接内存进行socket读写,通过DMA处理,就避免了用户buffer来回拷贝带来的损耗。
无锁化设计
并行多线程会提高系统的效率,但是随之而来的风险也是不容忽视的,如果对共享资源访问处理不当,会带来严重的锁竞争,导致性能下降,而频繁的线程切换也会使得性能有损失。
Netty通过串行无锁化
设计,即将消息的处理放在同一个线程内完成,在线程内部进行串行操作,避免线程竞争。而且可以通过调整NIO线程池的参数,同时启动多个串行化的线程并行运行。
高性能的序列化框架
使用的是google的protobuf实现序列化,
Nice,又水了一篇,Netty虽然不会直接使用,但是其中的设计思想,很值得学习。而且很多的RPC框架都采用了这个NB的框架。