资源耗尽必须先区分类型:文件描述符(Too many open files)、内存(OOMKilled)、连接数(getClientCount飙升)或task进程异常;再用ulimit -n、/proc/pid/limits、ss -s三命令5秒定位。

看日志前先确认资源类型:是文件描述符、内存,还是连接数?
资源耗尽不是模糊概念——Swoole崩溃或连接拒绝,背后一定是某类系统资源触顶。不区分类型就瞎查日志,90%会绕弯路。
常见现象对应资源类型:
-
Too many open files→ 文件描述符(ulimit -n被突破) - 进程被
OOMKilled或dmesg | grep -i "killed process"有记录 → 内存耗尽 -
getClientCount()持续飙升且不下降 → 连接未正确关闭,或心跳/超时逻辑失效 - task worker 频繁重启 +
max_request日志刷屏 → 内存泄漏叠加 worker 自保护退出
实操建议:先跑这三条命令,5秒内定位方向:
ulimit -n cat /proc/$(pgrep -f "swoole")/limits | grep "Max open files" ss -s | grep "total:"
文件描述符不够用?别只改 ulimit,还要检查 Swoole 的连接生命周期
很多人改了 ulimit -n 65535 就以为万事大吉,结果一压测还是 accept(): Unable to accept connection: Too many open files —— 因为连接没关,不是没开。
Swoole 不像 PHP-FPM 那样自动回收资源,每个 onConnect 建立的连接,必须显式 close() 或依赖超时机制释放。
- 检查是否漏写
$server->close($fd),尤其在onClose或异常分支里 - 确认
set(['heartbeat_idle_time' => 60, 'heartbeat_check_interval' => 25])已启用,否则空闲连接永远挂着 - 避免在协程里用
sleep()等待客户端响应——它不释放 fd,只挂起协程 - 用
swoole_server::stats()查connection_count和close_connection_count差值,差太多说明连接泄漏
内存持续上涨?重点盯死 defer、全局变量和 MySQL/Redis 连接复用
协程内创建的资源,不会随协程结束自动销毁。PHP 的引用计数在协程上下文里容易失灵,尤其是对象闭包持有 $this 或静态属性时。
- 数据库连接必须用
defer关闭:defer(function () use ($mysql) { $mysql->close(); }); - 禁止在
onWorkerStart里 new 大对象并赋值给全局变量(如$GLOBALS['cache'] = new Redis();),worker 进程会一直持有着 - MySQL 协程客户端不要每次查询都
new SwooleCoroutineMySQL(),应复用连接或用连接池 - 用
memory_get_usage(true)在关键协程入口/出口打点,对比增长量;超过 2MB 就该怀疑有大数组或缓存堆积
连接池满、task 进程卡死?本质是阻塞点没切到协程
连接池报 pool is full 或 task worker 长时间无响应,表面是池子小,实际多因同步调用卡住了整个协程调度器。
- 所有 IO 操作必须用协程版:用
SwooleCoroutineMySQL,别用mysqli;用SwooleCoroutineRedis,别用Redis扩展 - 第三方 SDK(如 HTTP 客户端)若非协程安全,必须扔进
taskworker处理,不能在 worker 协程里直接file_get_contents - 检查
task_worker_num是否过小:压测时看task_queue_num统计值,持续 > 0 就说明 task 队列积压 - 避免在
onTask里再起协程做耗时操作——task 进程不跑 event loop,go()无效,会退化成同步执行
最常被忽略的一点:Swoole 的“资源”从来不是孤立的。一个连接没关,会占 fd + 内存 + 协程栈;一次同步调用卡住,会拖垮整个 worker 的并发能力。查问题时,永远从「哪个资源最先告警」倒推,而不是从代码行数猜。










