深夜告警:连接池活跃连接 100%,等待队列排到几千,业务请求全线超时。 这篇文章不讲「调大 maxActive」那种把问题往后拖的做法,而是完整聊一遍连接池耗尽的治理体系。
一、先分清故障类型,再动手
很多团队遇到连接池打满,第一反应是重启服务、调大最大连接数、加告警。这三板斧能应急,但治不了根 ------ 半小时后连接再次被打满,就只能一直救火。
连接池耗尽不是一个问题,而是四类问题的共同表象,动手前必须先区分:
表格
| 故障类型 | 典型特征 | 判断依据 |
|---|---|---|
| 连接泄露 | 活跃连接只增不减,缓慢爬升 | 拿了连接没在 finally 里释放,连接永久不归还 |
| 事务占坑 | 连接持有时间异常长 | 事务内嵌套 RPC 调用、事务迟迟不提交,连接被长期占住 |
| 慢 SQL | 少量 SQL 拖垮整个池 | 单条 SQL 执行几分钟,连接死死占住不释放 |
| 流量突增 | 连接数随流量同步暴涨 | 突发流量冲垮,属于「合理占满」而非故障 |
区分方法很直接:看连接持有时间的分布。 泄露是「不还」,持有时间趋向无穷大;慢 SQL 是「还得慢」,集中在少数长尾请求上;流量突增是整体水位抬升但单次持有时间正常。
看错类型,动作全是无用功 ------ 泄露问题你去扩容,只会让数据库端连接数跟着暴涨,反过来打满数据库最大连接数、拖垮整个库,形成恶性循环。
二、应急止损:不重启服务也能止血
核心链路不能停,重启又不能随时做,应急阶段有四招,全部不用改代码:
- 数据库端批量杀连接 让 DBA 批量 kill 掉空闲超时和执行超时的慢连接,注意筛选条件,别把正常活跃连接一起杀。应用池侧仍会把这些连接当活的,杀完要确保连接池开启借出校验(
testOnBorrow或 keepalive),否则业务拿到死连接会报Communications link failure。 - 动态调小连接池最大等待时长 从等 60 秒改成 500ms 快速失败。宁可让部分请求立刻报错,也不要让线程堆积拖垮服务线程池,演变成雪崩。
- 非核心接口熔断降级 报表、查询类接口直接降级返回兜底数据,把有限连接留给支付、下单等核心链路。
- 服务入口兜底熔断 连接池完全打满且无法快速恢复时,入口直接拒绝新请求,防止堆积拖垮线程池。
操作顺序:先杀库端连接释放存量 → 再收紧等待时间控制增量 → 然后降级保护核心链路 → 最后才考虑入口熔断。
三、根因定位:让代码自己「招供」
止损之后必须回答:到底是哪段代码导致的?人工翻日志效率太低,要靠机制:
- 开启连接池泄露检测 HikariCP 的
leakDetectionThreshold(最小 2000ms,支持经 JMX 动态开启)和 Druid 的logAbandoned,都能在连接借出超阈值未归还时,打印获取连接时的完整调用堆栈 ------ 哪行代码拿了没还,一目了然。两者默认关闭,要提前配好。 - 看连接池监控看板 活跃连接数、等待队列长度、平均连接持有时间三指标对照:活跃数只涨不跌是泄露;等待队列长而持有时间正常是流量问题。
- 配合慢 SQL 日志和事务监控 拉出执行时间 TOP 的 SQL 和长事务列表,交叉定位慢 SQL 和「事务不提交」的源头。
重要提醒: 泄露不能只靠人工排查,还要配自动化兜底,但要分清能力边界:只有 Druid 的
removeAbandoned会强制回收超时未归还的连接,HikariCP 只告警不回收。 而removeAbandoned生产环境要慎用 ------Druid 源码本身警示 not use in production ,强制回收可能误杀执行中的长事务,持有线程报Connection has been closed,造成业务数据错乱。 要开的话,阈值须大于业务最长合理耗时(建议 10 分钟以上),且只作应急期临时安全网,根治仍要回到代码规范。
四、架构根治:从编码规范上杜绝复发
应急和定位都是「术」,根治要落在架构和规范上:
- 强制约束事务边界 严禁在数据库事务内调用 RPC 或第三方接口 ------ 这是事务占坑的头号来源。事务要短小,网络调用一律移出事务。
- 统一封装数据访问层 连接的获取和释放由框架统一管理,业务代码不允许裸操作连接,从规范层面杜绝泄露。
- 建立常态化 SQL 审核机制 上线前过索引校验和执行计划检查,从源头减少慢 SQL;同时给 SQL 设置最大执行超时(应用侧 statement timeout 最通用,MySQL 的
max_execution_time只对 SELECT 生效),超时自动中断,避免一条慢 SQL 拖垮整个池。核心接口再配 SQL 异常熔断 ------ 超时比例过高自动降级。 - 流量侧分层限流 入口按业务优先级限流,非核心业务降级返回兜底数据;数据库端设置应用连接数上限,避免无限制扩容把库打满。分库分表场景下多个连接池要分池监控,排查优先级:核心链路的库 > 高流量业务库 > 边缘库。
写在最后
连接池问题看似基础,恰恰是「会调参数」和「能设计高可用体系」的分水岭。 真正的考验不是那一个 maxActive,而是三点: 能不能区分泄露、慢 SQL、流量突增等故障场景; 知不知道不重启服务的应急止损方案; 有没有连接池治理、SQL 管控、熔断降级的落地经验。
普通做法是出事故重启救火;成熟的体系是应急止损、根因定位、架构优化形成闭环。 这套思路不止适用于连接池,线程池、缓存、消息队列的耗尽问题,治理框架都是相通的。