什么是 Reactor 模式?单 Reactor 单线程、多线程、主从 Reactor 有什么区别?
简化版
Reactor 是一种基于 I/O 多路复用 + 事件驱动的高性能网络编程模式——用少量线程(甚至一个)通过 Selector(底层 epoll)监听大量连接的事件(可读、可写、新连接),哪个就绪就处理哪个,避免「一个连接一个线程」的浪费。它有三种演进:① 单 Reactor 单线程——一个线程既监听事件又处理业务,简单但业务耗时会阻塞整个事件循环;② 单 Reactor 多线程——一个线程负责监听、一个线程池处理业务,业务不再阻塞事件循环,但单个 Reactor 监听所有连接可能成瓶颈;③ 主从 Reactor——主 Reactor 只负责接受新连接、从 Reactor(多个)负责处理连接的读写,业务再交给线程池,是 Netty、Redis 等高性能框架的标准模型。
详细版
Reactor 模式的核心角色:
Reactor(反应器):用 Selector 监听所有连接的事件,事件就绪就分发给对应处理器
Acceptor(接受器):专门处理"新连接"事件
Handler(处理器):处理具体连接的读、写、业务逻辑
三种 Reactor 模型对比:
| 模型 | 监听 | 业务处理 | 特点 |
|---|---|---|---|
| 单 Reactor 单线程 | 1 个线程 | 同一个线程 | 简单,但业务耗时会阻塞事件循环,无法利用多核 |
| 单 Reactor 多线程 | 1 个线程 | 线程池 | 业务不阻塞事件循环,但单 Reactor 监听是瓶颈 |
| 主从 Reactor 多线程 | 主 Reactor 接连接 + 从 Reactor(多个)处理读写 | 线程池 | 监听不再是瓶颈,充分利用多核,高性能框架标配 |
主从 Reactor 结构(Netty 的 boss/worker 就是这个):
MainReactor(主,1个):只监听 ACCEPT 事件(新连接),接受后把连接注册给 SubReactor
│
├─ SubReactor 1(从):监听一批连接的 READ/WRITE 事件 → 业务丢给线程池
├─ SubReactor 2(从):监听另一批连接...
└─ SubReactor N(从):...(通常 N = CPU 核数或 2 倍)
│
业务线程池:执行耗时的业务逻辑(DB、RPC)
⚠️ Reactor 模型的核心原则是「别在事件循环线程里做耗时操作」。Reactor 线程(负责监听和分发事件)必须快进快出——如果在它里面执行慢业务(查数据库、调远程接口),会阻塞整个事件循环,导致其他连接的事件得不到及时处理。所以耗时业务一定要丢给独立的业务线程池,Reactor 线程只做「IO 事件的监听和分发」。
完整版教学
一、Reactor 要解决什么:C10K 与线程浪费
传统 BIO 模型是「一个连接一个线程」——每来一个连接就分配一个线程处理它的读写。这在连接少时没问题,但高并发下是灾难:
BIO:1 万个连接 = 1 万个线程
每个线程约 1MB 栈内存 → 10GB 内存
大量线程上下文切换 → CPU 忙于切换而非干活
而大部分连接其实在"空等"(等客户端发数据),线程白白占着
→ 这就是著名的 C10K 问题(一万连接就扛不住)
Reactor 的洞察是:大部分时间连接是空闲的(在等 IO),真正需要处理的只是「此刻就绪的少数连接」。所以不必给每个连接配线程,而是用少量线程 + IO 多路复用(Selector/epoll)监听所有连接,哪个连接就绪了(有数据可读、可写)就处理哪个。这样几个线程就能管几万连接,从「一连接一线程」变成「一线程管多连接」。这是 Reactor 高性能的根基——用事件驱动替代线程堆砌。
二、Reactor 的三个角色与事件循环
Reactor 模式有三个核心角色,协作完成「监听-分发-处理」:
Reactor(反应器):核心,内含一个事件循环
while (true) {
selector.select(); // 阻塞等待,直到有事件就绪
for (就绪的事件) {
if (是新连接 ACCEPT) → 交给 Acceptor
if (是可读 READ) → 交给对应连接的 Handler 读数据、处理
if (是可写 WRITE) → 交给 Handler 写数据
}
}
Acceptor:处理 ACCEPT 事件,接受新连接并注册到 Reactor
Handler:处理具体连接的读写和业务
核心是那个 while 事件循环(event loop)——不断地 select() 等待事件,事件来了就分发给对应的处理器。这就是「Reactor(反应器)」名字的由来:它对事件做出「反应」,事件驱动、被动响应。理解「Reactor = 事件循环 + 事件分发」,就抓住了所有三种模型的共同骨架——它们的区别只在「用几个线程跑事件循环、业务在哪处理」。
三、单 Reactor 单线程:简单但有瓶颈
最基础的模型——一个线程包办一切:监听事件、接受连接、读写数据、执行业务,全在同一个线程里:
单线程 Reactor:
一个线程的事件循环:
select() → 有连接可读 → 读数据 → 执行业务逻辑 → 写回结果 → 继续 select()
所有事情都在这一个线程里串行做
优点:实现简单、无并发问题(单线程,不用加锁)。Redis 的核心就是这个模型(单线程事件循环 + 多路复用),所以 Redis 命令天然线程安全。
致命缺点:业务耗时会阻塞整个事件循环。如果某个连接的业务处理很慢(如复杂计算、等 IO),这个线程就卡在那,其他所有连接的事件都得不到处理——一个慢请求拖垮所有连接。而且无法利用多核(就一个线程)。所以它只适合「业务处理极快、纯内存操作」的场景(如 Redis 的内存命令),业务一慢就不行。这就催生了多线程模型。
四、单 Reactor 多线程:业务丢给线程池
为了解决「业务阻塞事件循环」,第二种模型把业务处理拆出去:
单 Reactor 多线程:
Reactor 线程(1 个):只负责 select() 监听事件、读写数据(IO 操作快)
→ 读到数据后,把"业务处理"丢给业务线程池
业务线程池(多个线程):执行耗时的业务逻辑
→ 处理完把结果交回给 Reactor 线程写出
关键改变:Reactor 线程只做「快」的 IO 事件监听和数据读写,「慢」的业务丢给线程池。这样即使业务耗时,也是线程池的线程在忙,Reactor 线程能继续监听和处理其他连接的事件,不会被阻塞。同时业务由多线程处理,利用了多核。
但它还有一个瓶颈:只有一个 Reactor 线程负责监听所有连接。在超高并发下(大量连接同时有事件),单个 Reactor 线程的「监听 + 读写」也可能忙不过来,成为瓶颈。尤其是「接受新连接」和「已有连接的读写」都挤在一个 Reactor 上,连接量极大时处理不过来。这就引出了主从模型。
五、主从 Reactor:监听也分工
第三种模型——主从 Reactor 多线程,是把「监听」这件事也拆开,让多个 Reactor 分担:
主从 Reactor:
MainReactor(主,1个):只负责监听 ACCEPT(新连接)事件
→ 接受新连接后,把连接分配给某个 SubReactor
SubReactor(从,多个,通常 = CPU 核数或 2 倍):
→ 每个 SubReactor 负责监听一批连接的 READ/WRITE 事件
→ 读到数据后,把业务丢给业务线程池
业务线程池:执行耗时业务
分工:主管"接客"、从管"读写"、线程池管"干活"
核心改进:「接受新连接」和「处理已有连接的读写」分开——主 Reactor 专职接客(ACCEPT),从 Reactor(多个)分担所有连接的读写监听。这样:① 接受连接不会被读写拖累(主 Reactor 只干这一件事,能快速接入新连接);② 读写监听由多个从 Reactor 分担,充分利用多核,不再是单点瓶颈。
这就是 Netty 的标准线程模型——bossGroup(主 Reactor,接连接)+ workerGroup(从 Reactor,处理读写),也是绝大多数高性能网络框架的选择。它把「监听、读写、业务」三层都做了合理的线程分工,能扛住海量并发连接。
六、Reactor 在实际框架中的应用
理论对应到实际框架,就更清晰了:
| 框架/系统 | 采用的模型 | 说明 |
|---|---|---|
| Redis | 单 Reactor 单线程 | 命令是纯内存操作、极快,单线程够用且免锁(6.0 后网络 IO 才多线程) |
| Netty | 主从 Reactor 多线程 | bossGroup 接连接、workerGroup 读写、业务丢 EventExecutor |
| Nginx | 多 Reactor(多进程/多线程) | 每个 worker 进程一个事件循环 |
| Tomcat(NIO 模式) | 类 Reactor | Acceptor 接连接、Poller 轮询、Worker 处理 |
一个关键认知:Reactor 线程数不是越多越好。Reactor 线程做的是 IO 事件监听(CPU 密集度低但要求响应快),通常设为 CPU 核数或 2 倍即可;真正耗时的业务才需要大线程池。而且 Reactor 线程里绝不能做阻塞操作(这条铁律再强调一次)——一旦阻塞,它负责的那批连接全部卡住。所以用 Netty 时,业务里的 DB 查询、RPC 调用一定要丢到业务线程池,不能在 EventLoop 里直接做。
记忆钩子:「Reactor = 多路复用 + 事件循环(对事件做反应),解决 C10K 的线程浪费;单 Reactor 单线程(简单如 Redis、业务阻塞会卡死)→ 单 Reactor 多线程(业务丢线程池、但单监听瓶颈)→ 主从 Reactor(主接连接、从管读写、Netty 标配);铁律:Reactor 线程别做阻塞操作」。
七、常见误区与追问
- 误区:Reactor 模式就是多线程。 Reactor 的核心是「IO 多路复用 + 事件驱动」,单 Reactor 单线程(如 Redis)也是 Reactor;线程数是演进出的变体,不是本质。
- 误区:单 Reactor 单线程性能差。 Redis 用它扛住极高 QPS——因为它的命令是纯内存操作极快;它的短板是「业务耗时会阻塞事件循环」,不适合慢业务,不是绝对慢。
- 误区:Reactor 线程越多越好。 Reactor 线程做 IO 监听,一般设 CPU 核数或 2 倍即可;耗时业务才需要大线程池,Reactor 线程过多没意义。
- 误区:可以在 Reactor(EventLoop)线程里查数据库。 绝对不行——会阻塞整个事件循环,导致该线程负责的所有连接卡住;耗时业务必须丢给业务线程池。
- 追问:Netty 的 bossGroup 和 workerGroup 对应 Reactor 的什么? bossGroup 是主 Reactor(负责 ACCEPT 接受新连接)、workerGroup 是从 Reactor(负责已连接的 READ/WRITE 读写监听),是主从 Reactor 模型的实现。
- 追问:Redis 为什么用单线程 Reactor 还这么快? 命令是纯内存操作、执行极快,单线程避免了锁和上下文切换开销,配合 IO 多路复用监听大量连接;瓶颈在网络 IO,所以 6.0 后把网络 IO 部分改成多线程。
- 追问:Reactor 和 Proactor 的区别? Reactor 是「就绪」通知(IO 就绪了通知你,你自己去读写,同步 IO);Proactor 是「完成」通知(内核帮你读写完成后通知你,异步 IO);Reactor 对应 NIO/epoll,Proactor 对应 AIO。
八、加强记忆
Reactor 是解决 C10K(一连接一线程扛不住海量连接)的高性能网络模式,核心是「IO 多路复用 + 事件驱动」——用少量线程通过 Selector(epoll)监听大量连接的事件,一个事件循环不断 select() 等事件、就绪了就分发给处理器(Reactor 对事件做「反应」)。三种演进只在「用几个线程、业务在哪处理」上不同:① 单 Reactor 单线程——一个线程包办监听+读写+业务,简单免锁(Redis 用它,纯内存命令够快),但业务耗时会阻塞整个事件循环、无法利用多核;② 单 Reactor 多线程——Reactor 线程只做 IO 监听读写、业务丢线程池(不再阻塞事件循环、用多核),但单个 Reactor 监听所有连接仍是瓶颈;③ 主从 Reactor——主 Reactor 专职接受新连接、多个从 Reactor 分担读写监听、业务再丢线程池,是 Netty(bossGroup+workerGroup)、Nginx、Tomcat NIO 的标配。铁律:Reactor/EventLoop 线程绝不能做阻塞操作(否则那批连接全卡住),DB/RPC 必须丢业务线程池。一句话「多路复用+事件循环解决 C10K,单线程简单会卡、多线程业务丢池、主从分工 Netty 标配,Reactor 线程别阻塞」。