一、背景
Mysql 的DBA给Mysql定义一套规则,mysql 服务器端的默认的超时时间wait_timeout为8小时,但DBA把wait_timeout改为600秒,我估计这规则本意是减少数据库的长时间链接的情况,只要链接空闲超过600秒,服务器端会自动断开链接。所有产生的影响必须由客户端程序来保障。
1.1、版本说明:
mysql:5.7.17
druid:1.1.5
mysql-connector-java:5.1.44
1.2、druid重要属性配置
二、现象
系统上线一段时间后,在监控时而报错如下:
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
The last packet successfully received from the server was 72,557 milliseconds ago. The last packet sent successfully to the server was 0 milliseconds ago.
根据错误日志初步判断肯定是 mysql服务端把链接已经断开,但客户端不知道并,依然尝试使用了一个已经断开的链接才会引起这个错误发生。但是根据我们对 druid 了解,druid 有链接检查功能,按理不会拿到一个无效链接才对。
三、分析
3.1、整体分析
image.png
图片表示了druid在获取线程池的大致的逻辑过程:druid在初始化时会创建两个守护线程,分别承担线程的创建(CreateConnectionThread)和销毁任务(DestoryConnectionThread),
当用户线程出现等待获取线程的操作时(且线程池中的线程数不大于最大活动线程数),创建线程会自动创建新的连接并放到线程池中,所以当用户线程需要新的连接时,只需要直接从线程池获取即可。
当用户线程从线程池中获取到连接会根据用户的配置决定是否线程进行有效性验证,如果验证线程有效则返回线程,如果无效则将该连接关闭,(DestoryConnectionThread自动回收已关闭的连接)
3.2、线程创建及销毁任务
程序启动在创建数据连接时,会自动创建两个任务(job),也就是CreateConnectionThread和DestoryConnectionThread
CreateConnectionThread比较简单,也是个守候线程,代码如下:
public class CreateConnectionThread extends Thread {
public CreateConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
long lastDiscardCount = 0;
int errorCount = 0;
for (;;) {
// addLast
try {
lock.lockInterruptibly();
} catch (InterruptedException e2) {
break;
}
long discardCount = DruidDataSource.this.discardCount;
boolean discardChanged = discardCount - lastDiscardCount > 0;
lastDiscardCount = discardCount;
try {
boolean emptyWait = true;
if (createError != null
&& poolingCount == 0
&& !discardChanged) {
emptyWait = false;
}
if (emptyWait
&& asyncInit && createCount < initialSize) {
emptyWait = false;
}
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& (!(keepAlive && activeCount + poolingCount < minIdle))
&& !isFailContinuous()
) {
empty.await();
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
} catch (InterruptedException e) {
lastCreateError = e;
lastErrorTimeMillis = System.currentTimeMillis();
if (!closing) {