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

📎 参考文章

  • Druid Release Notes – Druid Releases (1.2.15+ 优化 MySQL URL/超时支持)
 
 
Java 8 Stream 全面解析:从一个排序示例讲清「中间操作 vs 终端操作」与底层实现“Throwable 为 null”的真相:Dubbo 自定义异常无法透传的原因与修复
Loading...