dispatch_mode不是“选哪个更好”,而是“哪个匹配业务模型和代码风格”——选错会导致连接乱序、状态丢失、负载不均,甚至onconnect/onclose事件失效。

直接说结论:dispatch_mode 不是“选哪个更好”,而是“哪个匹配你的业务模型和代码风格”——选错会导致连接乱序、状态丢失、负载不均,甚至 onConnect/onClose 事件失效。
dispatch_mode=1(轮询)为什么在长连接场景下容易出问题
每次新请求(包括同一个连接的后续数据包)都按顺序分给下一个 Worker,看似公平,但实际破坏了连接上下文:
- 同一个 TCP 连接的
onConnect、onReceive、onClose可能被分到不同 Worker,导致onConnect和onClose被底层自动屏蔽(Swoole 明确不保证事件顺序) - 如果你在
onConnect里存了用户 session 到全局数组或 Redis,而后续onReceive被另一个 Worker 处理,就查不到上下文 - HTTP 短连接服务可以扛住,但 WebSocket 或 IM 长连接几乎不可用
- UDP 场景下反而更稳,因为无连接状态,
onPacket本身就不依赖 fd 生命周期
dispatch_mode=2(固定 FD 分配)和 mode=5(UID 分配)的本质区别
两者都靠取模实现“连接粘性”,但哈希依据完全不同,适用阶段也不同:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
-
mode=2是fd % worker_num,fd 是内核分配的整数,同一连接生命周期内不变,适合纯 TCP 长连接且不关心用户身份的场景(比如设备直连网关) -
mode=5要求你显式调用$server->bind($fd, $uid),之后按$uid % worker_num分发;如果$uid是字符串,得自己crc32($uid)再传入,否则会报错 -
mode=5的关键优势:用户登录后换设备、换 IP,只要$uid不变,所有请求仍落到同一个 Worker,方便做内存级会话缓存 - 注意:
mode=5必须在onConnect后尽早绑定,不能拖到onReceive第二个包才 bind,否则前几个包已被其他 Worker 处理
dispatch_mode=3(抢占式)和 mode=7(Stream 模式)谁更适合协程服务
别只看文档写“mode=3 更好”——mode=7 才是真正解决调度瓶颈的方案:
-
mode=3依赖 Worker 主动上报忙/闲状态,但一旦所有 Worker 都卡在 I/O(比如同步 MySQL 查询),就会退化成mode=1,请求堆积在队列里等空闲 -
mode=7(Stream 模式)从架构上反转了调度逻辑:Reactor 不再“推”请求,而是让空闲 Worker 主动accept一个 Unix Socket 连接来“拉”请求,彻底避免队列积压 -
mode=7要求 Swoole ≥ 1.9.24,且必须配合enable_coroutine => true使用;它不支持onConnect/onClose,只保留onReceive和onPacket,适合纯数据转发类服务 - 压测时对比
mode=3和mode=7,QPS 差距可能达 30% 以上,尤其在高并发 + 中等延迟 DB 查询场景下
什么时候必须用 dispatch_func 自定义分发函数
内置 7 种模式覆盖不了所有业务规则时,比如需要按 URL path 前缀、消息类型字段、或 Redis 实时权重分发:
-
dispatch_func只在SWOOLE_PROCESS模式下生效,SWOOLE_BASE模式下设了也忽略 - PHP 函数签名必须是
function my_dispatch($server, $fd, $from_id, $data): int,返回值是目标 Worker ID(0 到worker_num - 1) - 注意:如果
$data超过 8KB,PHP 层收到的只是前 8180 字节,大包分发逻辑要提前在协议层切分或加 header 标识 - 自定义函数里不要做阻塞操作(如 file_get_contents、sleep),否则整个 Reactor 线程会被卡住
最常被忽略的一点:dispatch_mode 的效果必须结合 worker_num 和实际连接数看。比如 mode=5 下只有 2 个 Worker,但 uid 范围是 1~10000,那绝大多数 uid 都会映射到同一个 Worker,根本起不到负载分散作用——不是参数设错了,是 worker_num 没配够。










