长连接需心跳检测、超时清理、fd生命周期管理三者联动;websocket须显式处理ping/pong;nginx keepalive_timeout需大于swoole心跳间隔;ulimit、buffer_output_size、max_coroutine等参数须合理调优;禁用opcache.enable_cli;onclose中避免闭包捕获大对象;连接上下文用swoole\table存储;启用max_request兜底内存释放。

长连接不是靠 tcp_keepalive 一开就稳的,它必须配合心跳检测、超时清理、FD 生命周期管理三者联动,否则压测时连接数上不去、内存涨得快、close 后 fd 还残留。
WebSocket 心跳超时设置不匹配导致连接假死
客户端发 ping,服务端没回 pong,或间隔时间超出 Nginx/ALB 的 idle timeout,连接会被中间件静默断开,但 Swoole 进程里 fd 还在,$server->connections 持续增长。
-
onMessage中必须显式处理ping/pong类型帧,不能只依赖底层 TCP keepalive - 服务端要主动发
heartbeat:用Timer::tick()每 25s 扫描所有连接,对 45s 无消息的 fd 调用$server->disconnect($fd) - Nginx upstream 必须配
keepalive_timeout 60s,且大于 Swoole 的心跳间隔,否则还没等你 pong,Nginx 先 close - 别信
set(['open_tcp_keepalive' => true])—— 它只管内核层保活,不解决应用层空闲断连
压测时连接数卡在 3000–5000 上不去
不是 CPU 或带宽瓶颈,而是默认配置下协程栈、文件描述符、连接缓冲区三者之一先扛不住。尤其在流式 token 场景下,每个连接长期占用一个协程 + 一段输出 buffer。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 检查
ulimit -n,Swoole 默认单进程最多打开 1024 个 fd,worker_num * 1024就是理论上限,压测前必须调高(如ulimit -n 65535) -
buffer_output_size建议设为4M:小 buffer 导致频繁 syscall,吞吐上不去;太大则内存浪费,万连接下多占几百 MB -
max_coroutine设为3000左右较稳:高于此值协程调度器易抖动,火焰图会看到大量runtime.gopark - 禁用
opcache.enable_cli=1:CLI 模式下 OPcache 会缓存脚本字节码并长期驻留,压测中反复加载类会导致内存缓慢爬升
onClose 回调里 unset 失效,连接关闭后内存不释放
常见于闭包捕获了 $server 或全局数组引用,导致 PHP GC 无法回收。现象是压测跑 10 分钟后 RSS 内存持续上涨,ps aux 看 worker 进程越跑越大。
- 绝不能在
go(function () use ($server, $fd) { ... })里直接 push 数据 ——$server是大对象,闭包持有它等于锁死整个 Worker 内存空间 - 连接级上下文改用
Swoole\Table存储:$table->set($fd, ['messages' => [], 'last_active' => time()]),关闭时$table->del($fd) - 务必启用
max_request(建议1000):强制 Worker 进程定期重启,是兜底的内存释放手段,比靠 GC 更可靠 - 检查是否误用了
static变量缓存用户数据 —— Swoole Worker 进程常驻,static $cache = []会越积越多
真正难的不是写通心跳或调参,而是在协程里做资源释放时,得时刻意识到:你写的不是一次请求的代码,而是可能运行 24 小时、承载 10000 个 fd 的长期服务逻辑。任何一处闭包、静态变量、未 close 的 client,都会在压测第 8 分钟突然暴露出来。










