⚡ Reactor 模式详解

处理高并发 I/O 的核心设计模式 — Nginx / Redis / Node.js 背后的同一套思想

先从问题出发:高并发 I/O 的困境

传统的「一请求一线程」模型:每来一个客户端连接就 fork/create 一个线程去服务它。 线程在等待 I/O(读网络、读磁盘)时会阻塞,什么也干不了。 当并发连接数达到 10 万+ 时,内存和调度开销就会把系统压垮—— 这就是著名的 C10K 问题


解法的核心思路:不让线程等 I/O,让 I/O 好了之后来通知线程。 Reactor 模式就是这一思路的标准实现。

Reactor 模式的一句话定义

Reactor 模式是一种基于事件驱动的并发 I/O 设计模式。 它使用一个(或少数几个)线程, 通过操作系统提供的 I/O 多路复用(select / poll / epoll / kqueue) 机制,同时监听大量文件描述符(socket)上的 I/O 事件; 一旦某个描述符就绪,就把对应事件分发给预先注册的 Handler 来处理。


关键词:事件循环(Event Loop)事件分发(Dispatch)非阻塞 I/OHandler 回调

谁在用 Reactor?
项目Reactor 角色底层多路复用
Nginx每个 worker 进程一个 Reactorepoll / kqueue
Redis单线程 Reactor(ae 事件循环)epoll / kqueue / select
Node.js单线程 Reactor(libuv)epoll / kqueue / IOCP
Netty主从多 Reactor(Boss + Worker)epoll(Java NIO)
Memcached多 Reactor(libevent)epoll
Reactor 模式的四个核心组件
🔄 Reactor(反应堆)

也叫 Event Loop / Dispatcher
职责:不断调用 epoll_wait 等待事件, 把就绪事件路由给对应的 Handler。 它是整个模式的调度核心

📡 Handle(句柄)

就是文件描述符 fd, 代表一个 I/O 资源:监听 socket、连接 socket、管道、定时器 fd 等。 事件都附着在 Handle 上发生。

🔌 Event Demultiplexer(事件多路分解器)

操作系统提供的多路复用接口:
select / poll / epoll / kqueue
负责阻塞等待,直到至少一个 Handle 就绪再返回。

📋 Event Handler(事件处理器)

应用层注册的回调函数。 不同事件类型(可读、可写、连接、关闭) 对应不同 Handler。Handler 应快速执行, 不做耗时阻塞操作。

🚪 Acceptor(接受器)

特殊的 Handler,专门处理新连接到来事件。 调用 accept() 拿到新 socket fd, 为其创建 Connection Handler 并注册到 Reactor。

📦 Concrete Handler(具体业务处理器)

处理已建立连接上的读写业务逻辑: 读数据 → 解码 → 执行业务 → 编码 → 写回响应。 可以把耗时部分扔给线程池。

组件关系图
操作系统内核 Event Demultiplexer epoll / kqueue / select fd:3 listen sock fd:5 conn sock fd:6 conn sock fd:7 timer fd Reactor Event Loop · 注册表 · 分发逻辑 while(true) { epoll_wait → dispatch } Acceptor 处理新连接 Handler A 读写业务(fd:5) Handler B 读写业务(fd:6) 注册/等待事件 dispatch dispatch
单次请求完整处理流程(8 步)
Client Acceptor Reactor Demultiplexer Handler ① TCP connect() ② epoll_wait 返回 (EPOLLIN on listen fd) ③ dispatch → Acceptor ④ accept() → new fd ⑤ 注册 fd+Handler 到 Reactor ⑥ send(request) ⑦ epoll_wait 返回 (EPOLLIN on conn fd) ⑧ dispatch → Handler read() → 解析请求 执行业务逻辑 write() → 响应 response 数据 recv(response) ✓
关键设计原则

1. Handler 必须非阻塞 —— 若 Handler 内调用了阻塞操作(DB 查询、文件读写), Reactor 线程会被卡住,所有其他连接都饿死。解法:把耗时任务扔进线程池


2. 注册/注销是动态的 —— Acceptor 建立连接后把 (fd, Handler) 对插入 Reactor 的事件注册表;连接关闭时从表中移除,Reactor 不再监听该 fd。


3. 同一 fd 的读写都由同一 Handler 负责 —— 避免多线程竞争同一连接的状态,连接状态机天然线程安全。

单 Reactor 单线程 — 动态演示

演示:3 个客户端同时连接,Reactor 轮流分发事件,Handler 依次处理请求

三种经典变体
① 单 Reactor 单线程
单线程 Reactor (Event Loop) Acceptor Handler 代表:Redis(ae 事件循环)

