说明:不能这么配置,仅供参考,后续会重写一篇druid连接池的文章,目前本文的操作会导致一个问题:当数据源关闭时连接失败,数据源重启后就无法连接了,本周开发任务有点大,所以一直没有更新…所以说网上相关的文章也不能照葫芦画瓢,需要辩证的看待,DruidDataSource相关介绍稍微写了一下,地址在
京思祺:DruidDataSoure介绍zhuanlan.zhihu.com昨晚上线以后出现了一个非常神奇的"MySQLTransactionRollbackException;Lock wait timeout exceeded; try restarting transaction"错误,经过排查以后发现是没有调整配置的情况下导致DruidDataSource不断请求sqlserver导致的,特此记录一下
还原案发现场
业务逻辑是这样的,当一个业务单据进来的时候,会先进行id+type的唯一索引校验,当不存在的时候,进行单据的插入,随后同步到金蝶数据库,流程如下:
这个时候神奇的事情发生了:
DruidDataSource在连接sqlServer失败的情况下,疯狂的开始了重试,此时mysql中的插入操作还占着那一条单据的唯一索引,这个时候定时任务来了一条相同的业务请求,然后就出现了喜大普奔的报错:"MySQLTransactionRollbackException;Lock wait timeout exceeded; try restarting transaction"
问题排查
在查的时候发现一个非常神奇的run方法:
代码是这样的:
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) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& !(keepAlive && activeCount + poolingCount < minIdle)) {
empty.await();
}
// 防止创建超过maxActive数量的连接
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
} catch (InterruptedException e) {
lastCreateError = e;
lastErrorTimeMillis = System.currentTimeMillis();
break;
} finally {
lock.unlock();
}
PhysicalConnectionInfo connection = null;
try {
connection = createPhysicalConnection();
setFailContinuous(false);
} catch (SQLException e) {
LOG.error("create connection error, url: " + jdbcUrl + ", errorCode " + e.getErrorCode()
+ ", state " + e.getSQLState(), e);
errorCount++;
if (errorCount > connectionErrorRetryAttempts && timeBetweenConnectErrorMillis > 0) {
// fail over retry attempts
setFailContinuous(true);
if (failFast) {
lock.lock();
try {
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
if (breakAfterAcquireFailure) {
break;
}
try {
Thread.sleep(timeBetweenConnectErrorMillis);
} catch (InterruptedException interruptEx) {
break;
}
}
} catch (RuntimeException e) {
LOG.error("create connection error", e);
setFailContinuous(true);
continue;
} catch (Error e) {
LOG.error("create connection error", e);
setFailContinuous(true);
break;
}
if (connection == null) {
continue;
}
boolean result = put(connection);
if (!result) {
JdbcUtils.close(connection.getPhysicalConnection());
LOG.info("put physical connection to pool failed.");
}
errorCount = 0; // reset errorCount
}
}
这个时候魔性的事情发生了,可以看到connection一直都是null的
然后就开始了罪恶的一生:
if (connection == null) {
continue;
}
然后这个run方法是:
for (;;){}
在catch中可以看到断开判断是
第一个重试次数是30次,第二个是两个连接的间隔时间,可以看到要重试相当长一波才会进入到断开连接的判断中去,仔细看这个判断中的方法:
if
可以看到breakAfterAcquireFailure这个值为true的时候回断开这次重试,重试方法中的相关参数如注释:
//重试次数,进入时默认设置为0,每次失败累加
在经历了这一波操作后,如果还活着,就回到了那熟悉的节奏:
if
修改起来也非常简单,只需要做几个操作即可:
- 调整重试次数限制
- 调整breakAfterAcquireFailure 的默认值
- 设置maxWait最大连接等待时间
这里需要说明一下,不设置最大连接等待时间还是会有问题,测试的时候发现break了之前的请求仍然还在..像是一直在getConnection
其他Druid相关配置答疑可以参考Druid的开源仓库建议配置和答疑,Druid的文档有中文版的,所以看起来也没有什么压力,感觉可以学习一下,毕竟简介上写的是Java语言中最好的数据库连接池
祖传代码害死人啊!!!!