Netty 的 Reactor 线程模型是什么?
更新时间:2026-09-18。问题驱动短答页。异步事件驱动基础见异步和阻塞 IO 的区别,事件循环被阻塞的症状见 async/await 事件循环阻塞。
一句话回答
Netty 的 Reactor 模型核心是 EventLoop:每个 EventLoop 绑定一个常驻线程和一个 Selector,注册到它上面的所有 Channel 的读、写、连接、任务队列都由这同一个线程串行处理。一个 Channel 在生命周期内只属于一个 EventLoop,因此 Channel 上的操作天然线程封闭(thread confinement),业务 handler 一般无需加锁。
三层关系
EventLoopGroup(一组 EventLoop)
└─ EventLoop = 1 个线程 + 1 个 Selector + 任务队列
└─ 注册多个 Channel(TCP 连接)
└─ Pipeline 上的 Handler 在该 EventLoop 线程被串行回调- Selector:基于 NIO epoll/select,一个线程监听大量 Channel 的就绪事件。
- 串行无锁化:同一 Channel 的事件不会并发执行,避免了多线程竞争。
- 不同 Channel 可能在不同 EventLoop:多个 EventLoop 分摊全部连接,贴近 CPU 核数即可。
主从 Reactor 多线程模型(Netty 典型用法)
EventLoopGroup boss = new NioEventLoopGroup(1); // 接收连接
EventLoopGroup worker = new NioEventLoopGroup(); // 处理读写/业务
new ServerBootstrap().group(boss, worker)...| 角色 | 职责 | 线程数 |
|---|---|---|
| bossGroup | accept 新连接,注册到 worker | 通常 1 个即可(accept 很轻) |
| workerGroup | 已建立连接的 read/write、Pipeline 回调、定时/普通任务 | 默认 2×核数,按负载调 |
这对应 Reactor 模式演进:单 Reactor 单线程(Redis 6 前那种)→ 单 Reactor 多线程 → 主从 Reactor 多线程(Netty 默认:accept 与处理分离)。
为什么不能在 IO 线程里做阻塞操作
worker EventLoop 线程数很少,却各自身背成千上万个 Channel。一旦在 handler 里写阻塞 JDBC、同步 HTTP、sleep、重 CPU 计算或等待锁,这个 EventLoop 线程被占住,它名下所有连接的读写都暂停。正确做法:
- 把阻塞/重活提交到独立业务线程池,处理完再用
channel.writeAndFlush(Netty 会保证回到该 Channel 的 EventLoop 线程执行,线程安全)。 - IO 线程里只做非阻塞、短小的编解码和转发。
- 这与异步模型「事件线程不能阻塞」是同一条铁律。
常见疑问
- handler 里要加锁吗? 同一 Channel 的回调由唯一 EventLoop 串行执行,处理 Channel 自身状态通常不用锁;跨 Channel/共享可变状态才需保护。
- EventLoop 怎么选线程数? IO 密集接近核数即可,Netty 默认 2 倍核数;多了徒增切换。
- writeAndFlush 从别的线程调安全吗? 安全,Netty 会把操作封装成任务投递到该 Channel 所属 EventLoop 的队列执行。
- 如何并行利用多核? 连接天然分散到多个 EventLoop;单条连接仍是串行的,重活要显式卸载到业务线程池。
总结
Netty 用主从 Reactor 多线程模型:一个 EventLoop 绑定一个常驻线程、一个 Selector 和任务队列,多个 Channel 注册其上并由该线程串行处理全部 IO 与 Pipeline 回调,一个 Channel 终身归属一个 EventLoop 从而靠线程封闭实现无锁;bossGroup(通常 1 线程)只负责 accept 新连接,workerGroup(约 2 倍核数)负责已建立连接的读写与业务,多 EventLoop 把海量连接摊到多核;铁律是不能在 EventLoop IO 线程上执行阻塞或重计算,否则它名下所有连接一起停摆,必须把重活卸载到独立业务线程池、完成后用线程安全的 writeAndFlush 投递回所属 EventLoop。