真正拉开差距的是在连接断开、协程调度、心跳匹配等关键节点提前设防、精准清理:onClose需unset上下文并归还资源;禁用sleep()和同步IO,统一用co::sleep()及协程客户端;WebSocket需双向心跳匹配;协程池须设max_connections并监控coroutine_num。

真正拉开差距的不是“会启动一个 Swoole HTTP 服务”,而是你能否在连接断开、协程调度、心跳匹配这些关键节点上提前设防、精准清理。
onClose 里不 unset 连接上下文,内存就只增不减
很多人在 onClose 回调里只写 echo "fd {$fd} closed",或者干脆空着。但 Swoole Worker 进程常驻内存,$server->connections[$fd] 存的用户 session、Co\Channel、Redis 句柄、临时数组,只要没显式销毁,就会一直占着堆内存。
实操建议:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 必须在
onClose中执行unset($server->connections[$fd]) - 如果用了 Redis 连接池,要确保调用
$pool->put($redis)归还连接 - 避免在
onClose里再起新协程或发 HTTP 请求——连接已断,容易触发超时堆积或异常未捕获
协程里写 sleep() 或 file_get_contents(),等于卡死整个 Worker
常见错误现象:sleep(1) 写成 co::sleep(1) 的反向操作,或直接在协程函数里调用 file_get_contents('http://api.com'),结果整个 Worker 进程阻塞,后续所有请求排队等待。
原因很直接:Swoole 协程调度依赖可挂起的 IO,而 sleep() 和原生同步网络调用是系统级阻塞,协程无法让出控制权。
实操建议:
- 所有延时统一用
co::sleep() - 所有网络请求走
Swoole\Coroutine\Http\Client或Co\Redis,别用 Guzzle 默认实例 - 本地文件读写非用不可时,改用
Co\File::read(),或丢进Co\Process隔离
WebSocket 心跳两端不匹配,客户端隔几十秒就重连
典型表现:$server->stats() 显示 close_count 持续上涨,connection_num 剧烈波动,但前端日志看不出明显报错。
根本问题在于服务端靠 heartbeat_idle_time 和 heartbeat_check_interval 联合判断是否踢人,而很多同学只调大了前者,忘了后者也要同步调整;更常见的是客户端压根没发 ping。
实操建议:
- 服务端至少配两处:
'heartbeat_idle_time' => 60(60 秒无消息断连)、'heartbeat_check_interval' => 25(每 25 秒扫一次) - 客户端必须主动发文本帧
"ping"或空字节,不能只等服务端 push - 别指望
onMessage里的业务逻辑刷新 last_time——Swoole 内部心跳计时器独立运行,不受业务延迟影响
协程池没设限,流量高峰直接雪崩
协程池(如数据库、HTTP 客户端池)一旦不设最大数量,高并发下会无限创建协程,迅速耗尽内存或触发 OOM Kill。尤其在 Base 模式下,没有进程隔离,一个池失控会影响全部请求。
实操建议:
- 所有协程池初始化时必须指定
max_connections,例如new Co\Pool(100) - 池获取失败时不要死等,加超时和 fallback 逻辑,比如降级为直连或返回缓存
- 配合
Swoole\Server::stats()监控coroutine_num和worker_request_count,设置告警阈值
最易被忽略的一点:Swoole 6.1.1 起默认不再自动 Hook 所有 IO 函数,Swoole\Runtime::enableCoroutine() 必须显式调用,且需放在 go 或 Coroutine\run 之前——漏掉这行,协程里照样阻塞。

















