根本原因是发送节奏与缓冲区容量严重不匹配:tcpconnection::$maxsendbuffersize默认1mb,而高并发下连续send大包或高频小包导致积压远超内核写出速度;必须在onbufferfull中立即停发、onbufferdrain中恢复,并避免静默丢包。

onBufferFull 被频繁触发的根本原因
不是网络卡顿或客户端接收慢的“表象问题”,而是发送节奏和缓冲区容量严重不匹配——TcpConnection::$maxSendBufferSize 默认 1MB,但业务在高并发下连续调用 $connection->send(),尤其发送大包(如 >100KB)或高频小包(如每毫秒一次),缓冲区积压速度远超内核实际写出速度,很快触顶。
常见误操作:忽略连接状态与发送节流逻辑
很多人只注册了 onBufferFull 回调,却没配合 onBufferDrain 做闭环控制,导致缓冲区“满→停→又满→再停”反复震荡。更危险的是,在 onBufferFull 中直接 return 或仅打日志,后续仍继续调用 send(),这时超出缓冲区的数据会被静默丢弃,并触发 onError(错误码 2:“send buffer full and drop package”)。
- 必须在
onBufferFull中立即停止向该$connection发送新数据(例如设标记$connection->isSending = false) - 在
onBufferDrain中恢复发送($connection->isSending = true),并检查待发队列是否非空 - 不要在
onBufferFull里尝试重发或合并数据——它只是“减速带”,不是重试机制
缓冲区实际大小 ≠ maxSendBufferSize 的陷阱
TcpConnection::$maxSendBufferSize 是应用层软限制,不是硬边界。只要缓冲区还有 1 字节空间,send($data) 就会把整块 $data 塞进去——哪怕 $data 本身就有 512KB。这意味着缓冲区瞬时占用可能远超设定值(比如 1MB 阈值 + 512KB 新包 = 1.5MB 实际占用),而 onBufferFull 只在“塞入后超限”那一刻触发一次,之后若持续调用 send(),新数据就直接被丢弃,不再触发回调。
- 验证方法:在
onBufferFull里打印$connection->getSendBufferQueueSize(),你会发现它常远大于TcpConnection::$maxSendBufferSize - 调整建议:若业务必须发大数据,可适当提高
TcpConnection::$maxSendBufferSize(如设为4 * 1024 * 1024),但必须同步加强onBufferFull的响应强度,否则只是延缓丢包,不解决根本 - 更推荐做法:业务层主动分片,单次
send()控制在 64KB 以内,并自带序号/校验,由客户端拼接
高并发下容易被忽略的复合影响
单个连接的 onBufferFull 触发,在万级连接场景下会放大成系统级压力:大量连接同时进入“满→等 drain→再满”循环,事件循环不断在这些连接间切换,CPU 负载飙升,进一步拖慢 onBufferDrain 的响应时机,形成负反馈。此时单纯调大缓冲区或加 sleep 无济于事。
- 真正有效的缓解是“降频+分流”:对非实时消息走异步队列(如 Redis List + 定时扫描),只让心跳、指令类关键数据走直连
send() - 务必检查
$connection->isClosed()和$connection->isWebSocket()再发送,避免向已断开或未升级完成的连接无效写入,这类错误写入同样会快速填满缓冲区 - Workerman 5.0 用户注意:
onBufferFull在协程环境下仍属同步回调,不能 await 任何操作,否则阻塞整个事件循环
缓冲区告警不是警告,是系统在说“你正在用卡车往单车道上塞货”——节流逻辑必须端到端闭环,且要从协议设计层就规避单次大包依赖。











