java連接池Druid連接回收DestroyConnectionThread&DestroyTask
Druid連接池的連接回收線(xiàn)程DestroyThread
接上一篇文章,研究Druid連接池的連接回收線(xiàn)程DestroyThread,通過(guò)調(diào)用destroyTask.run->DruidDataSourcek.shrink完成過(guò)期連接的回收。
DruidDataSourcek.shrink
理解DruidDataSourcek的連接回收方法shrink有一個(gè)必要前提:Druid的getConnection方法總是從connectoins的尾部獲取連接,所以閑置連接最有可能出現(xiàn)在connections數(shù)組的頭部,閑置超期需要被回收的連接也應(yīng)該處于connections的頭部(數(shù)組下標(biāo)較小的對(duì)象)。
在這個(gè)基礎(chǔ)上,我們開(kāi)始分析代碼。
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;
}如果初始化尚未完成,則不能開(kāi)始做清理動(dòng)作,直接返回。
計(jì)算checkCount,checkCount的意思是本次需要清理、或者需要檢查的連接數(shù)量,checkCount等于連接池?cái)?shù)量減去參數(shù)設(shè)置的需要保持的最小空閑連接數(shù)。很好理解,清理完成之后仍然需要確保最小空閑連接數(shù)。
之后循環(huán)逐個(gè)檢查連接池connections中的所有連接,從頭部(connections[0])開(kāi)始。
如果清理過(guò)程中發(fā)生了錯(cuò)誤,并且錯(cuò)誤發(fā)生的時(shí)間是在當(dāng)前連接的連接獲取時(shí)間之后,則將當(dāng)前連接放入keepAliveConnections中,繼續(xù)檢查下一個(gè)連接。
然后:
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;
}
}這段代碼對(duì)應(yīng)的條件是checkTime,checkTime的意思是:是否檢查數(shù)據(jù)庫(kù)連接的空閑時(shí)間,是調(diào)用shrink方法時(shí)傳入的,destoryThread任務(wù)調(diào)用shrink方法時(shí)傳入的是true,所以會(huì)走到這段代碼邏輯中。
如果參數(shù)設(shè)置的phyTimeoutMillis,即物理連接的超時(shí)時(shí)間>0的話(huà),則檢查當(dāng)前連接創(chuàng)建以來(lái)到現(xiàn)在的時(shí)長(zhǎng)如果已超時(shí)的話(huà),當(dāng)前連接放入evictConnections中,準(zhǔn)備回收。
然后計(jì)算當(dāng)前連接的空閑時(shí)間idleMillis,如果空閑時(shí)間小于參數(shù)設(shè)置的連接最小空閑回收時(shí)間minEvictableIdleTimeMillis,并且也小于保持存活時(shí)間keepAliveBetweenTimeMillis,則結(jié)束當(dāng)前循環(huán),不再檢查連接池中剩余連接。
這里的邏輯其實(shí)也是基于connections的特性:數(shù)組第一個(gè)元素空閑時(shí)間最長(zhǎng),從左到右的空閑時(shí)間越來(lái)越短,如果從左到右檢查過(guò)程中發(fā)現(xiàn)當(dāng)前元素空閑時(shí)間沒(méi)有達(dá)到需要回收的時(shí)長(zhǎng)的話(huà),就沒(méi)必要檢查連接池中后續(xù)的元素了。
否則如果當(dāng)前連接空閑時(shí)長(zhǎng)idleMillis大于等于minEvictableIdleTimeMillis的話(huà),則判斷checkTime && i < checkCount的話(huà)則將當(dāng)前連接放入evictConnections中準(zhǔn)備回收。此處i < checkCount的意思就是,回收后的連接數(shù)量仍然能夠確保最小空閑連接數(shù)的要求,則直接回收當(dāng)前連接。
否則,就是i>=checkCount情況,這種情況下如果發(fā)生回收的話(huà),必然會(huì)導(dǎo)致連接池中的剩余連接數(shù)不能滿(mǎn)足參數(shù)設(shè)置的最小空閑連接數(shù)的要求、必須要重新創(chuàng)建連接了。但是如果空閑時(shí)長(zhǎng)大于maxEvictableIdleTimeMillis,也必須是要回收的,所以,將當(dāng)前連接放入evictConnections準(zhǔn)備回收。
有關(guān)連接回收,多說(shuō)一句,連接池參數(shù)maxEvictableIdleTimeMillis一般會(huì)根據(jù)數(shù)據(jù)庫(kù)端的參數(shù)進(jìn)行配置,連接閑置超過(guò)一定時(shí)長(zhǎng)的話(huà),數(shù)據(jù)庫(kù)會(huì)主動(dòng)關(guān)閉連接,這種情況下即使應(yīng)用端連接池不關(guān)閉連接,該連接也不可用了。所以為了確保連接可用,一般情況下應(yīng)用端數(shù)據(jù)庫(kù)連接池的maxEvictableIdleTimeMillis應(yīng)該設(shè)置為小于數(shù)據(jù)庫(kù)端的最大空閑時(shí)長(zhǎng)。
然后判斷如果keepAlive(參數(shù)設(shè)置,默認(rèn)false)并且當(dāng)前連接的空閑時(shí)間idleMillis大于等于參數(shù)設(shè)置的?;顣r(shí)長(zhǎng)keepAliveBetweenTimeMillis的話(huà),則當(dāng)前連接放入keepAliveConnections中?;?。
接下來(lái):
} else {
if (i < checkCount) {
evictConnections[evictCount++] = connection;
} else {
break;
}
}
}就是checkTime=false的情況,意思就是不檢查空閑時(shí)長(zhǎng),那么能夠確保最小空閑連接數(shù)的前提下,其他連接都可以回收,所以要把connections中小于checkCount((i < checkCount)的連接全部放入evictConnections中回收。
連接池中的連接檢查完畢,該回收連接放在evictConnections中,該保活的放在keepAliveConnections中。接下來(lái)的代碼開(kāi)始真正處理回收和保活。
清理連接池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;
}計(jì)算需要移除的連接數(shù)量removeCount等于回收數(shù)量與?;顢?shù)量之和,然后將connections中的位于removeCount之后的元素前移,使其處于connnections數(shù)組的頭部,并重新計(jì)算poolingCount。
接下來(lái)計(jì)算?;顢?shù)量
keepAliveCheckCount += keepAliveCount;
if (keepAlive && poolingCount + activeCount < minIdle) {
needFill = true;
}
} finally {
lock.unlock();
}累加keepAliveCheckCount,并且判斷如果連接池?cái)?shù)量小于最小空閑數(shù)的話(huà),設(shè)置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中的連接逐個(gè)回收:關(guān)閉連接,并累加destroyCount,并重新初始化evictConnections。
接下來(lái)處理?;钸B接:
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);
}逐個(gè)處理keepAliveConnections中的連接
調(diào)用validateConnection方法檢查當(dāng)前連接是否可用,如果連接仍然可用,則更新連接的lastKeepTimeMillis為當(dāng)前系統(tǒng)時(shí)間后,調(diào)用put方法將連接重新放回連接池connections中。如果放回失敗則關(guān)閉連接。連接關(guān)閉后檢查當(dāng)前連接池?cái)?shù)量activeCount + poolingCount <= minIdle則調(diào)用emptySignal();創(chuàng)建連接。
之后將keepAliveCount加入到連接統(tǒng)計(jì)分析數(shù)據(jù)中,重置keepAliveConnections數(shù)組。
最后:
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,說(shuō)明當(dāng)前連接池?cái)?shù)量不能滿(mǎn)足參數(shù)設(shè)置的最小空閑連接數(shù),則獲取鎖資源,計(jì)算需要?jiǎng)?chuàng)建的連接數(shù),調(diào)用emptySignal();創(chuàng)建連接填充連接池直到連接數(shù)滿(mǎn)足要求。
否則,如果發(fā)生錯(cuò)誤onFatalError,說(shuō)明有可能創(chuàng)建連接發(fā)生錯(cuò)誤,則調(diào)用emptySignal(),檢查并繼續(xù)創(chuàng)建連接。
Druid連接回收部分的代碼分析完畢!
小結(jié)
通過(guò)兩篇文章學(xué)習(xí)分析了Druid連接池的初始化及連接回收過(guò)程,還有連接獲取及關(guān)閉兩部分重要內(nèi)容,下一篇文章繼續(xù)分析,更多關(guān)于java連接池Druid連接回收的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java編寫(xiě)的簡(jiǎn)單移動(dòng)方塊小游戲代碼
這篇文章主要介紹了java編寫(xiě)的簡(jiǎn)單移動(dòng)方塊小游戲代碼,涉及Java簡(jiǎn)單圖形繪制與事件響應(yīng)的相關(guān)技巧,需要的朋友可以參考下2015-12-12
如何使用intellij IDEA搭建Spring Boot項(xiàng)目
這篇文章主要介紹了如何使用intellij IDEA搭建Spring Boot項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
java實(shí)現(xiàn)簡(jiǎn)單猜拳小游戲
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單猜拳小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11
SpringTask實(shí)現(xiàn)定時(shí)任務(wù)方法講解
通過(guò)重寫(xiě)Schedu lingConfigurer方法實(shí)現(xiàn)對(duì)定時(shí)任務(wù)的操作,單次執(zhí)行、停止、啟動(dòng)三個(gè)主要的基本功能,動(dòng)態(tài)的從數(shù)據(jù)庫(kù)中獲取配置的定時(shí)任務(wù)cron信息,通過(guò)反射的方式靈活定位到具體的類(lèi)與方法中2023-02-02
java文件操作報(bào)錯(cuò):java.io.FileNotFoundException(拒絕訪(fǎng)問(wèn))問(wèn)題
在進(jìn)行編程時(shí),經(jīng)常會(huì)遇到因疏忽小細(xì)節(jié)而導(dǎo)致的錯(cuò)誤,如忘記在路徑后添加文件名,本文通過(guò)一個(gè)具體的修改前后對(duì)比示例,解釋了錯(cuò)誤原因,并給出了解決方案,這類(lèi)經(jīng)驗(yàn)分享對(duì)編程學(xué)習(xí)者具有參考價(jià)值2024-10-10
Spring Boot啟動(dòng)banner定制的步驟詳解
這篇文章主要給大家介紹了關(guān)于Spring Boot啟動(dòng)banner定制的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
Java實(shí)現(xiàn)瀏覽器大文件上傳的示例詳解
文件上傳是許多項(xiàng)目都有的功能,用戶(hù)上傳小文件速度一般都很快,但如果是大文件幾個(gè)g,幾十個(gè)g的時(shí)候,上傳了半天,馬上就要完成的時(shí)候,網(wǎng)絡(luò)波動(dòng)一下,文件又要重新上傳,所以本文給大家介紹了Java實(shí)現(xiàn)瀏覽器大文件上傳的示例,需要的朋友可以參考下2024-07-07

