前言
针对从Druid连接池中获取连接,归还连接的三个参数进行了源码阅读与分析
属性定义
属性 | 默认值 | 备注 |
---|---|---|
testOnBorrow | false | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 1分钟(1.2.8) | 有两个含义:1) Destroy线程会检测连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 |
testOnBorrow
入口
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException
作用
从数据库链接池中获取后,进行校验,避免失效的链接被使用,造成错误
源码
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
// 核心逻辑
System.out.println("getConnectionInternal");
// 获取的池化数据库链接
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
if (LOG.isWarnEnabled()) {
LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}
// testOnBorrow =false 每次获取链接都会验证
if (testOnBorrow) {
// 发送一条validateSql SELECT 1
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
// 如果不可用就回收
discardConnection(poolableConnection.holder);
continue;
}
}
分析
申请链接时候会进行一次验证,比较消耗性能,我的理解是无论是开发环境还是生产环境都不需要打开,Druid 1.2.8版本已经有其他的属性去在获取链接时判断链接的有效性
testOnReturn
入口方法
当连接使用完,调用commit或者rollback方法后,连接池会回收该连接
// 回收链接时候
protected void recycle(DruidPooledConnection pooledConnection)
作用
确保归还的时候链接是有效的,如果无效则会丢弃,丢弃的过程中会用lock锁,造成其他线程的堵塞,
源码
if (testOnReturn) {
boolean validate = testConnectionInternal(holder, physicalConnection);
if (!validate) {
JdbcUtils.close(physicalConnection);
destroyCountUpdater.incrementAndGet(this);
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
}
分析
每次事务提交或者回滚都会回收链接到连接池中,回收过程对链接判断是否有效,进行验证,浪费性能.
testWhileIdle
入口
与testOnBorrow一致,个人认为testWhileIdle就是代替testOnBorrow的
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException
作用
如果每次从数据库连接池获取链接都进行验证,性能太差,所以引入了空闲时间,当某个链接获取时,快速的check其是否过期.当判断是过期的链接,则进行校验.
源码
if (testWhileIdle) {
final DruidConnectionHolder holder = poolableConnection.holder;
long currentTimeMillis = System.currentTimeMillis();
// // 记录该链接最后活动时间
long lastActiveTimeMillis = holder.lastActiveTimeMillis;
// 记录该链接最后执行时间
long lastExecTimeMillis = holder.lastExecTimeMillis;
// 记录最后心跳时间
long lastKeepTimeMillis = holder.lastKeepTimeMillis;
// 是否检查执行时间 checkExecuteTime 默认false
if (checkExecuteTime
&& lastExecTimeMillis != lastActiveTimeMillis) {
lastActiveTimeMillis = lastExecTimeMillis;
}
// 如果最后一次心跳时间 大于 最后活动的时间,则赋值
if (lastKeepTimeMillis > lastActiveTimeMillis) {
lastActiveTimeMillis = lastKeepTimeMillis;
}
// 空闲时间 = 当前时间减去 - 最后活跃的时间
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
// 默认60秒
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (timeBetweenEvictionRunsMillis <= 0) {
timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
}
// 如果空闲时间 大于等于 60秒
if (idleMillis >= timeBetweenEvictionRunsMillis || idleMillis < 0 ) {
// 发送测试语句 SELECT 1 确保回收的是有效的链接
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
// 回收
discardConnection(poolableConnection.holder);
continue;
}
}
}
分析
如果每次从数据库连接池获取链接都进行验证,性能太差,这就是原来testOnBorrow的做法.不可取.
所以引入了testWhileIdle,吸取了testOnBorrow的弊端,设计了保活机制,增加了校验的前置判断,对从数据库连接池中获取的链接先进行保活判断,不满足主观的保活判断,再客观判断(就是SELECT 1)