✓ 无锁,实现简单
✗ CPU 单核,Handler 阻塞即全挂
适合:单核 CPU、业务极轻、延迟要求不高

② 单 Reactor 多线程
Reactor (单线程) Thread Pool T1 T2 T3 T4

✓ 可利用多核 CPU
✗ Reactor 仍单线程,高并发下 accept 成瓶颈
适合:中等并发、业务有 CPU 计算

③ 主从 Reactor 多线程(Netty 模型)
Main Reactor (1 个线程) epoll (listen fd) Acceptor 只负责建连 分配 Sub Reactor Pool(N 个线程) Sub Reactor 1 epoll (conn fds) Handler Thread Pool Sub Reactor 2 epoll (conn fds) Handler Thread Pool

✓ Main Reactor 专职 accept,Sub Reactor 并行处理 I/O,吞吐量最高
✓ 可完整利用多核,是现代高性能服务器首选
✗ 实现最复杂,需处理线程间任务分发
代表:Netty、Nginx(多 worker 进程版)

单 Reactor 单线程 — 核心伪代码(Python 风格)
# ── 1. 注册表 ──────────────────────────────────────────
handlers = {}   # fd → handler_func

def register(fd, event_type, handler):
    epoll.ctl(EPOLL_CTL_ADD, fd, event_type)
    handlers[fd] = handler

def unregister(fd):
    epoll.ctl(EPOLL_CTL_DEL, fd)
    del handlers[fd]

# ── 2. Acceptor ────────────────────────────────────────
def on_accept(listen_fd):
    conn_fd = accept(listen_fd)          # 拿到新连接 fd
    set_nonblocking(conn_fd)
    register(conn_fd, EPOLLIN, on_read)  # 注册读事件

# ── 3. Handler ─────────────────────────────────────────
def on_read(conn_fd):
    data = recv(conn_fd, 4096)
    if not data:
        unregister(conn_fd)
        close(conn_fd)
        return
    response = process(data)             # 业务逻辑(必须快!)
    send(conn_fd, response)

# ── 4. Reactor 主循环 ──────────────────────────────────
listen_fd = create_tcp_server("0.0.0.0", 8080)
set_nonblocking(listen_fd)
register(listen_fd, EPOLLIN, on_accept)

while True:
    events = epoll.wait(timeout=-1)    # 阻塞直到有事件
    for fd, event in events:
        handler = handlers.get(fd)
        if handler:
            handler(fd)                  # dispatch!
单 Reactor 多线程 — 关键差异
from concurrent.futures import ThreadPoolExecutor
pool = ThreadPoolExecutor(max_workers=16)

def on_read(conn_fd):
    data = recv(conn_fd, 4096)          # 读数据(非阻塞,快)
    # ↓ 耗时业务交给线程池,Reactor 立刻返回继续监听
    pool.submit(handle_business, conn_fd, data)

def handle_business(conn_fd, data):
    response = slow_db_query(data)       # 可以阻塞
    # 写回:注意需要线程安全,或 post 写事件到 Reactor
    send(conn_fd, response)
Reactor vs 其他模型对比
维度 BIO(一请求一线程) Reactor(事件驱动) Proactor(异步 I/O)
I/O 等待 线程阻塞等待 epoll 统一等待,不阻塞业务线程 内核完成后回调(IOCP / io_uring)
线程数 = 连接数(可达数千) 固定少量(1 ~ CPU 核数) 固定少量
数据拷贝时机 内核 → 用户(read 完成) 用户层调用 read(就绪才读) 内核直接完成 read,通知用户
实现难度 高(平台差异大)
跨平台 ✓ 好 ✓ 好(抽象层封装) ✗ 差(Windows IOCP vs Linux io_uring)
典型并发 数百 ~ 千 十万 ~ 百万 十万 ~ 百万
代表实现 传统 Java Servlet Nginx / Redis / Netty / Node.js Windows IOCP / io_uring
Reactor 模式核心价值总结

① 解耦事件检测与事件处理 —— Reactor 只管"谁就绪",Handler 只管"怎么处理",职责清晰。


② 用少量线程撬动海量并发 —— 线程不在 I/O 上等待,CPU 利用率高,内存占用低。


③ 可扩展 —— 新 I/O 类型只需实现新 Handler 并注册,不改动 Reactor 核心逻辑,符合开闭原则。


④ 注意边界 —— Reactor 不是银弹:Handler 中不能有阻塞操作;CPU 密集型任务仍需线程池; 单 Reactor 在极高 QPS 下 accept 仍可能成瓶颈(升级为主从 Reactor)。