单节点 WebSocket 推送可用 $server->connections 遍历发送,多节点下该属性仅返回当前 Worker 进程内连接,不跨进程更不跨机器;必须依赖 Redis Pub/Sub、Stream 或 Kafka 等外部消息通道实现广播,并手动处理连接映射、健康检查与心跳清理。

单节点 WebSocket 推送能用 $server->connections 遍历发消息,多节点(跨机器)必须依赖外部通信机制,没有内置方案。
为什么 $server->connections 在多节点下完全失效
这个属性只返回当前 Worker 进程内建立的连接句柄,是纯内存结构,不跨进程、更不跨机器。当你部署多个 Swoole 实例(比如用 Nginx 做负载均衡,后端挂 3 台服务器),每个实例的 $server->connections 互不可见。
常见错误现象:
- 用户 A 连在 node1,你从 node2 调用
foreach ($server->connections as $fd) { $server->push($fd, $msg); }—— 没有任何效果,也不报错 - 广播时只有本机在线用户收到,其他节点用户“静默丢失”
多节点推送必须引入中心化消息通道
核心思路:所有节点不直接操作对方连接,而是把“要推什么消息给谁”这件事,交给一个共享中间件统一调度和分发。
可选方案与关键差异:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
-
Redis Pub/Sub:轻量、低延迟,但不保证消息不丢(断连期间发布的消息会丢失);适合实时性高、允许少量丢失的场景(如聊天室状态更新) -
Redis Stream:支持消费者组、消息持久、可回溯;适合需可靠投递的业务(如订单状态变更通知);注意要用XREADGROUP+ ACK 机制 -
Kafka / Pulsar:吞吐高、分区明确、支持多订阅;适合大规模、高一致性要求系统;但运维成本明显上升 - 避免用
MySQL轮询:性能差、锁竞争严重,仅适合极低频兜底场景
无论选哪种,每个 Swoole 节点都需启动一个独立协程监听消息通道,并根据消息内容决定是否向本地 $fd 推送——这个“判断逻辑”必须自己写,Swoole 不提供自动路由。
onMessage 和 onOpen 里存连接信息的坑
很多开发者习惯在 onOpen 里把 $fd 和用户 ID 存进 Redis,比如:SET user:1001 fd:123。这本身没问题,但容易忽略两个关键点:
- 没做连接生命周期绑定:用户断开时,
onClose必须同步删掉 Redis 中的映射,否则后续推送会失败或打到已关闭的$fd,触发Swoole\Server::push(): fd[123] is not a valid connection - 没处理
$fd重用问题:Swoole 的文件描述符会复用,如果只存$fd不存$server->getClientInfo($fd)中的remote_port或时间戳,可能把消息错推给新连接的用户 - 跨节点时,不能只存
$fd,必须存完整标识,例如:node2:fd:123,否则其他节点无法识别该连接归属
真正落地时最常被跳过的一步:连接健康检查
多节点环境下,网络抖动、节点重启、连接假死都会导致本地 $fd 看似存在实则无效。光靠 onClose 不够。
建议在监听到推送指令后、执行 $server->push() 前,加一层校验:
if (!$server->exist($fd) || !$server->getClientInfo($fd)) {
// 从 Redis 清除该映射,跳过推送
return;
}
更稳妥的做法是配合心跳:前端每 30 秒发一次 ping,服务端用 tick 定时器扫描超时连接并主动 close,再清理 Redis 数据。这个逻辑不会自动发生,必须手写。

















