Netty如何支持3种Reactor
Netty如何支持3种Reactor
Java 并发包作者 Doug Lea,在 Scalable I/O in Java 一文中阐述了服务端开发中 I/O 模型的演进过程,也被 Netty、Mina 等大多数高性能 IO 服务框架所采用。
Reactor 是一种开发模式,核心流程如下:
- 注册感兴趣的事件;
- 扫描是否有感兴趣的事件发生;
- 对感兴趣事件做出相应的处理。
在网络IO中:Reactor = I/O 多路复用(底层监听) + 事件分发 + 处理器回调(高层调度)。
Reactor 线程模型
先不提 Reactor 的三种线程模型,拿生活中饭店的发展举例子:
- 前期刚刚成立,老板一个人负责迎宾,做菜,上菜,送客;
- 招了几个伙计,大家一起做这些事情;
- 伙计中找数人单独做迎宾,其他人做剩下的事情。
Reactor 线程模型的发展也是如此~
单线程模型
在单线程模型中,这个线程不但要负责迎宾接收连接(accept),还需要负责后续的编解码及业务处理,流程如图:

单线程模型逻辑简单,缺陷也十分明显:
- 一个线程支持处理的连接数非常有限,无法充分利用多核 CPU,性能方面有明显瓶颈;
- 当多个事件被同时触发时,只要有一个事件没有处理完,其他后面的事件就无法执行,这就会造成消息积压及请求超时;
- 线程在处理 I/O 事件时,Select 无法同时处理连接建立、事件分发等操作;
- 如果 I/O 线程一直处于满负荷状态,很可能造成服务端节点不可用。
单 Reactor 多线程模型
为了提高单线程模型的瓶颈,多线程模型中多了业务线程池处理业务逻辑,流程如图:

多线程模型中,一个 Reactor 线程负责连接建立和数据读写,数据读取完毕后交给业务线程池处理,优点是可以充分利用多核 CPU 的处理能力,缺点是单线程承担所有事件的监听和响应,高并发场景下容易成为性能瓶颈。
主从 Reactor 模型
单 Reactor 多线程模型的缺陷在于连接建立和数据读写的事件都由一个 Reactor 线程负责,主从 Reactor 模型中,MainReactor 线程负责连接建立(accept),连接建立成功后将新创建的连接对象注册至 SubReactor。再由 SubReactor 分配线程池中的 I/O 线程与其连接绑定,它将负责连接生命周期内所有的 I/O 事件。流程如图:

Netty 推荐使用主从多线程模型,这样就可以轻松达到成千上万规模的客户端连接。在海量客户端并发请求的场景下,主从多线程模式甚至可以适当增加 SubReactor 线程的数量,从而利用多核能力提升系统的吞吐量。
Netty 与 Reactor
Netty 在 Java NIO 基础上进行了更高层次的封装,屏蔽了 NIO 的复杂性;封装了更加人性化的 API,提供了良好的可拓展性,大大降低了开发者的上手难度。
Netty 的可扩展性其中之一就是可定制化的线程模型。
单线程模型
1 | |
单 Reactor 多线程模型
1 | |
主从 Reactor 模型
1 | |
Netty 只是修改了几行简单的启动代码,就切换了不同的线程模型。
初学者可能会有疑问,这些代码里完全没有看到 workThreads(即业务处理线程),这里涉及到 Netty 中 Handler 的逻辑,如果在添加 Handler 时不指定额外的线程池,那么 SubReactor 线程既充当 I/O 事件分发器,又充当业务逻辑的执行线程。
代码中的 ServerBootstrap 姑且可以认为是服务端引导器,通过它来启动服务端(本来 Reactor 模型也是用于服务端处理高并发客户端连接的)。
NioEventLoopGroup 是 Netty 中的核心概念之一,NIO 是一种 IO 模型,EventLoop 事件循环,字面意思其实就是在做 NIO 模型的事件循环,NioEventLoop 中维护着一个 Selector 选择器和任务队列 taskQueue, 每次循环的处理流程都包含事件轮询 select、事件处理 processSelectedKeys、任务处理 runAllTasks 几个步骤,是典型的 Reactor 线程模型的运行机制。NioEventLoopGroup 就是这样的组件的组成的 group。
SubReactor 中的每一个线程其实就是一个 NioEventLoop,由于默认情况下每个 SubReactor 要处理分配的所有 Socket 的操作,因此不要在 EventLoop 线程中执行耗时操作。