1. Reactor模式全景

要理解Reactor,先要理解它试图解决什么问题。传统阻塞式网络服务通常采用“一连接一线程”模型:主线程在 accept() 上阻塞,每接受一个客户端连接就创建一个新线程,该线程在 read() 上阻塞等待数据,处理完再回到 read() 继续等待。这个模型在连接数很少时逻辑直观、代码简单,但当并发连接达到数千甚至上万时,线程数量、栈内存、上下文切换和锁竞争会迅速压垮系统。更关键的是,绝大多数连接在任意时刻都是“空闲”的——它们没有数据可读,却各自占用一个线程在阻塞等待,这是巨大的资源浪费。

Reactor模式的核心思想是:把“等待IO就绪”这件事从业务线程中剥离出来,交给一个或少数几个事件循环统一完成。业务代码不再阻塞在某个具体连接上,而是注册“当这个连接可读时请调用我的处理函数”。操作系统提供的IO多路复用机制(Linux下的 epoll、BSD/macOS下的 kqueue、Windows下的 IOCP 或 select/poll)正是实现这一思想的基础设施:它允许一个线程同时监视成千上万个文件描述符,并在其中任意一个就绪时返回。

Reactor的三个核心角色

Reactor模式由三个职责清晰的角色构成,理解它们的边界是掌握整个模式的关键。

Reactor(反应器/事件循环):它是整个模式的中枢,持有一个多路复用器(如 epoll 实例)和一张“文件描述符 → 处理器”的注册表。Reactor循环执行三步:调用多路复用接口等待事件、取出就绪的事件集合、根据注册表把每个事件分发给对应的处理器。Reactor本身不处理业务,它只负责“监听—分发”。

Acceptor(连接接受器):它专门处理监听套接字上的“新连接到达”事件。当Reactor检测到监听fd可读时,会调用Acceptor,由它执行 accept() 得到新的连接fd,然后把这个新fd连同对应的Handler注册到Reactor中。Acceptor是连接建立阶段的入口,一旦连接建立完成,它的任务就结束了。

Handler(处理器):它绑定到某个具体的连接fd上,负责该连接上的读写事件处理。典型实现中,Handler会持有连接缓冲区、协议解析状态机和业务回调。当Reactor通知“该fd可读”时,Handler执行非阻塞 read(),把数据放入缓冲区并尝试解析;当需要发送响应时,Handler把数据写入发送缓冲区,并关注可写事件,由Reactor在fd可写时回调它继续发送。

关键区分:Reactor负责“什么时候可以动”,Handler负责“动的时候做什么”。把这两件事分开,是事件驱动区别于阻塞IO的本质。

从单Reactor单线程到主从多Reactor

Reactor模式并非只有一种形态,随着并发压力和CPU核数的增长,它演化出几种典型拓扑。

单Reactor单线程:一个线程同时承担事件监听、连接接受、读写处理和业务计算。它的优点是实现简单、无并发竞争;缺点是所有工作串行,一旦某个Handler的业务处理耗时较长(如复杂计算或磁盘IO),整个事件循环都会被阻塞,其他连接的事件无法及时响应。它适合连接数不多、处理逻辑轻量的场景,例如教学示例或内部管理端口。

单Reactor多线程:Reactor仍然只有一个,负责监听和分发事件,但把Handler中的业务处理部分交给线程池执行。这样耗时的业务逻辑不再阻塞事件循环,IO事件的分发保持流畅。需要注意的是,如果Handler的读写操作本身也在线程池中执行,就必须处理共享缓冲区的并发问题;通常的做法是让Reactor线程只做非阻塞读写,把纯计算或阻塞调用交给线程池。

主从多Reactor:这是Netty、Muduo等高性能框架采用的经典结构。主Reactor(MainReactor)只负责监听和接受新连接,它把 accept() 得到的新连接fd分配给某个从Reactor(SubReactor);每个从Reactor运行在独立线程中,各自持有一个多路复用器,负责所辖连接上的读写事件分发。连接建立后,主Reactor不再参与该连接的任何后续工作。这种结构把“接受连接”和“处理连接”彻底分离,充分利用多核CPU,同时避免了单Reactor成为瓶颈。实践中,从Reactor的数量通常设置为CPU核数或核数的两倍。

Reactor 拓扑演进:单Reactor单线程 → 单Reactor多线程 → 主从多Reactor 单Reactor 单线程 Reactor epoll_wait + 分发 Acceptor accept() 新连接 Handler read / 业务 / write 业务计算 阻塞事件循环 所有环节串行执行 适合轻量、低并发场景 单Reactor 多线程 Reactor epoll_wait + 分发 Acceptor accept() 新连接 Handler(IO) 非阻塞 read / write 线程池 业务计算并行执行 IO分发与业务计算分离 Reactor不再被业务阻塞 主从多Reactor MainReactor 仅监听 + accept Acceptor 分配新连接到Sub SubReactor × N 各自 epoll_wait Handler × N 绑定连接,独立处理 接受与处理彻底分离 充分利用多核CPU

三种拓扑的对比与选型依据

拓扑 事件监听 连接接受 IO读写 业务计算 典型适用场景
单Reactor单线程 1个线程 同线程 同线程 同线程 教学示例、内部工具、连接数少
单Reactor多线程 1个线程 同线程 Reactor线程 线程池 业务计算较重、连接数中等
主从多Reactor Main + Sub Main线程 Sub线程 Sub线程或线程池 高并发网关、RPC框架、消息中间件

选型时可以从三个维度判断:连接规模、单连接处理耗时、CPU核数。如果连接数在几百以内且处理逻辑很轻,单Reactor单线程足够且最易调试;如果业务计算明显耗时(例如涉及加解密、序列化、数据库查询),应把计算移入线程池,采用单Reactor多线程;如果连接数达到数千以上、且服务器有多核,主从多Reactor几乎是必然选择,因为它让每个SubReactor在自己的线程中独立运行事件循环,连接之间的隔离性更好,单个连接的异常也不容易拖垮全局。

一个具体的连接生命周期示例

以主从多Reactor为例,假设客户端发起一次连接并发送一条请求,完整路径如下:

  1. MainReactor在 epoll_wait 中检测到监听fd可读,调用Acceptor执行 accept(),得到新连接fd(例如fd=42)。
  2. Acceptor按照轮询或哈希策略,把fd=42注册到某个SubReactor的epoll实例中,关注 EPOLLIN 事件,并创建对应的Handler绑定该fd。
  3. 客户端发送数据后,SubReactor的 epoll_wait 返回fd=42可读,SubReactor调用该Handler的 onRead()。
  4. Handler执行非阻塞 read(),把数据读入接收缓冲区,驱动协议解析状态机;若一条完整请求已解析完成,则触发业务回调。
  5. 业务处理产生响应数据,Handler把响应写入发送缓冲区,并向SubReactor注册 EPOLLOUT 关注。
  6. SubReactor检测到fd=42可写,回调Handler的 onWrite(),把发送缓冲区中的数据写出;写完后取消 EPOLLOUT 关注,回到只监听可读的状态。

这个流程中,MainReactor只参与了第1步,后续全部由SubReactor和Handler完成。这正是主从结构“接受与处理分离”的直接体现。理解这条路径,也就理解了Reactor模式在真实框架中是如何落地的。

Reactor模式的本质不是“用了epoll”,而是把事件等待、事件分发、事件处理三层职责解耦,让少量线程能够管理大量连接。拓扑的选择,本质上是在“实现复杂度”和“并发能力”之间做权衡。