Reactor
线程模式
1. 概念
Reactor 模式有两个核心组成部分:
Reactor
(图中的ServiceHandler
):Reactor
在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理线程来对 IO 事件做出反应。Handlers
(图中的EventHandler
):处理线程执行处理方法来响应 I/O 事件,处理线程执行的是非阻塞操作。
2. 模式
2.1. 单 Reactor
单线程模式
这种模式的基本工作流程为:
Reactor
通过select
监听客户端请求事件,收到事件之后通过dispatch
进行分发- 如果事件是建立连接的请求事件,则由
Acceptor
通过accept
处理连接请求,然后创建一个Handler
对象处理连接建立后的后续业务处理。 - 如果事件不是建立连接的请求事件,则由
Reactor
对象分发给连接对应的Handler
处理。 Handler
会完成read-->业务处理-->send
的完整处理流程。
优点 | 缺点 |
---|---|
模型简单,没有多线程、进程通信、竞争的问题,一个线程完成所有的事件响应和业务处理 | 存在性能问题,只有一个线程,无法完全发挥多核 CPU 的性能。存在可靠性问题,若线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障 |
适用场景:
客户端的数量有限,业务处理非常快速,比如 Redis 在业务处理的时间复杂度为 O(1)的情况。
2.2. 单 Reactor
多线程模式
这种模式的基本工作流程为:
Reactor
对象通过select
监听客户端请求事件,收到事件后通过dispatch
进行分发。- 如果事件是建立连接的请求事件,则由
Acceptor
通过accept
处理连接请求,然后创建一个Handler
对象处理连接建立后的后续业务处理。 - 如果事件不是建立连接的请求事件,则由
Reactor
对象分发给连接对应的Handler
处理。Handler
只负责响应事件,不做具体的业务处理,Handler
通过read
读取到请求数据后,会分发给后面的Worker
线程池来处理业务请求。 Worker
线程池会分配独立线程来完成真正的业务处理,并将处理结果返回给Handler
。Handler
通过send
向客户端发送响应数据。
优点 | 缺点 |
---|---|
充分的利用多核 cpu 的处理能力 | 多线程数据共享和控制比较复杂,Reactor 处理所有的事件的监听和响应,在单线程中运行,面对高并发场景还是容易出现性能瓶颈 |
2.3. 主从 Reactor
多线程模式
这种模式的基本工作流程为:
Reactor
主线程MainReactor
对象通过select
监听客户端连接事件,收到事件后,通过Acceptor
处理客户端连接事件。- 当
Acceptor
处理完客户端连接事件之后(与客户端建立好Socket
连接),MainReactor
将连接分配给SubReactor
。(即:MainReactor
只负责监听客户端连接请求,和客户端建立连接之后将连接交由SubReactor
监听后面的 IO 事件。) SubReactor
将连接加入到自己的连接队列进行监听,并创建Handler
对各种事件进行处理。- 当连接上有新事件发生的时候,
SubReactor
就会调用对应的Handler
处理。 Handler
通过read
从连接上读取请求数据,将请求数据分发给Worker
线程池进行业务处理。Worker
线程池会分配独立线程来完成真正的业务处理,并将处理结果返回给 Handler。Handler
通过send
向客户端发送响应数据。- 一个
MainReactor
可以对应多个SubReactor
,即一个MainReactor
线程可以对应多个SubReactor
线程。
优点 | 缺点 |
---|---|
1. MainReactor 线程与 SubReactor 线程的数据交互简单职责明确,MainReactor 线程只需要接收新连接,SubReactor 线程完成后续的业务处理。2. MainReactor 线程与 SubReactor 线程的数据交互简单, MainReactor 线程只需要把新连接传给 SubReactor 线程,SubReactor 线程无需返回数据 3. 多个 SubReactor 线程能够应对更高的并发请求 |
编程复杂度较高 |
这种模式也被叫做服务器的 1+M+N
线程模式,即使用该模式开发的服务器包含1个(或多个,1 只是表示相对较少)连接建立线程 + M个IO线程 + N个业务处理线程。这是业界成熟的服务器程序设计模式。
Netty 架构
1. 简版
BossGroup
线程维护Selector
,ServerSocketChannel
注册到这个Selector
上,只关注连接建立请求事件(相当于MainReactor
)。- 当接收到来自客户端的连接建立请求事件的时候,通过
ServerSocketChannel.accept
方法获得对应的SocketChannel
,并封装成NioSocketChannel
注册到WorkerGroup
线程中的Selector
,每个Selector
运行在一个线程中(相当于SubReactor
)。 - 当
WorkerGroup
线程中的Selector
监听到自己感兴趣的 IO 事件后,就调用Handler
进行处理。
2. 第二版
- 有两组线程池:
BossGroup
和WorkerGroup
:
BossGroup
中的线程(可以有多个,图中只画了一个)专门负责和客户端建立连接;WorkerGroup
中的线程专门负责处理连接上的读写。
BossGroup
和WorkerGroup
含有多个不断循环的执行事件处理的线程,每个线程都包含一个Selector
,用于监听注册在其上的Channel
。- 每个
BossGroup
中的线程循环执行以下三个步骤:
3.1. 轮训注册在其上的ServerSocketChannel
的accept
事件(OP_ACCEPT 事件
)
3.2. 处理accept
事件,与客户端建立连接,生成一个NioSocketChannel
,并将其注册到WorkerGroup
中某个线程上的Selector
上
3.3. 再去以此循环处理任务队列中的下一个事件 - 每个
WorkerGroup
中的线程循环执行以下三个步骤:
4.1. 轮训注册在其上的NioSocketChannel
的read/write
事件(OP_READ/OP_WRITE 事件
)
4.2. 在对应的NioSocketChannel
上处理read/write
事件
4.3. 再去以此循环处理任务队列中的下一个事件
3. 第三版
Netty
抽象出两组线程池:BossGroup
和WorkerGroup
,也可以叫做BossNioEventLoopGroup
和WorkerNioEventLoopGroup
。每个线程池中都有NioEventLoop
线程。BossGroup
中的线程专门负责和客户端建立连接,WorkerGroup
中的线程专门负责处理连接上的读写。BossGroup
和WorkerGroup
的类型都是NioEventLoopGroup
。NioEventLoopGroup
相当于一个事件循环组,这个组中含有多个事件循环,每个事件循环就是一个NioEventLoop
。NioEventLoop
表示一个不断循环的执行事件处理的线程,每个NioEventLoop
都包含一个Selector
,用于监听注册在其上的Socket
网络连接(Channel
)。NioEventLoopGroup
可以含有多个线程,即可以含有多个NioEventLoop
。- 每个
BossNioEventLoop
中循环执行以下三个步骤:
5.1.select
:轮训注册在其上的ServerSocketChannel
的accept
事件(OP_ACCEPT 事件
)
5.2.processSelectedKeys
:处理accept
事件,与客户端建立连接,生成一个NioSocketChannel
,并将其注册到某个WorkerNioEventLoop
上的Selector
上
5.3.runAllTasks
:再去以此循环处理任务队列中的其他任务 - 每个
WorkerNioEventLoop
中循环执行以下三个步骤:
6.1.select
:轮训注册在其上的NioSocketChannel
的read/write
事件(OP_READ/OP_WRITE 事件
)
6.2.processSelectedKeys
:在对应的NioSocketChannel
上处理read/write
事件
6.3.runAllTasks
:再去以此循环处理任务队列中的其他任务 - 在以上两个
processSelectedKeys
步骤中,会使用Pipeline
(管道),Pipeline
中引用了Channel
,即通过Pipeline
可以获取到对应的Channel
,Pipeline
中维护了很多的处理器(拦截处理器、过滤处理器、自定义处理器等)。