Hyperf连接池“用完不释放”的本质是连接被协程长期占用未归还,核心需排查pipeline/multi未exec、异常分支遗漏释放、MySQL异常污染Redis上下文三类泄漏点,并通过ss -s或swoole_get_local_socket_count()验证连接堆积。

Hyperf连接池用完不释放,本质是连接被长期占用而未归还,不是池子“真没了”,而是连接卡在某个协程里出不来。核心要盯住两点:谁占着不放?为什么放不了?
检查 pipeline/multi 是否漏掉 exec
这是最常见、最隐蔽的泄漏点。调用 $redis->pipeline() 或 $redis->multi() 后,若中间抛异常、提前 return、或忘记调 ->exec(),连接就会一直绑定在当前协程上下文里,直到协程结束——而协程可能活很久(比如在定时任务、WebSocket 长连接中),导致连接“假性耗尽”。
- 错误写法:没包 try/finally,异常一出就跳过清理
- 正确做法:用 try/finally 包裹,确保 exec 或 rollback 执行;或直接用 Hyperf 3.1+ 提供的 withPipeline() 封装方法,它内部已自动兜底
- 临时验证:在疑似位置加日志,打印 Context::has($redis->getContextKey()),为 true 但没 exec,基本坐实泄漏
确认异常分支是否遗漏连接释放
数据库或 Redis 操作抛异常后,如果代码没在 finally 块里显式释放连接,或者没用 try-with-resources 类机制,连接就无法回到池中。尤其在多层嵌套、手动获取连接(如 $this->connection->getConnection())的场景下极易发生。
- 避免直接操作底层 PDO 或 Co\Redis 实例,优先走框架封装的客户端
- 所有手动获取连接的地方,必须配对 finally { $conn->close() },哪怕只是 close(),也比不关强
- 检查定时任务、异步队列 handle 方法——这些地方容易忽略异常后的资源回收
排查 MySQL 异常污染 Redis 上下文
业务中混用 DB 和 Redis 时,若 MySQL 连接因 wait_timeout 断开,又没捕获异常、事务没回滚干净,协程上下文可能已损坏。后续 Redis 操作虽没报错,但底层连接实际已失效,表现为卡住或 Connection reset by peer,看起来像 Redis 连接池满了,其实是上下文错乱导致连接无法正常归还。
- 不要让 MySQL 和 Redis 共享同一个异常处理盲区
- MySQL 报 gone away 或 Connection has been closed 后,立即检查 Redis 相关逻辑是否还在继续执行
- 关键链路建议拆开事务边界,避免 DB 异常波及缓存操作
观察连接数与配置是否匹配
运行中执行 ss -s | grep ESTAB 或 swoole_get_local_socket_count(),看 ESTABLISHED 连接数是否长期接近 pool.max_connections。若持续在高位,说明连接确实在堆积,不是瞬时高峰而是持续泄漏。
- 合理设置 max_connections:按峰值 QPS × 平均耗时 × 2~3 倍余量估算(例如 100 QPS × 0.1s = 10,设 30~50)
- wait_timeout 别设太小(如 0.1),否则掩盖真实问题;推荐 2.0~5.0 秒,给排队留出合理窗口
- 开启 heartbeat => 30 和 max_idle_time => 60,双保险清理死连接和空闲连接











