可通过事件拦截+滑动时间窗口计数+连接级状态管理实现轻量可靠限流:为每个fd维护独立计数器,用swoole\table存储时间戳与次数,超阈值则关闭连接或返回429错误帧,并配合客户端退避、消息去重、长度限制及onbufferfull监听防缓冲区溢出。

客户端高频发送消息,服务端不做限速,容易压垮连接、触发队列溢出、甚至导致 worker 崩溃或消息静默丢失。Swoole 本身不内置“每秒最多收 N 条”的限流开关,但可通过事件拦截 + 时间窗口计数 + 连接级状态管理,实现轻量、可靠、可配置的限流。
按连接维度做滑动时间窗口计数
每个 WebSocket 连接(fd)维护一个独立的请求计数器,避免全局锁竞争。推荐用 滑动时间窗口(非固定周期桶),更平滑抗突发。
- 在
onMessage回调中,先获取当前 fd 对应的计数结构(可用 Swoole\Table 或 Redis 存储) - 记录每条消息到达的毫秒时间戳,只保留最近 1 秒内的记录(例如用数组+二分查找或 SortedSet)
- 若当前窗口内已超阈值(如 20 条/秒),直接
$server->close($fd)或返回限流错误帧(如{"code":429,"msg":"too many requests"}) - 注意:不要用
sleep()或协程co::sleep()阻塞,会拖慢整个 worker
利用 Swoole\Table 实现无锁高频计数
若并发连接在万级以内,Swoole\Table 是比 Redis 更低延迟的选择,支持原子操作且内存常驻。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- 初始化时创建 Table:
$limitTable = new \Swoole\Table(65536); $limitTable->column('last_time', \Swoole\Table::TYPE_INT, 8); $limitTable->column('count', \Swoole\Table::TYPE_INT, 4); $limitTable->create(); - 每次 onMessage 中:
$now = microtime(true) * 1000;,读取并更新对应 fd 行;若距上次更新 >1000ms,则重置 count=1;否则 count++;再判断是否超限 - 务必设置合理的
worker_num和max_request,防止 Table 被撑爆或长连接泄漏
配合背压与客户端反馈机制
单纯服务端限流可能让客户端“盲目重试”,需双向协同。
- 服务端限流时,主动推送一条带
"type":"throttle"的提示帧,引导客户端退避重试(如指数退避) - 对关键业务消息(如支付确认、订单提交),可要求客户端附带 sequence ID,服务端去重 + 限流双校验
- 若使用 EasySwoole 框架,可在
WebSocketEvent::onMessage前插入中间件,统一做限流判定,逻辑更清晰
避免常见陷阱
限流不是加个计数器就完事,几个易忽略但致命的点:
-
不校验 readyState:客户端断连后仍发包,fd 可能复用,需结合
onClose清理 Table 中对应行 -
忽略大消息体开销:单条消息 500KB,10 条/秒就占 5MB/s 内存,限流要同时限制消息长度(如
strlen($frame->data) > 102400则拒收) -
没设 write_limit:Swoole 默认
write_limit是 2MB,若客户端疯狂发、服务端又卡在处理逻辑,缓冲区满后 send() 会失败——必须监听onBufferFull并主动 close










