【领你⼊门Netty】-Netty线程模型与⼊门程序详解
⼀、Netty 简介
Netty 是由 JBOSS 提供的⼀个 Java 开源框架。Netty 提供异步的、基于事件驱动的⽹络应⽤程序框架,⽤以快速开发⾼性能、⾼可靠性的⽹络 I/O 程序。它是⼀个基于 NIO 的⽹络编程框架,相⽐于 Java 原⽣的 NIO,它具有如下的优势:
⽀持三种 I/O 模型同时⽀持三种 Reactor 模型。
⽀持很多应⽤层的协议,提供了很多编解码器。
能够很好地解决 TCP 长连接所带来的缺陷(粘包、半包等)。
提供了应⽤层的 Keep Alive 机制。
有更好的性能。
此外,Netty 具有如下特性:
统⼀的 API,⽀持多种传输类型,阻塞的和⾮阻塞的。
真正的⽆连接数据报套接字⽀持。
链接逻辑组件以⽀持复⽤。
得益于池化和复⽤,拥有更低的资源消耗。
简单⽽强⼤的线程模型,即主从 Reactor 多线程模型。
⼆、Netty 线程模型
在介绍 Netty 的线程模型之前,⾸先需要了解下 Reactor 模型,Reactor 模型基于事件驱动,特别适合处理海量的 I/O 事件。根据Reactor 的数量和处理资源池的线程数量的不同,Reactor 模型有以下 3 种不同的实现:
单 Reactor 单线程模型。
单 Reactor 多线程模型。
主从 Reactor 多线程模型。
2.1 单 Reactor 单线程模型
单 Reactor 单线程模型指所有的 I/O 操作都在同⼀个 NIO 线程上完成,NIO 线程的职责如下:
作为 NIO 服务端,接收客户端的 TCP 连接。
作为 NIO 客户端,向服务端发起 TCP 连接。
读取通信对端的请求或者应答消息。
向通信对端发送消息请求或者应答消息。
单 Reactor 单线程模型图如下所⽰:
通过 Reactor 的 lect 监听客户端的请求事件,若是连接请求事件,则由 Acceptor 通过 accept 处理连接请求,然后创建⼀个Handler 对象处理后续的业务请求。若不是连接请求事件,则通过 Reactor 的 dispatch 调⽤该连接对应的 Handler 进⾏业务处理,即通过dispatch 将对应的 ByteBuffer 分发到连接对应的 Handler 上进⾏消息解码,最后 NIO 线程将需要回送的消息进⾏编码发送给客户端。
单 Reactor 单线程不存在多线程、进程通信、竞争的问题。但因为只有⼀个线程同时处理成百上千的链路,⽆法发挥多核 CPU 的优势,且若 Handler 在处理⼀个耗时的业务时,会导致整个进程⽆法
处理其他的连接事件,造成客户端连接超时和超时重发,最终导致⼤量消息积压和处理超时。此外,若线程意外终⽌,或陷⼊死循环,会导致整个系统的通信模块不可⽤,不能接收和处理外部消息,从⽽造成结点故障。
综上所述,单 Reactor 单线程适合⼩容量应⽤场景,如客户端连接数较少、业务处理迅速。
2.2 单 Reactor 多线程模型
单 Reactor 多线程模型如下图所⽰,其具有如下特点:
有专门的⼀个 NIO 线程 Acceptor 线程⽤于监听服务端⼝,接收客户端的 TCP 连接请求。
⽹络 I/O 操作(读或写等)由⼀个 NIO 线程池负责,线程池可以采⽤标准的 JDK 线程池实现,它包含⼀个任务队列和 N 个可⽤的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。
1 个 NIO 线程可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,避免发⽣并发操作问题。
Reactor 依然是以单线程的⽅式运⾏,在客户端⾼并发连接场景或者服务端需要对客户端握⼿进⾏安全认证的情况下容易出现性能瓶颈。
Worker 线程池会存在多线程的数据共享和竞争。
根据上图,单 Reactor 多线程模型的⼯作过程如下:
1)Reactor 对象通过 lect 监控客户端的请求事件,根据收到的事件类型,使⽤ dispatch 进⾏分发。
2)若是请求连接事件,则分发给 Acceptor 通过 accept 处理连接请求,然后创建⼀个 Handler ⽤于处理后续的业务。
3)若不是请求连接事件,则分发给连接对应的 Handler 进⾏处理。
4)Handler 只负责响应事件,它通过 read 读取数据后,会分发给 Worker 线程池中的任意⼀个线程进⾏业务处理。
5)Worker 线程池在线程可⽤的情况下,分配独⽴的线程完成业务过程,并将处理结果返回给 Handler。
6)Handler 收到响应后,通过 nd 将结果返回给 client。
2.3 主从 Reactor 多线程模型
主从 Reactor 多线程模型如下图所⽰,其具有如下特点:
服务端⽤于接收客户端连接的不再是个 1 个单独的 NIO 线程,⽽是⼀个独⽴的 NIO 线程池。
Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接⼊认证等),将新创建的 SocketChannel注册到 SubReactor 的某个I/O 线程上,由它负责 SocketChannel 的读写和编解码⼯作。
Acceptor 线程池仅仅只⽤于客户端的登陆、握⼿和安全认证,⼀旦链路建⽴成功,就将链路注册到 SubReactor 线程池的 I/O 线程上,由 I/O 线程负责后续的 I/O 操作。
Reactor 主线程即 MainReactor,它可以对应多个 Reactor ⼦线程即 SubReactor。
根据上图,主从 Reactor 多线程模型的⼯作过程如下:
1)Reactor 主线程的 MainReactor 对象通过 lect 监听客户端的连接请求事件,通过 Acceptor 处理连接请求事件。 2)当 Acceptor 处理连接事件后,MainReactor 创建 SocketChannel,并将其注册到 SubReactor。
3)SubReactor 监听连接队列中的连接,并创建对应的 Handler 进⾏各种事件的处理。
4)当连接队列中的连接有新事件发⽣时,SubReactor 会调⽤对应的 Handler 处理。
5)Handler 通过 read 读取数据,并分发给 Work 线程池中的任意⼀个 Worker 线程处理。
6)Worker 线程池分配独⽴的 Worker 线程进⾏业务处理,并返回结果给 Handler。
7)Handler 收到响应结果后,再通过 nd 将结果返回给客户端。
2.4 Reactor 模型⼩结
Reactor 可类⽐为⽣活中的前台接待员和服务员。
单 Reactor 单线程模型,可类⽐为:前台接待员和服务员是同⼀个⼈,全程提供⼀条龙服务。
单 Reactor 多线程模型,可类⽐为:只有⼀个前台接待员,多个服务员,前台接待员只负责接待。
主从 Reactor 多线程模型,可类⽐为多个前台接待员,多个服务员。
Reactor 模型具有如下优点:
响应快,不必为单个同步时间所阻塞,即使 Reactor 本⾝是同步的。
可以最⼤程度地避免复杂的多线程与同步问题,且避免了多线程/进程间的上下⽂切换。
扩展性好,可⽅便地通过增加 Reactor 实例的个数来充分利⽤ CPU 资源。
复⽤性好,Reactor 模型本⾝与具体事件的处理逻辑⽆关。
三、Netty 线程模型
Netty 线程模型是在主从 Reactor 多线程模型的基础上进⾏改进和优化的。其模型图如下图所⽰:
从上图可以看到,Netty 抽象出两组线程池,其中 BossGroup 负责接收客户端的连接请求,WorkerGroup 负责⽹络读写请求,即具体的业务处理。BossGroup 和 WorkerGroup 的类型都是 NioEventLoopGroup,每个 NioEventLoopGroup 相当于⼀个事件循环组,在这个事件循环组中包含多个事件循环,每个事件循环都是 NioEventLoop,即⼀个不断循环地执⾏任务的线程。此外,每个 NioEventLoop 都有⼀个 Selector,⽤于监听绑定在其上的⽹络通讯。
BossGroup 中的 NioEventLoop 循环执⾏的步骤如下:
1)轮询 Accept 事件,即客户端请求连接事件。
2)处理 Accept 事件,与 client 建⽴通讯连接,并⽣成 SocketChannel,进⽽将其包装成 NioSocketChannel,再NioSocketChannel 注册到 WorkGroup 中的任意⼀个 NioEventLoop 的 Selector 中。
3)处理任务队列中的任务,即 runAllTasks。
WorkGroup 中的 NioEventLoop 循环执⾏的步骤如下:
1)轮询 read 和 write 事件。
2)处理 I/O 事件,即 read 或 write 事件,在该连接对应的 NioSocketChannel 中处理。
3)处理任务队列中的任务,即 runAllTasks。
在图中还存在⼀个 ChannelPipeline 即处理器管道。每个 WorkGroup 在处理业务时,都会使⽤它。ChannelPipeline 是⼀个包含了ChannelHandlerContext 的双向链表,每个 ChannelHandlerContext 都关联着⼀个 ChannelHandler 处理器。WorkerGroup 在进⾏业务处理时,会经过 ChannelPipeline 中的 ChannelHandler 的⼀系列处理(解码,处理业务,编码)。
四、Netty ⼊门程序
项⽬的⽬录结构如下所⽰:
⾸先编写服务端程序。
/**
* 配置 Netty 服务端信息
*/
public class MyServer {