Reactor 模型是什么?有哪几种?
简化版
Reactor 是一种事件驱动的高并发网络编程模型:用 IO 多路复用(epoll) 监听所有连接上的事件(可读、可写、新连接),有事件到来就分发(dispatch)给对应的处理器(Handler) 去处理。核心是「一个(或几个)线程用 epoll 监听海量连接,只处理有事件的那些」,避免了「一连接一线程」的开销。按线程划分有三种:单 Reactor 单线程、单 Reactor 多线程、主从 Reactor 多线程(Netty 默认用主从多 Reactor)。
详细版
Reactor 的三个核心组件:
- Reactor(反应器/分发器):用 epoll 监听事件,事件来了负责分发给对应的 Acceptor 或 Handler;
- Acceptor(接受器):专门处理「新连接建立」事件(accept 新连接);
- Handler(处理器):处理已建立连接上的「读写」事件和业务逻辑。
三种 Reactor 结构:
- 单 Reactor 单线程:一个线程用 epoll 监听所有事件,accept 新连接、读写数据、处理业务全在这一个线程。简单,但业务处理阻塞会拖垮整个服务。**Redis(早期)**是典型。
- 单 Reactor 多线程:一个 Reactor 线程负责 epoll 监听和 IO 读写,但把耗时的业务处理丢给一个线程池。IO 和业务分离,业务不再阻塞 IO 线程。
- 主从 Reactor 多线程:主 Reactor(mainReactor)专门 accept 新连接,接受后把连接分给从 Reactor(subReactor,通常多个) 去负责该连接的 IO 读写,业务再丢给 worker 线程池。分工最细、并发能力最强,Netty 默认就是这种。
完整版教学
一、Reactor 要解决什么:告别「一连接一线程」
传统的网络服务器用「一连接一线程/进程」——每来一个连接就开一个线程阻塞地处理它。这在连接少时简单直接,但连接一多(上万)就崩了:线程内存(每个约 1MB 栈)、上下文切换开销爆炸(这正是 C10K 问题,详见那道题)。
Reactor 的思路是反过来:不为每个连接配一个线程,而是用少量线程 + IO 多路复用——一个线程用 epoll 同时盯着成千上万个连接,只有哪个连接来了事件(有数据可读、可写、新连接),才去处理哪个。空闲的连接不占线程。这样少数几个线程就能撑起海量连接,这是所有高性能网络框架(Nginx、Redis、Netty)的共同基石。
「Reactor」这个名字的含义就是:它像一个「反应器」,静静地等待事件,事件一来就「反应」——分发给对应的处理器。 这是典型的事件驱动编程。
二、事件分发:Reactor 的核心机制
Reactor 的运转围绕「监听事件 → 分发事件」这个循环:
- Reactor 用 epoll 监听所有注册的 fd(监听 socket + 各连接 socket);
epoll_wait返回一批就绪事件;- Reactor 判断每个事件的类型并分发:
- 是监听 socket 就绪(有新连接)→ 交给 Acceptor,accept 建立新连接,并把新连接注册到 epoll;
- 是连接 socket 就绪(有数据可读/可写)→ 交给对应的 Handler,读数据、处理业务、写响应。
这个「事件循环 + 分发」就是 Reactor 的灵魂。它把「什么时候有事」交给 epoll,把「有事了怎么处理」交给 Handler,Reactor 自己只管「牵线搭桥」。
三、单 Reactor 单线程:简单但有短板
最基础的形态——一个线程包揽一切:epoll 监听、accept、读写、业务处理全在这一个线程里。
- 优点:模型极简,没有多线程的锁和竞争问题;
- 致命短板:只要某个业务处理耗时(如一个复杂计算、一次慢查询),这一个线程就被占住,期间所有其他连接的事件都处理不了,整个服务卡住。
所以它只适合「业务处理极快」的场景。Redis 早期用单 Reactor 单线程正是因为 Redis 命令都是纯内存操作、极快,不会长时间占用线程(Redis 6 后网络 IO 部分才引入多线程)。
四、单 Reactor 多线程:IO 与业务分离
针对单线程「业务阻塞拖垮 IO」的问题,改进是把业务处理搬到线程池:
- Reactor 线程:仍负责 epoll 监听、accept、以及连接的读写 IO;
- worker 线程池:Handler 读到数据后,把耗时的业务处理丢给线程池,处理完再由 IO 线程写回。
这样 IO 线程不再被业务阻塞,能持续处理事件。但单个 Reactor 线程要处理所有连接的 IO 读写,在超高并发下这一个线程可能成为瓶颈——于是有了主从 Reactor。
五、主从 Reactor 多线程:Netty 的选择
这是并发能力最强的形态,把「接受连接」和「处理连接 IO」也分开:
- 主 Reactor(mainReactor):只负责 accept 新连接。新连接建立后,把它「分派」给某个从 Reactor;
- 从 Reactor(subReactor,一组):每个从 Reactor 负责一批连接的 IO 读写(各自有独立的 epoll);
- worker 线程池:业务处理再丢给它。
分工:主 Reactor 管「进门」,从 Reactor 们管「和各自的客人读写」,worker 池管「干业务活」。三层分离,把连接接受、IO 读写、业务处理的压力分摊到不同线程,充分利用多核。
Netty 的默认模型就是主从 Reactor 多线程——bossGroup(主 Reactor,接受连接)+ workerGroup(从 Reactor,处理 IO),这也是它高性能的关键。
六、常见误区与追问
| 考点 | 正确口径 |
|---|---|
| 事件分离器 | select/epoll 等等待就绪事件 |
| 事件处理器 | accept、read、write、业务处理回调 |
| 线程模型 | 单 Reactor、主从 Reactor、多 worker |
loop:
events = selector.wait()
for event in events:
if accept: create connection
if read: decode request
if write: flush response
Reactor 的核心是把“等 IO”集中到事件循环,把“处理事件”分发给对应 handler。
- 误区:Reactor 就是一个线程。 Reactor 是模式,既可以单线程,也可以主从 Reactor 加 worker 线程池。
- 误区:所有业务都应放在事件循环里执行。 耗时业务会阻塞事件循环,应交给 worker 或异步后端处理。
- 误区:Reactor 只适合网络服务器。 只要是事件驱动 IO,文件、定时器、信号等也可纳入类似事件循环。
- 追问:单 Reactor 单线程有什么问题? 实现简单,但 CPU 密集任务和慢业务会拖住所有连接。
- 追问:主从 Reactor 解决什么? 主 Reactor 负责 accept,从 Reactor 负责连接读写,分散事件循环压力。
- 追问:Netty 为什么是典型 Reactor? EventLoop 监听 channel 事件,并把事件分发到 ChannelPipeline 中的 handler。
七、加强记忆
Reactor 是事件驱动的高并发模型:用 IO 多路复用(epoll)监听海量连接的事件,就绪了就分发给 Acceptor(接新连接)或 Handler(读写 + 业务),用少量线程撑海量连接,告别「一连接一线程」。三种:单 Reactor 单线程(简单、业务阻塞会拖垮,Redis 早期)、单 Reactor 多线程(业务丢线程池、IO 与业务分离)、主从 Reactor 多线程(主 accept + 从 IO + worker 业务,Netty 默认,并发最强)。