共计 5741 个字符,预计需要花费 15 分钟才能阅读完成。
接上一篇文章,钻研 Druid 连接池的连贯回收线程 DestroyThread,通过调用 destroyTask.run->DruidDataSourcek.shrink 实现过期连贯的回收。
DruidDataSourcek.shrink
了解 DruidDataSourcek 的连贯回收办法 shrink 有一个必要前提:Druid 的 getConnection 办法总是从 connectoins 的尾部获取连贯,所以闲置连贯最有可能呈现在 connections 数组的头部,闲置超期须要被回收的连贯也应该处于 connections 的头部(数组下标较小的对象)。
在这个根底上,咱们开始剖析代码。
public void shrink(boolean checkTime, boolean keepAlive) {
try {lock.lockInterruptibly();
} catch (InterruptedException e) {return;}
boolean needFill = false;
int evictCount = 0;
int keepAliveCount = 0;
int fatalErrorIncrement = fatalErrorCount - fatalErrorCountLastShrink;
fatalErrorCountLastShrink = fatalErrorCount;
首先获取锁资源,并初始化控制变量。
try {if (!inited) {return;}
final int checkCount = poolingCount - minIdle;
final long currentTimeMillis = System.currentTimeMillis();
for (int i = 0; i < poolingCount; ++i) {DruidConnectionHolder connection = connections[i];
if ((onFatalError || fatalErrorIncrement > 0) && (lastFatalErrorTimeMillis > connection.connectTimeMillis)) {keepAliveConnections[keepAliveCount++] = connection;
continue;
}
如果初始化尚未实现,则不能开始做清理动作,间接返回。
计算 checkCount,checkCount 的意思是本次须要清理、或者须要查看的连贯数量,checkCount 等于连接池数量减去参数设置的须要放弃的最小闲暇连接数。很好了解,清理实现之后依然须要确保最小闲暇连接数。
之后循环一一查看连接池 connections 中的所有连贯,从头部 (connections[0]) 开始。
如果清理过程中产生了谬误,并且谬误产生的工夫是在以后连贯的连贯获取工夫之后,则将以后连贯放入 keepAliveConnections 中,持续查看下一个连贯。
而后:
if (checkTime) {if (phyTimeoutMillis > 0) {
long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {evictConnections[evictCount++] = connection;
continue;
}
}
long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;
if (idleMillis < minEvictableIdleTimeMillis
&& idleMillis < keepAliveBetweenTimeMillis
) {break;}
if (idleMillis >= minEvictableIdleTimeMillis) {if (checkTime && i < checkCount) {evictConnections[evictCount++] = connection;
continue;
} else if (idleMillis > maxEvictableIdleTimeMillis) {evictConnections[evictCount++] = connection;
continue;
}
}
if (keepAlive && idleMillis >= keepAliveBetweenTimeMillis) {keepAliveConnections[keepAliveCount++] = connection;
}
}
这段代码对应的条件是 checkTime,checkTime 的意思是:是否查看数据库连贯的闲暇工夫,是调用 shrink 办法时传入的,destoryThread 工作调用 shrink 办法时传入的是 true,所以会走到这段代码逻辑中。
如果参数设置的 phyTimeoutMillis,即物理连贯的超时工夫 >0 的话,则查看以后连贯创立以来到当初的时长如果已超时的话,以后连贯放入 evictConnections 中,筹备回收。
而后计算以后连贯的闲暇工夫 idleMillis,如果闲暇工夫小于参数设置的连贯最小闲暇回收工夫 minEvictableIdleTimeMillis,并且也小于放弃存活工夫 keepAliveBetweenTimeMillis,则完结以后循环,不再查看连接池中残余连贯。
这里的逻辑其实也是基于 connections 的个性:数组第一个元素闲暇工夫最长,从左到右的闲暇工夫越来越短,如果从左到右查看过程中发现以后元素闲暇工夫没有达到须要回收的时长的话,就没必要查看连接池中后续的元素了。
否则如果以后连贯闲暇时长 idleMillis 大于等于 minEvictableIdleTimeMillis 的话,则判断 checkTime && i < checkCount 的话则将以后连贯放入 evictConnections 中筹备回收。此处 i < checkCount 的意思就是,回收后的连贯数量依然可能确保最小闲暇连接数的要求,则间接回收以后连贯。
否则,就是 i >=checkCount 状况,这种状况下如果产生回收的话,必然会导致连接池中的残余连接数不能满足参数设置的最小闲暇连接数的要求、必须要从新创立连贯了。然而如果闲暇时长大于 maxEvictableIdleTimeMillis,也必须是要回收的,所以,将以后连贯放入 evictConnections 筹备回收。
无关连贯回收,多说一句,连接池参数 maxEvictableIdleTimeMillis 个别会依据数据库端的参数进行配置,连贯闲置超过肯定时长的话,数据库会被动敞开连贯,这种状况下即便利用端连接池不敞开连贯,该连贯也不可用了。所以为了确保连贯可用,个别状况下利用端数据库连接池的 maxEvictableIdleTimeMillis 应该设置为小于数据库端的最大闲暇时长。
而后判断如果 keepAlive(参数设置,默认 false)并且以后连贯的闲暇工夫 idleMillis 大于等于参数设置的保活时长 keepAliveBetweenTimeMillis 的话,则以后连贯放入 keepAliveConnections 中保活。
接下来:
} else {if (i < checkCount) {evictConnections[evictCount++] = connection;
} else {break;}
}
}
就是 checkTime=false 的状况,意思就是不查看闲暇时长,那么可能确保最小闲暇连接数的前提下,其余连贯都能够回收,所以要把 connections 中小于 checkCount((i < checkCount)的连贯全副放入 evictConnections 中回收。
连接池中的连贯查看结束,该回收连贯放在 evictConnections 中,该保活的放在 keepAliveConnections 中。接下来的代码开始真正解决回收和保活。
首先清理连接池 connections:
int removeCount = evictCount + keepAliveCount;
if (removeCount > 0) {System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
poolingCount -= removeCount;
}
计算须要移除的连贯数量 removeCount 等于回收数量与保活数量之和,而后将 connections 中的位于 removeCount 之后的元素前移,使其处于 connnections 数组的头部,并从新计算 poolingCount。
接下来计算保活数量:
keepAliveCheckCount += keepAliveCount;
if (keepAlive && poolingCount + activeCount < minIdle) {needFill = true;}
} finally {lock.unlock();
}
累加 keepAliveCheckCount,并且判断如果连接池数量小于最小闲暇数的话,设置 needFill 为 true。
开释锁资源。
而后回收连贯:
if (evictCount > 0) {for (int i = 0; i < evictCount; ++i) {DruidConnectionHolder item = evictConnections[i];
Connection connection = item.getConnection();
JdbcUtils.close(connection);
destroyCountUpdater.incrementAndGet(this);
}
Arrays.fill(evictConnections, null);
}
将 evictConnections 中的连贯一一回收:敞开连贯,并累加 destroyCount,并从新初始化 evictConnections。
接下来解决保活连贯:
if (keepAliveCount > 0) {
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {DruidConnectionHolder holer = keepAliveConnections[i];
Connection connection = holer.getConnection();
holer.incrementKeepAliveCheckCount();
boolean validate = false;
try {this.validateConnection(connection);
validate = true;
} catch (Throwable error) {if (LOG.isDebugEnabled()) {LOG.debug("keepAliveErr", error);
}
// skip
}
boolean discard = !validate;
if (validate) {holer.lastKeepTimeMillis = System.currentTimeMillis();
boolean putOk = put(holer, 0L);
if (!putOk) {discard = true;}
}
if (discard) {
try {connection.close();
} catch (Exception e) {// skip}
lock.lock();
try {
discardCount++;
if (activeCount + poolingCount <= minIdle) {emptySignal();
}
} finally {lock.unlock();
}
}
}
this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
Arrays.fill(keepAliveConnections, null);
}
一一解决 keepAliveConnections 中的连贯。
调用 validateConnection 办法查看以后连贯是否可用,如果连贯依然可用,则更新连贯的 lastKeepTimeMillis 为以后零碎工夫后,调用 put 办法将连贯从新放回连接池 connections 中。如果放回失败则敞开连贯。连贯敞开后查看以后连接池数量 activeCount + poolingCount <= minIdle 则调用 emptySignal(); 创立连贯。
之后将 keepAliveCount 退出到连贯统计分析数据中,重置 keepAliveConnections 数组。
最初:
if (needFill) {lock.lock();
try {int fillCount = minIdle - (activeCount + poolingCount + createTaskCount);
for (int i = 0; i < fillCount; ++i) {emptySignal();
}
} finally {lock.unlock();
}
} else if (onFatalError || fatalErrorIncrement > 0) {lock.lock();
try {emptySignal();
} finally {lock.unlock();
}
}
}
查看如果 needFill,阐明以后连接池数量不能满足参数设置的最小闲暇连接数,则获取锁资源,计算须要创立的连接数,调用 emptySignal(); 创立连贯填充连接池直到连接数满足要求。
否则,如果产生谬误 onFatalError,阐明有可能创立连贯产生谬误,则调用 emptySignal(),查看并持续创立连贯。
Druid 连贯回收局部的代码剖析结束!
小结
通过两篇文章学习剖析了 Druid 连接池的初始化及连贯回收过程,还有连贯获取及敞开两局部重要内容,下一篇文章持续剖析。
Thanks a lot!
上一篇 连接池 Druid(一)– 初始化过程