一次 Druid 慢查询超时问题的源码排查

现象

生产环境遇到以下报错:

vbscript 复制代码
Error querying database.  Cause: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
The last packet successfully received from the server was 18,860 milliseconds ago. 
The last packet sent successfully to the server was 18,868 milliseconds ago.

询问 AI 后,得到的分析是"链接长时间未使用,被回收了"。

复现

根据报错信息,猜测可能是超过 10 秒的慢 SQL 就会报错。 为了验证,准备了一个测试接口,只执行一个耗时 30 秒的查询:

sql 复制代码
SELECT SLEEP(30)

测试结果: 每次执行到 10 秒左右,果然报错,复现成功!

初步解决

咨询 AI 后,尝试通过设置 socketTimeout 来解决。在 JDBC 连接串中增加 socketTimeout=60000(60 秒)配置后,问题确实消失了。 但问题是:为什么? 追问 AI 具体原因,得到的回答却含糊不清,解释不清根本原因。于是决定自己翻源码。

深入源码

首先查看 Druid 源码中的 socketTimeout 配置,发现默认值是 0,理论上应该没有超时限制:

java 复制代码
protected volatile int socketTimeout; // milliSeconds

于是猜测是在启动时通过 setSocketTimeout 进行了赋值。在所有调用处打断点 debug 启动,发现并没有走进这些设置方法。 然而,进入 com.alibaba.druid.pool.DruidAbstractDataSource#createPhysicalConnection() 创建连接时,发现 socketTimeout 的值已经是 10000(10秒)了!

既然不是运行时赋值的,那只能是类加载时成员变量的默认值了。

版本考古

到底是哪个版本开始有了这个默认 10 秒的配置呢?于是开始了版本追溯(当前使用的是1.2.20)。

1. 从 1.2.20 追溯

com.alibaba.druid.pool.DruidDataSource.javainit() 方法中,找到了以下代码,翻看官方源码发现是从 1.2.15 增加的默认值设置逻辑:

java 复制代码
if (socketTimeout == 0) {
    socketTimeout = DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS;
}

至此,想当然地认为:1.2.14 版本应该没有这个限制。但测试 1.2.14 时,发现依然会 10 秒超时,debug 到createPhysicalConnection()中发现值仍然是 10 秒。

2. 从 1.2.14 继续往前追溯

继续查找,发现 1.2.14的DruidAbstractDataSource 类中,直接在成员变量声明时就设置了默认值:

java 复制代码
protected volatile int socketTimeout = DEFAULT_TIME_SOCKET_TIMEOUT_MILLIS; // milliSeconds

PS: 1.2.13之后的源码仓库都有个core层,1.2.12之前的源码仓库是没有的,当时还以为1.2.12没有这个类呢druid/core/src/main/java/com/alibaba/druid/pool/DruidAbstractDataSource.java at 1.2.13 · alibaba/druid · GitHub,结果1.2.12也有,继续往前找。

3. 终于找到"清白"版本

继续往前追溯,在 1.2.11 版本中,终于没找到这个默认配置! 测试验证:

  • 使用 1.2.11 :执行 SELECT SLEEP(30),正常等待 30 秒后返回结果。
  • 使用 1.2.12+:10 秒超时报错。

结论与思考

最终确认:Druid 从 1.2.12 版本开始,默认将 socketTimeout 设置为 10 秒。 为什么 Druid 要加这个默认配置呢?查看官方 Release Notes

官方说明明确提到:增加默认 10 秒超时,是为了减少因为网络丢包时导致的连接池无法创建链接的问题。 这是一个为了提升连接池健壮性的防御性配置,但在某些需要执行长耗时 SQL 的场景下,反而导致了超时问题。

解决方案

在 Druid 配置中显式设置 socketTimeout 为更大的值即可解决:

yaml 复制代码
spring:
  datasource:
    druid:
      socket-timeout: 60000 # 60秒

总结

这次排查从现象复现,到 AI 辅助定位,再到源码调试和版本考古,最终找到了问题的根本原因。技术问题的排查,往往需要一点"考古"精神,追根溯源,才能知其然更知其所以然。

相关推荐
Boop_wu21 小时前
简单介绍 JSON
java·开发语言
知识即是力量ol21 小时前
初识 Kafka(一):分布式流平台的定义、核心优势与架构全景
java·分布式·kafka·消息队列
爱吃生蚝的于勒21 小时前
【Linux】线程概念(一)
java·linux·运维·服务器·开发语言·数据结构·vim
kong790692821 小时前
Nginx性能优化
java·nginx·性能优化
Pluchon21 小时前
硅基计划4.0 算法 简单模拟实现位图&布隆过滤器
java·大数据·开发语言·数据结构·算法·哈希算法
我命由我1234521 小时前
Java 泛型 - Java 泛型通配符(上界通配符、下界通配符、无界通配符、PECS 原则)
java·开发语言·后端·java-ee·intellij-idea·idea·intellij idea
Seven9721 小时前
AQS深度探索:以ReentrantLock看Java并发编程的高效实现
java
4311媒体网21 小时前
C语言操作符全解析 C语言操作符详解
java·c语言·jvm
淡忘_cx21 小时前
使用Jenkins自动化部署spring-java项目+宝塔重启项目命令(2.528.2版本)
java·自动化·jenkins
毕设源码-钟学长21 小时前
【开题答辩全过程】以 基于SSM的孤儿救助信息管理系统设计与实现为例,包含答辩的问题和答案
java