连接泄露的典型表现是“慢”和“涨”:Worker内存持续上涨、SHOW PROCESSLIST出现大量Sleep连接、hyperf_db_pool_used_connections长期接近max_connections且idle极低,重启后几小时复现。

连接泄露的典型表现是什么
不是报错,而是“慢”和“涨”:Worker进程内存持续上涨、SHOW PROCESSLIST里出现大量Sleep状态连接、监控显示hyperf_db_pool_used_connections长期接近max_connections且idle极低。更隐蔽的是,重启后一切正常,几小时后又复现——这基本可锁定为协程未归还连接。
为什么Swoole Tracker比日志更有效
日志只能告诉你“哪里用了DB”,但无法回答“用完没还”。swoole_tracker能直接挂钩malloc/free调用,捕获连接资源分配与释放不匹配的瞬间。它不依赖代码埋点,也不受协程切换干扰,只要连接对象(如PDO实例)没被GC回收,就能在tracker.report()中暴露出来。
- 必须开启两个开关:
tracker.enable_malloc_hook=1+tracker.enable_memcheck=1(写进php.ini或ini_set()) - 只对启用了
enable_coroutine = On的 Swoole 环境生效;Hyperf v3.0+ 默认启用,v2.2+ 需确认 - 不要在全量请求上常开——它有性能损耗;建议仅在复现问题的 Worker 进程上临时启用(通过
kill -USR1 {pid}触发快照)
怎么从Tracker报告里定位泄漏点
执行tracker.report()后,输出类似:
Leak #1: malloc(128) at /path/to/hyperf/src/db/src/Connection.php:142 Leak #2: malloc(96) at /path/to/app/Service/UserService.php:87
关键看两行:malloc位置说明资源在哪创建,Leak #N编号对应未free的堆块。重点关注:
- 所有指向
Connection.php、PDO.php或Pool.php的malloc调用——它们大概率是连接对象本身 - 指向业务文件(如
UserService.php)的调用——说明你在该处手动new PDO或绕过了Db::,且没显式close() - 若泄漏点集中在
Context::set()或Coroutine::create()附近,大概率是协程上下文里存了连接句柄但没配defer清理
配合Hyperf原生机制做交叉验证
Tracker发现泄漏点后,别急着改代码。先确认是否触发了Hyperf的自动回收兜底逻辑:
- v2.2+ 版本中,协程退出时会尝试自动回滚未提交事务并归还连接——但前提是没发生
exit、die或致命错误中断流程 - 检查
config/autoload/databases.php中pool.wait_timeout是否设得太小(如1.0),导致连接还没用完就被池强制回收,引发下游重连风暴 - 用
swoole_get_local_socket_count()对比lsof -p {pid} | grep mysql,如果前者远小于后者,说明存在已关闭但fd未释放的“僵尸连接”,根源在Swoole层或PDO底层驱动
真正难缠的泄漏往往藏在自定义进程、定时任务或中间件里——那些地方容易忽略协程生命周期,手动创建连接却忘了归还。Tracker能揪出它,但修复得靠你盯紧try-finally和defer的每一处落点。


















