1. 背景与问题概述
在一个 Spring Cloud 微服务项目中,后端应用基于 K8s 部署,数据库为 MySQL 8.0.21,使用 Druid 1.2.5 作为连接池,JDBC 驱动版本为 mysql-connector-java:8.0.21。
近期在测试与生产环境中频繁出现:
1.1 现象分类
场景 A:建连后 3 秒内报错
- 建立连接成功,但 Druid 初始化时执行
isReadOnly(),3 秒内未收到响应 → 抛出异常。
- 属于读取超时问题。
场景 B:空闲 16 分钟后断链
- 连接空闲超过 16 分钟后被下游链路回收。
- 再次借出旧连接时才发现“已失效”,导致异常。
- 属于链路空闲回收问题。
2. 系统组件与版本情况
组件 | 版本 |
Spring Boot | 2.3.2.RELEASE |
Spring JDBC | 5.2.8.RELEASE |
Druid | 1.2.5 |
MySQL Driver | 8.0.21 |
MySQL Server | 8.0.21 |
K8s | 1.25+ |
配置
3. 根因分析
3.1 建连 3 秒超时问题
3.1.1 关键调用链
在 Druid 1.2.5 中,新建连接时会主动调用
Connection.isReadOnly(),导致额外一次 I/O。如果 validation-query-timeout=3s,或者 JDBC URL 配置了
socketTimeout=3000,在网络抖动或 K8s Pod 调度下很容易超时。3.1.2 问题特征
- 日志中固定显示 3,003 ms 左右;
- MySQL 端无任何错误日志;
- 多发生在Pod 弹性扩缩容或节点抢占时。
3.1.3 结论
这是 Druid 校验超时配置过小 与 SkyWalking 探针放大时延叠加导致的“误杀”。
3.2 16 分钟空闲断链问题
3.2.1 现象特征
- MySQL
wait_timeout=8h,数据库不会主动回收;
- Druid
min-evictable-idle-time-millis=300000(5 分钟),按理应提前回收;
- 但日志显示连接实际闲置 ~969,159 ms(≈16 分钟)才被动报错。
3.2.2 深入源码分析
Druid 源码片段(
com.alibaba.druid.pool.DruidDataSource#testWhileIdle):关键点:
- 当连接池中的空闲连接数 ≤ minIdle时,Druid 会“保底”维持这些连接,即便空闲时间超过
min-evictable-idle-time-millis也不会回收。
- 配置
min-idle=30时,Druid 会一直保留这 30 条连接,导致: - Druid 不会提前回收;
- 链路中间件(NAT/LB)在 15 分钟 idle 后无声断开 → 借出僵尸连接报错。
3.2.3 结论
这是 Druid minIdle 策略 + 链路中间件 idle 回收的组合问题。
4. 配置优化方案
4.1 核心思路
原则:应用保活 < 链路 idle 超时 < 连接池回收 < MySQL wait_timeout
建议值:
阶段 | 推荐值 | 原因 |
TCP 保活 | 60s | 让应用侧提前发心跳 |
Druid keepAlive | 5min | 保证 MySQL 不 idle 超时 |
Druid 回收阈值 | 10min | 在链路回收前主动淘汰旧连接 |
NAT/LB 超时 | 15min | 云厂商默认值,保持不动 |
MySQL wait_timeout | 8h | 足够长,避免误收连接 |
4.2 优化后的 Druid 配置
4.3 JDBC URL 调整
connectTimeout=5s:建连失败尽快返回;
socketTimeout=30s:避免瞬时 3s 误杀;
tcpKeepAlive=true:应用侧提前发探活包。
4.4 K8s 节点 TCP 保活优化
通过节点级配置,避免 NAT/LB 无声断开长连接。
5. 后续建议
5.1 升级 Druid 版本(推荐 1.2.18)
- 1.2.5:对 MySQL 8.0.21 兼容,但 K8s 场景保活策略不够智能;
- 1.2.15+:修复了
Communications link failure在 MySQL 8+ 的兼容性;
- 1.2.18:优化了 K8s 容器化场景,支持 MySQL JDBC 8.0.33+。
5.2 引入连接泄露检测
防止应用层忘记归还连接导致 MySQL 连接池“假死”。
5.3 监控与告警
- Druid 自带 StatFilter,结合 Prometheus + Grafana,可监控:
- 活跃连接数
- 空闲连接数
- 借出失败率
- 物理重连次数
6. 总结
问题 | 原因 | 解决方案 |
建连 3s 超时报错 | validation-query-timeout=3s 太小 + SkyWalking 延迟 | 放宽到 8s,JDBC socketTimeout 设 30s |
16 分钟空闲断链 | min-idle=30 导致旧连接长期存活,被 NAT/LB 回收 | 减少 min-idle,5min 保活,10min 回收 |
高心跳压力 | keepAlive=30s + min-idle=30 | 改为 5min,降低保活成本 |
K8s NAT 空闲回收 | 默认 900s | 提前应用侧 5min 发心跳,TCP keepalive=60s |
版本兼容问题 | Druid 1.2.5 老旧 | 升级 Druid ≥1.2.15,建议 1.2.1 |
📎 参考文章
- Spring Boot 官方文档 – Data Access: Connection Pooling
- MySQL 官方文档 – Server System Variables: wait_timeout (默认值 28800 秒 = 8 小时)
- AWS 官方文档 – Network Load Balancers: Connection Timeouts (默认 TCP idle timeout = 350s)
- Azure 官方文档 – Azure Load Balancer TCP idle timeout (默认 4 分钟,可调)
- Druid GitHub 源码 – DruidDataSource shrink() 逻辑 (minIdle/回收策略)
- Druid Release Notes – Druid Releases (1.2.15+ 优化 MySQL URL/超时支持)
- 作者:Yibin
- 链接:https://yibin.dev/article/25660b50-99a4-8032-82f5-e67d88bfe5a8
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章








