Reactor模式是处理并发I/O比较常见的一种模式,用于同步I/O,中心思想是将所有要处理的I/O事件注册到一个中心I/O多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有I/O事件到来或是准备就绪(文件描述符或socket可读、写),多路复用器返回并将事先注册的相应I/O事件分发到对应的处理器中。
Reactor是一种事件驱动机制,和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。用“好莱坞原则”来形容Reactor再合适不过了:不要打电话给我们,我们会打电话通知你。
在 Reactor 模式中,会先对每个 client 注册感兴趣的事件,然后有一个线程专门去轮询每个 client 是否有事件发生,当有事件发生时(比如可读数据到达,新的套接字连接等等),便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询,如下图所示:

就好像在长途客车上,每一站都有人上车有人下车,但是车上的乘客总是希望能够在客车上得到休息(睡觉)。
- 传统做法:每隔一段时间(或每一个站),司机或售票员对每一个乘客询问是否下车。
- Reactor做法:汽车是乘客访问的主体(Reactor),乘客上车后,到售票员(acceptor)处登记,之后乘客便可以休息睡觉去了,当到达乘客所要到达的目的地时(指定的事件发生,乘客到了下车地点),售票员将其唤醒即可。
常用的 Reactor 线程模型有三种,分别如下:
1.单线程模式
这是最简单的Reactor单线程模型。Reactor 单线程模型,指的是所有的 IO 操作都在同一个 NIO 线程上面完成,NIO 线程的职责如下:
- 作为 NIO 服务端,接收客户端的 TCP 连接
- 作为 NIO 客户端,向服务端发起 TCP 连接
- 读取通信对端的请求或者应答消息
- 向通信对端发送消息请求或者应答消息
特别强调:该图片及下面的两张图片出自这篇博客!!

由于 Reactor 模式使用的是异步非阻塞 IO,所有的 IO 操作都不会导致阻塞,理论上一个线程可以独立处理所有 IO 相关的操作。从架构层面看,一个 NIO 线程确实可以完成其承担的职责。例如,通过 Acceptor 接收客户端的 TCP 连接请求消息,链路建立成功之后,通过 Dispatch 将对应的 ByteBuffer 派发到指定的 Handler上进行消息解码。 用户 Handler 可以通过 NIO 线程将消息发送给客户端。
对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发的应用却不合适,主要原因如下:
- 一个 NIO 线程同时处理成百上千的链路,性能上无法支撑,即便 NIO 线程的CPU负荷达到 100%,也无法满足海量消息的编码、解码、读取和发送;
- 当 NIO 线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO 线程的负载,最终会导致大量消息积压和处理超时,NIO 线程会成为系统的性能瓶颈;
- 可靠性问题:一旦 NIO 线程意外跑飞,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障。
为了解决这些问题,演进出了Reactor 多线程模型。
2.多线程模式
Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 IO 操作,也是后端程序常用的模型,它的原理图如下:
Reactor 多线程模型的特点:
- 有专门一个 NIO 线程-Acceptor线程用于监听服务端,接收客户端的 TCP 连接请求;
- 网络 IO 操作-读、写等由一个NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送;
- 1 个NIO 线程可以同时处理N 条链路,但是 1个链路只对应 1个 NIO 线程,防止发生并发操作问题。
在绝大多数场景下,Reactor 多线程模型都可以满足性能需求;但是,在极特殊应用场景中,一个 NIO 线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手消息进行安全认证,认证本身非常损耗性能。在这类场景下,单独一个 Acceptor 线程可能会存在性能不足问题,为了解决性能问题,产生了第三种Reactor 线程模型-主从 Reactor 多线程模型。
3.主从模式
比起第二种模型,它是将 Reactor 分成两部分
- mainReactor:负责监听并 accept 新连接,然后将建立的 socket 通过多路复用器(Acceptor)分派给subReactor
- subReactor:负责多路分离已连接的 socket,读写网络数据;业务处理功能,其交给 worker 线程池完成。通常, subReactor个数上可与CPU个数等同。
- 服务端用于接收客户端连接的不再是个 1个单独的 NIO 线程,而是一个独立的 NIO线程池。Acceptor接收到客户端 TCP连接请求处理完成后(可能包含接入认证等),将新创建的 SocketChannel 注册到 IO 线程池(sub reactor线程池)的某个IO 线程上,由它负责 SocketChannel 的读写和编解码工作。
- Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO线程上,由IO 线程负责后续的IO 操作。
利用主从 NIO 线程模型,可以解决 1个服务端监听线程无法有效处理所有客户端连接的性能不足问题。因此,在 Netty 的官方demo 中,推荐使用该线程模型。
事实上,Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup 实例并通过适当的参数配置,就可以支持上述三种 Reactor 线程模型。正是因为 Netty 对 Reactor 线程模型的支持提供了灵活的定制能力,所以可以满足不同业务场景的性能诉求。







还没有评论,来说两句吧...