接上一篇文章,钻研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 (一) - 初始化过程