onmessage里直接计数不行,因workerman多进程内存隔离,$this->count++仅当前进程有效,用户连接切换进程即重置;文件计数高并发下丢数据、锁冲突;必须用redis的incr+expire原子操作(推荐lua脚本),键名建议"rate:ws:{$connection->id}:1s",redis连接须在onworkerstart初始化复用。

onMessage里直接计数为什么不行
因为Workerman是多进程模型,每个worker进程内存隔离。$this->count++只在当前进程有效,用户换一个TCP连接可能落到别的进程上,计数就重置了——压测时看起来限流没生效,其实是“每人一把算盘”。
更隐蔽的问题是:用file_put_contents写文件计数,高并发下会丢数据、锁冲突、IO拖垮性能,线上绝对不能这么干。
必须用Redis做共享计数,且要用原子操作
每秒限流本质是“窗口内计数+自动过期”,只有Redis的INCR配合EXPIRE(或Lua脚本)能保证原子性。键名建议用"rate:ws:{$connection->id}:1s",按连接ID区分,避免IP级误伤(比如NAT后多个用户共用一个IP)。
关键点:
• 不要分开调$redis->incr()和$redis->expire(),网络延迟可能导致key没过期就堆积
• 推荐用Lua脚本一次性完成递增+设置过期,示例如下:
local count = redis.call('INCR', KEYS[1])
if count == 1 then
redis.call('EXPIRE', KEYS[1], 1)
end
return count
调用时传入["rate:ws:{$connection->id}:1s"]即可。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
Redis连接不能在onMessage里new
每秒成百上千次消息进来,如果每次onMessage都new Redis()或connect(),Redis连接数会瞬间打满,TCP握手延迟也会飙升。
正确做法:
• 启动时在Worker::$onWorkerStart里初始化一个全局$redis实例(用phpredis扩展,非Predis)
• 如果并发极高(比如10万连接),得自己封装简单连接池,或改用Swoole + Redis协程客户端(但这就不是纯Workerman方案了)
• 别图省事用static $redis延迟初始化——首次调用仍会阻塞,且无法复用连接
注意连接ID不稳定,别直接当唯一标识用
$connection->id在连接断开重连后会变,但同一浏览器多次WebSocket重连往往属于逻辑上的“同一个客户端”。如果业务要求更稳定的限流粒度:
• 在onConnect里解析token或cookie,提取用户ID或设备指纹,拼进rate key,如"rate:uid:{$uid}:1s"
• 一定要校验token有效性,否则攻击者伪造ID就能绕过
• 不要依赖$_SERVER['HTTP_SEC_WEBSOCKET_KEY'],它每次握手都不同,没法聚类










