单节点可用$server->connections遍历推送,多节点必须依赖Redis Pub/Sub、Stream或Kafka等外部消息通道实现广播,并手动处理连接映射、心跳清理与fd存在性校验。

单节点 Swoole WebSocket 服务用 $server->connections 遍历推送很直接,但一上集群,这个方式就完全失效——它只返回当前进程内的连接句柄,跨进程不感知,跨机器更无从谈起。真正可行的方案,是让所有节点通过共享中间件“知道消息该推给谁”,再由各自节点负责本地推送。
为什么不能靠负载均衡或 session 共享?
nginx 的 ip_hash 或 sticky session 看似能固定用户到某台机器,但 WebSocket 场景下存在硬伤:
- 服务端需主动向客户端推送(如订单通知、客服响应),而触发推送的业务请求可能来自任意节点,无法保证和连接在同一个实例上
- 用户多端登录时(PC + App + 小程序),同一用户会连多个节点,必须全量触达,而非绑定单一节点
- WebSocket Session 是 TCP 连接句柄(fd),无法序列化存 Redis,所谓“共享 session”在技术上不可行
必须引入外部消息通道
核心思路:所有节点不互相调用,而是统一把“要发什么、发给谁”广播出去,每个节点监听后自行判断——是否持有目标用户的 fd,有则本地 $server->push(),没有则忽略。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- Redis Pub/Sub:最轻量,适合聊天室状态更新、在线人数广播等允许少量丢失的场景;断连期间发布的消息会丢
- Redis Stream:支持消费者组、ACK 确认、消息回溯,适合订单通知、工单分配等要求可靠投递的业务
- Kafka / Pulsar:吞吐高、分区明确、天然支持多订阅,适合万级并发+多业务系统共用消息总线,但运维复杂度明显上升
关键实现细节
光有消息通道还不够,几个容易踩坑的点必须手动处理:
-
连接映射要带生命周期:onOpen 时把
fd → user_id写入 Redis(如HSET socket:map $fd $uid),但 onClose 时必须同步删除,否则变成僵尸连接 -
心跳与清理要闭环:Swoole 自带
heartbeat_idle_time,但需配合定时任务扫描 Redis 中超时未更新的 fd,并主动 close,避免内存泄漏 -
本地推送前做二次校验:收到消息后,先查
$server->exist($fd),再调$server->push(),防止 fd 已被回收却仍尝试推送导致崩溃
代码结构建议
每个 Swoole 节点启动时,应并行运行三个协程:
- 一个监听 Redis 消息通道(Pub/Sub 或 Stream),解析目标 user_id 或 group_id
- 一个从本地
$server->connections构建实时 fd 映射表(可缓存 1–2 秒,减少高频查询) - 一个执行实际推送:匹配 user_id → fd → exist → push,失败记录日志,不阻塞主流程

















