不能用server->push()做群广播,因其内部遍历所有session对象触发send(),导致高并发下内存暴涨、CPU阻塞、无分片控制;正确做法是启用webman/push的Redis Pub/Sub架构,用Push::trigger()触发广播,并对超大群引入读扩散层。

webman/push 插件是当前最轻量、延迟最低的开箱即用方案,但直接调用 $session->send() 或 server->push() 会在线上高并发场景下迅速卡死 worker 进程——这不是配置问题,是设计误用。
为什么不能用 server->push() 做群广播
单群成员超 5000 时,server->push() 内部仍会遍历所有 session 对象触发 send(),导致:
- 内存暴涨:每个
Session对象序列化+拷贝一次,百万连接即 GB 级临时内存 - CPU 阻塞:write 系统调用在慢客户端上阻塞整个 worker,其他连接全部排队
- 无分片控制:无法限制单次推送目标数,无法熔断或降级
官方文档里没明说,但生产环境实测:群聊 2 万人时,server->push() 平均延迟从 12ms 跃升至 800ms+,且抖动剧烈。
webman/push 的正确接入姿势
它本质是「客户端订阅 + 服务端 Pub/Sub」架构,所有广播由 Redis 中转,Webman 进程只做事件中转,不触达具体连接。
- 必须启用 Redis:配置
config/plugin/webman/push/redis.php,确保pubsub通道可用 - 频道命名要收敛:用
user-{uid}、group-{gid}等固定前缀,避免通配符订阅 - 服务端推送用
Push::trigger(),而非server->push():Push::trigger('group-123', 'message', ['content' => 'hello']); - 私有频道需配
auth接口,返回{'auth': 'xxx', 'channel_data': '...'},否则订阅失败
超大群(>10 万)必须加读扩散层
即使走 webman/push,Redis Pub/Sub 在单 channel 百万订阅者时也会出现消息积压和丢帧。此时必须引入「读扩散」:
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 消息写入
Redis Streams(如stream:group:123),每条消息带唯一 ID - 客户端拉取用
XREAD GROUP,按游标消费,支持断点续传 - 在线状态服务(独立进程)负责把新消息 ID 推给在线用户,用户再主动拉取完整内容
- Webman 只负责将消息写入 Stream,不参与广播逻辑
这个环节容易被跳过——很多人以为装了 webman/push 就万事大吉,结果上线后发现群聊延迟越来越高,根本原因就是没拆离「写」和「推」。
自定义 WebSocket 进程的延迟陷阱
有人为“更可控”而弃用 webman/push,改写自定义 process/Websocket.php,但极易踩坑:
- Timer::add() 不可直接在
onMessage里调用:回调函数闭包持有$connection,导致连接无法释放,内存泄漏 - 未设
max_connection限流:单个进程承载过多连接后,epoll 效率断崖下降 - 未启用
open_websocket_protocol:Workerman 默认关闭 WebSocket 协议解析,需显式开启 - 心跳检测未配
ping_interval和ping_timeout:大量假在线连接堆积,广播目标失真
除非你明确需要定制协议头或二进制帧处理,否则不要碰自定义 WebSocket 进程——webman/push 已覆盖 95% 场景,且经过 20 万+ 日活项目验证。

















