Netty 的 Reactor 线程模型是什么?BossGroup 和 WorkerGroup 如何分工?
简化版
Netty 常用主从 Reactor 多线程模型:BossGroup 负责监听端口、接收新连接(accept),接到后把连接注册给 Worker;WorkerGroup 负责已建立连接的读写 I/O 和 pipeline 事件处理。核心机制是 EventLoop——每个 EventLoop 是一个单线程事件循环,一个 Channel 一旦建立就固定绑定到一个 EventLoop,这个连接的所有 I/O 事件都在同一个线程串行执行(顺序有保证、单连接内免锁)。因为 Worker 用少量线程(约 CPU 核数)通过 I/O 多路复用管理海量连接,所以能高并发;但绝不能在 EventLoop 里做阻塞操作——会拖住同线程上所有连接。
详细版
主从 Reactor 模型结构:
BossGroup(1~少量 EventLoop)
监听端口、accept 新连接
│ 把新连接注册给
↓
WorkerGroup(多个 EventLoop,约 2×CPU 核数)
每个 EventLoop 管理多个 Channel 的读写
EventLoop1 ── Channel A, Channel B ...
EventLoop2 ── Channel C, Channel D ...
标准启动代码:
EventLoopGroup boss = new NioEventLoopGroup(1); // Boss:接连接
EventLoopGroup worker = new NioEventLoopGroup(); // Worker:默认 2×CPU 核
ServerBootstrap b = new ServerBootstrap();
b.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new MyHandler());
}
});
关键规则:
- 一个 Channel 固定绑一个 EventLoop(一个 EventLoop 管多个 Channel)。
- 同一 Channel 的事件在同一线程串行执行 → 无并发、免锁。
- EventLoop 不能阻塞——阻塞会拖垮该线程上所有连接。
完整版教学
一、Reactor 模型解决什么
传统「一连接一线程」模型在长连接、高并发下成本极高:几万个连接就要几万个线程,内存爆炸、上下文切换开销巨大(见「Tomcat 线程模型」的类似讨论)。而且很多连接大部分时间是空闲的(等着下一个请求),一个线程干等浪费。
Reactor 模型用少量线程 + I/O 多路复用(Selector) 处理大量连接:线程只在连接「有事件(可读/可写)」时才处理它,空闲连接不占用线程。这非常适合「大量连接但单连接多数时间空闲」的网络服务(IM、推送、长连接网关)。Netty 用的是 Reactor 的进阶版——主从 Reactor 多线程模型。
二、BossGroup 的职责(接连接)
BossGroup 负责连接的建立:
- 绑定
ServerSocketChannel,监听端口、处理accept事件(有新客户端连进来)。 - 接到新连接后,它不负责处理这个连接上的业务读写,而是把新连接(
SocketChannel)注册给 WorkerGroup 里的某个 EventLoop。
因为**「建立连接」这个动作很快**(就是 accept 一下),所以 Boss 线程数量通常很少(常配 1 个就够,除非有大量瞬时新连接)。
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup(); // 默认约 2 * CPU 核数
new ServerBootstrap().group(boss, worker);
三、WorkerGroup 的职责(读写 I/O)
WorkerGroup 负责已建立连接的数据读写和事件处理:
- 每个 Worker EventLoop 用自己的 Selector 监听注册到它的多个 Channel 的读写事件。
- 某个 Channel 可读/可写时,触发它 pipeline 里的回调(
channelRead、编解码、业务 Handler)。 - 也执行提交给它的普通任务和定时任务(见「EventLoop」专题)。
一个 EventLoop 可以管理多个 Channel(用一个线程通过多路复用服务很多连接),但一个 Channel 注册后固定在一个 EventLoop 上(不会换)。WorkerGroup 的线程数默认是 2×CPU 核数(NioEventLoopGroup 无参构造)。
| 角色 | 主要职责 | 线程数量特点 | 不应该做的事 |
|---|---|---|---|
| BossGroup | 监听端口、处理 accept、新连接注册 | 1 个或少量 | 处理业务读写 |
| WorkerGroup | 处理已连接 Channel 的读写和 Pipeline 回调 | 默认约 2×CPU 核数 | 执行阻塞业务 |
| EventLoop | 单线程事件循环,管理多个 Channel | 一个线程绑定多个 Channel | 被慢 SQL、远程调用、sleep 阻塞 |
Reactor 模型的面试核心是分工和绑定关系:Boss 接连接,Worker 管读写,一个 Channel 固定绑定一个 EventLoop。
四、EventLoop 与 Channel 的绑定关系
这是理解 Netty 线程模型的核心:一个 Channel 一旦建立并注册,就固定绑定到一个 EventLoop(一个线程),这个连接的所有 I/O 事件都由这一个线程串行处理。
关系是「一个 EventLoop 对多个 Channel,一个 Channel 只属于一个 EventLoop」(一对多)。这个设计的意义在下一节。
五、单 Channel 串行处理的好处(免锁)
因为同一个 Channel 的所有事件都在同一个 EventLoop 线程串行执行,带来一个巨大的好处:处理单个连接的状态时,基本不需要加锁。
- 传统多线程模型下,「多个线程可能同时处理同一个连接」,要用锁保护连接状态——复杂且慢。
- Netty 模型下,一个连接只被一个固定线程处理,事件顺序执行、不会并发——Handler 里操作这个连接的状态(如累积的半包、会话状态)天然线程安全,不用锁。
并发复杂度从「多线程同时改同一连接(要锁)」变成了「单线程顺序处理(免锁),只要保证不阻塞这个线程」——这是 Netty 高性能又相对好写的关键。
六、业务线程池何时需要(EventLoop 不能阻塞)
前面说 Worker 用少量线程管海量连接——代价是:绝对不能在 EventLoop 里做阻塞或耗时操作!
因为一个 EventLoop 管理很多 Channel,如果某个 Handler 里执行了慢 SQL、远程调用、Thread.sleep、复杂计算,会霸占 EventLoop 这个单线程,导致同一个 EventLoop 上的所有其他连接都无法及时读写(延迟抖动、心跳误判、写堆积,见「EventLoop」专题)。
所以耗时/阻塞逻辑要交给「业务线程池」执行——Handler 里把耗时任务 submit 到业务线程池,处理完再通过 channel.writeAndFlush(Netty 自动切回 EventLoop 线程)把结果写回。Netty 也支持给某个 Handler 指定独立的 EventExecutorGroup。
七、线程数不是越多越好
Worker 线程数(NioEventLoopGroup 的大小)要合理:
- 太少:处理不过来,连接读写延迟。
- 太多:增加上下文切换开销,且线程多了 CPU 缓存局部性变差。
默认 2×CPU 核数是个经验值。实际要结合连接数、消息大小、业务耗时、部署资源压测来定——不是拍脑袋调大。CPU 密集的处理线程数应接近核数,I/O 等待多的可适当增加(但耗时 I/O 本就该放业务线程池,EventLoop 自身线程数不用太多)。
八、常见误区与追问
- 误区:BossGroup 会处理业务读写。 BossGroup 主要负责监听和 accept,新连接建立后会注册给 WorkerGroup,后续读写由 WorkerGroup 处理。
- 误区:一个 Channel 会在多个 EventLoop 之间轮流执行。 Channel 注册后固定绑定一个 EventLoop,同一连接事件在同一线程串行执行。
- 误区:Worker 线程越多吞吐越高。 线程过多会增加上下文切换和缓存失效,默认值只是经验起点,最终要靠压测。
- 追问:为什么单 Channel 内通常不用加锁? 因为同一 Channel 的 I/O 事件和 Pipeline 回调由同一个 EventLoop 串行执行,不会多个线程同时处理同一连接。
- 追问:EventLoop 中做慢 SQL 会怎样? 会阻塞这个 EventLoop 上的所有 Channel,导致读写延迟、心跳误判和写缓冲堆积。
- 追问:耗时业务应该放在哪里? 放到业务线程池或指定
EventExecutorGroup,处理完再通过channel.writeAndFlush写回。
九、加强记忆
Netty 主从 Reactor 多线程模型:BossGroup(1~少量 EventLoop)监听端口、accept 新连接,接到就注册给 Worker(建连很快,Boss 少);WorkerGroup(默认 2×CPU 核 EventLoop)管已连接的读写 I/O 和 pipeline 事件。核心是 EventLoop(单线程事件循环)+ 一个 Channel 固定绑一个 EventLoop(一 EventLoop 管多 Channel),同一连接的事件在同一线程串行执行 → 顺序保证、单连接内免锁(并发复杂度从「多线程改同连接要锁」变成「单线程顺序处理,只要别阻塞」)。黄金规则:EventLoop 绝不能阻塞——慢 SQL/远程调用/sleep 会拖住该线程上所有连接,耗时逻辑放业务线程池、回写用 writeAndFlush 切回。Worker 线程数默认 2×CPU 核,太多增加切换开销,按压测调。适合「海量连接、单连接多空闲」场景。