连接停顿本质是accept队列溢出,非Workerman卡住;其根本原因是内核listen() backlog队列满导致客户端SYN包被丢弃或延迟响应,表现为TCP握手耗时突增,而非直接失败。

连接停顿本质是 accept 队列溢出,不是 Workerman 本身卡住
Workerman 进程本身没挂、CPU 也不高,但新连接就是卡几秒才建立——这大概率是 Linux 内核的 listen() backlog 队列满了,客户端 SYN 包被丢弃或延迟响应。Workerman 的 Worker 实例调用 stream_socket_server() 时,默认 backlog 值极小(PHP 通常为 5 或 128),远低于高并发场景需求。
关键点在于:这不是 PHP 层逻辑问题,而是 socket 底层队列阻塞。客户端看到的现象是 TCP 握手耗时突增(SYN → SYN-ACK 延迟),表现为“连接停顿”,而非直接失败。
- 检查当前 backlog 设置:
cat /proc/sys/net/core/somaxconn(内核上限)和ss -lnt中Recv-Q持续非零即为已满 - Workerman 启动前需显式增大 backlog:
$worker->socket = 'websocket://0.0.0.0:8080?backlog=4096'; - 同步调大内核参数:
sysctl -w net.core.somaxconn=65535,并写入/etc/sysctl.conf持久化 - 若使用 Nginx 反代,还需确保
listen 8080 reuseport backlog=4096;(Nginx 1.17+ 支持)
onConnect 回调里执行同步 I/O 会放大停顿感知
即使 accept 队列没满,只要 onConnect 回调里做了任何阻塞操作(比如查 MySQL、读本地文件、调 file_get_contents),整个事件循环就会卡住,后续所有新连接必须排队等待这个回调结束——此时你看到的“停顿”其实是串行化延迟,而非网络层问题。
Workerman 的每个 Worker 进程是单线程事件循环,onConnect 和 onMessage 都在同一个 loop 中执行,没有自动异步化。
- 禁止在
onConnect中做任何同步网络请求或磁盘 I/O - 如需鉴权,改用异步 MySQL 客户端(如
workerman/mysql)或提前缓存 token 到内存 - 日志写入必须异步:用
Worker::$stdoutFile重定向,或通过amqp/redis投递到日志服务,避免error_log()阻塞 - 心跳检测也别放
onConnect,应在连接建立后由定时器触发
客户端压测工具自身成为瓶颈
用 fsockopen 或 stream_socket_client 写的简单压测脚本,在并发量上不去时,往往不是服务端扛不住,而是客户端机器的 ephemeral port 耗尽、TIME_WAIT 积压,或 DNS 解析阻塞导致连接发起变慢。
典型表现:压测 QPS 上不去,但服务端 netstat -an | grep :8080 | wc -l 显示 established 连接数远低于预期。
- 压测机执行:
sysctl -w net.ipv4.ip_local_port_range="1024 65535"扩大临时端口范围 - 降低 TIME_WAIT 重用门槛:
sysctl -w net.ipv4.tcp_tw_reuse=1 - 压测脚本中禁用 DNS 缓存,直接用 IP 连接,避免
gethostbyname阻塞 - 优先选用专业工具如
wrk或vegeta,它们自带连接池与异步调度,比 PHP 脚本更真实反映服务端能力
Linux 网络栈在短连接洪峰下会触发隐式限速
当压测以极高频率建连-发包-断连(例如每秒数千个短连接),内核的 TCP 保活、FIN-WAIT-2 回收、time_wait bucket 分配等机制可能来不及处理,导致新建连接被短暂延迟,甚至触发 tcp_abort_on_overflow 直接丢弃 SYN。
这种停顿不可预测、持续时间短(几十到几百毫秒)、且不报错,容易误判为代码问题。
- 确认是否短连接压测:长连接压测(复用连接)几乎不会出现此现象
- 临时关闭 time_wait 快速回收(仅测试环境):
sysctl -w net.ipv4.tcp_fin_timeout=30 - 真正解法是业务层避免高频短连接:客户端应复用 WebSocket 连接,或服务端启用连接池代理(如 HAProxy 的
balance leastconn) - 监控
netstat -s | grep -i "listen overflows",数值增长说明内核已开始丢包
真实压测中,“停顿”往往是多层缓冲区叠加的结果:客户端端口不够 → 发起慢;内核 backlog 满 → 接受慢;PHP 回调阻塞 → 处理慢;日志同步写 → 放大慢。每一层都只拖几毫秒,合起来就变成肉眼可见的卡顿。定位时一定要分层验证,别一上来就改 Workerman 配置。











