Reactor是纯C实现的异步网络事件分发器,只负责监听socket状态、分发就绪事件给Worker、维护心跳,不执行PHP代码或业务逻辑。

Reactor 不是 PHP 代码执行层,它是纯 C 实现的异步网络事件分发器——你写的 onConnect、onReceive 回调根本不会在这里运行。
Reactor 是谁在干活?
它不解析 HTTP、不执行业务逻辑、不调用你的 PHP 函数。它只干三件事:监听 socket 是否可读/可写/出错、把就绪的连接或数据包交给 Worker 进程、定期踢掉空闲连接(靠 heartbeat_interval 和 timewheel)。整个过程不涉及任何 PHP 用户态代码,全是底层 epoll/kqueue/select 的封装。
常见错误现象:strace -p $pid 看到大量 epoll_wait 调用但 CPU 却不高;top 显示 reactor_thread 占用极低,而 worker_proc 吃满 CPU——这说明瓶颈不在 Reactor,别瞎调 max_connection 或 reactor_num。
- Reactor 线程默认和 CPU 核心数一致(
reactor_num = cpu_num),改多没用,改少会排队阻塞 - 它不处理请求内容,所以
dispatch_mode = 2(FD 取模)下,同一个 TCP 连接的数据永远进同一个 Worker,但 Reactor 自己并不维护这个映射关系——那是调度策略,不是 Reactor 职责 - 如果你在
onReceive里做 sleep 或 file_get_contents,Worker 卡住,Reactor 依然飞快收包,只是包全堆在内核缓冲区或 Swoole 的 socket_array 里,最终触发buffer_output_size限制断连
怎么知道 Reactor 正在被拖慢?
看 reactor->event_num 和 reactor->max_event_num 的比值,以及 reactor->timeout_msec 是否频繁触发。Swoole 本身不暴露这些字段,但你可以通过 swoole_server->stats() 查 connection_num 和 accept_count 差值间接判断积压程度。
使用场景:压测时 accept_count 增速远低于连接建立速率,同时 connection_num 长期卡在某个值不上升,大概率是 Reactor 接受新连接变慢——检查是否触发了 EMFILE(文件描述符耗尽),或 disable_accept = 1 已被置位。
- 不要依赖
ulimit -n输出值,Swoole 启动时会调getrlimit获取真实 soft limit,若没显式调setrlimit,可能只有 1024 -
reactor_num设太高反而降低性能:线程切换开销上升,且多个 Reactor 线程争抢同一个 listen socket 的 accept 队列(Linux 内核 5.10+ 才支持 SO_REUSEPORT 多队列分流) - 观察
/proc/$pid/fd/下 fd 数量,如果接近max_connection但connection_num很低,说明大量连接卡在三次握手完成前,可能是 SYN queue 溢出(需调net.ipv4.tcp_max_syn_backlog)
dispatch_mode 怎么选才不翻车?
这不是 Reactor 的配置项,而是 Master 进程协调 Reactor 与 Worker 之间投递策略的开关。选错会导致连接状态丢失、session 错乱、负载不均,但 Reactor 本身完全无感。
参数差异:
-
dispatch_mode = 1(轮询):最公平,但无法保证同个 TCP 连续包进同一 Worker,不适合需要长连接上下文的场景 -
dispatch_mode = 2(FD 取模):简单高效,但客户端用连接池复用少量连接时,90% 请求只打到少数几个 Worker,其他 Worker 闲置 -
dispatch_mode = 3(忙闲分配):实际负载更均衡,但连接归属随机,server->connections在不同 Worker 里不可见,必须用 Redis 存 session -
dispatch_mode = 4(IP 取模):适合 Web 场景,但 NAT 网关后所有用户 IP 相同,直接失效
性能影响:模式 2/4/5 用取模运算,几乎零开销;模式 3 需遍历 Worker 状态数组找空闲进程,Worker 数 > 32 时有轻微延迟;模式 1 最简单,但网络栈局部性差,cache miss 更多。
为什么改了 reactor_num 没效果?
因为 Swoole 默认启用多线程 Reactor,但你启动的是 Swoole\Http\Server 或 Swoole\WebSocket\Server,它们内部强制将 reactor_num 限制为 1——除非你显式调用 $server->set(['reactor_num' => 4]) 并确保未启用 task_worker(否则 Manager 进程会忽略该设置)。
容易踩的坑:
- PHP-FPM 用户容易误以为 “Reactor = nginx”,其实 Reactor 更像 libevent + epoll 封装,而 Worker 才对应 php-fpm 子进程
- 在
onStart里打印posix_getpid()和posix_getppid(),你会发现 Reactor 线程没有独立 PID,它和 Master 进程共享 PID,只是 Linux 的 LWP(轻量级进程) - 想调试 Reactor 行为?别 echo,别 var_dump,它们会阻塞整个线程。用
swoole_trace_log或gdb -p $pid -ex 'thread apply all bt' -ex quit看线程栈
真正关键的不是 Reactor 多不多,而是它转给 Worker 的数据包是否干净、及时、不粘包——这取决于你有没有正确设置 open_tcp_nodelay、package_max_length 和协议解析逻辑,而不是 Reactor 本身。










