一、Reactor线程模型
Reactor模式思想:分而治之+事件驱动
Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler
角色:
- Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;
- Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;
- Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。
二、单线程模型
单线程模型下,所有的IO操作都由同一个Reactor线程来完成。
特点:
- 作为服务端,接收客户端的TCP连接;
- 作为客户端,向服务端发起TCP连接;
- 读取通信对端的请求或者应答消息;
- 向通信对端发送消息请求或者应答消息。
缺点:
-
不能利用多核CPU;
-
一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,处理成百上千的链路时性能上无法支撑;
-
一旦reactor线程意外跑飞或者进入死循环,会导致整个系统通信模块不可用
注意这里指的是基于Reactor的单线程模式,不是BIO的单线程
三、多线程模型
- 专门由一个Reactor线程-Acceptor线程用于监听服务端,接收客户端连接请求
- 网络I/O操作读、写等由Reactor线程池负责处理
- 一个Reactor线程可同时处理多条链路,但一条链路只能对应一个Reactor线程,这样可避免并发操作问题。
这种模式在绝大部分情况下都可以使用,但是如果客户端连接数量过多,并发量太大,只有一个Acceptor线程用来监听连接,会出现性能问题
四、主从多线程模型
- 从主线程池中随机选择一个 Reactor 线程作为 Acceptor 线程,用于绑定监听端口,接收客户端连接
- Acceptor 线程接收客户端连接请求之后创建新的 SocketChannel,将其注册到主线程池的其它 Reactor 线程上,由其负责接入认证、IP 黑白名单过滤、握手等操作
- 业务层的链路正式建立,将 SocketChannel 从主线程池的 Reactor 线程的多路复用器上摘除,重新注册到 Sub 线程池的线程上,用于处理 I/O 的读写操作
五、Netty线程模型
Netty同时支持Reactor单线程模型 、Reactor多线程模型和Reactor主从多线程模型,用户可根据启动参数配置在这三种模型之间切换
Netty 线程模型采用“服务端监听线程”和“IO线程”分离的方式,与多线程Reactor模型类似。抽象出NioEventLoop来表示一个不断循环执行处理任务的线程,每个NioEventLoop有一个selector,用于监听绑定在其上的socket链路
服务端客户端启动代码
-
ServerBootstrap 类用于创建服务端实例,Bootstrap 用于创建客户端实例。
-
bossGroup 线程组实际就是 Acceptor 线程池,负责处理客户端的 TCP 连接请求,如果系统只有一个服务端端口需要监听,则建议 bossGroup 线程组线程数设置为 1
-
workerGroup 是真正负责 I/O 读写操作的线程组,通过 ServerBootstrap 的 group 方法进行设置,用于后续的 Channel 绑定
-
客户端只需要创建一个 EventLoopGroup,因为它不需要独立的线程去监听客户端连接,也没必要通过一个单独的客户端线程去连接服务端
NioEventLoop 是 Netty 的 Reactor 线程,职责如下:
- 作为服务端 Acceptor 线程,负责处理客户端的请求接入;
- 作为客户端 Connecor 线程,负责注册监听连接操作位,用于判断异步连接结果;
- 作为 IO 线程,监听网络读操作位,负责从 SocketChannel 中读取报文;
- 作为 IO 线程,负责向 SocketChannel 写入报文发送给对方,如果发生写半包,会自动注册监听写事件,用于后续继续发送半包数据,直到数据全部发送完成;
- 作为定时任务线程,可以执行定时任务,例如链路空闲检测和发送心跳消息等;
- 作为线程执行器可以执行普通的任务线程(Runnable)
NioEventLoop串行化设计避免线程竞争
系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗,同时多线程运行的情况下,对于共享资源的读写,需要做额外的控制,防止出错。
为了解决上述问题,Netty 采用了串行化设计理念。
从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责。整个流程不会进行线程上下文切换,数据无并发修改风险。对于用户而言,甚至不需要了解 Netty 的线程细节,这确实是个非常好的设计理念。
一个NioEventLoop聚合一个多路复用器selector,因此可以处理多个客户端连接。
Netty 只负责提供和管理“IO线程”,其他的业务线程模型由用户自己集成。
时间可控的简单业务建议直接在“IO线程”上处理,复杂和时间不可控的业务建议投递到后端业务线程池中处理。