Druid connection holder is null未解之謎

系統(tǒng)中出現(xiàn)過(guò)幾次connection holder is null問(wèn)題,有的已解決,有的未解決,記錄如下。

首先說(shuō)先druid連接池的實(shí)現(xiàn):

  • DruidPooledConnection是一個(gè)靜態(tài)代理,持有ConnectionHolder, connection Holder里持有具體的connection對(duì)象, 在執(zhí)行druidPooledConnection的所有和數(shù)據(jù)庫(kù)相關(guān)方法時(shí),都會(huì)先調(diào)用checkState()判斷connection holder是否為null,如果是null就拋connection holder is null的異常。

  • 那connection holder是什么時(shí)候賦值以及什么時(shí)候置成null的?
    在Datasource.getConnection()獲取連接的時(shí)候,是從池里取出緩存的connection holder對(duì)象,druid是用一個(gè)數(shù)組緩存connection holder對(duì)象,每次都是從最后一個(gè)取,還的時(shí)候也是放到最后,這樣保證位于數(shù)組最后的連接會(huì)經(jīng)常處于使用狀態(tài),當(dāng)然這中間會(huì)有鎖的使用以及池里沒(méi)線(xiàn)程了通知任務(wù)線(xiàn)程去創(chuàng)建新連接。
    Datasource.getConnection()從池里拿出connection holder后,然后new一個(gè)druidPooledConnection去包裝connection holder,所有每次看到都是不同的druidPooledConnection對(duì)象。

第一次:系統(tǒng)中事務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng),超過(guò)60秒,后面導(dǎo)致有的請(qǐng)求會(huì)報(bào)connection holder is null。

  • 拿出來(lái)的connection holder肯定不為null,項(xiàng)目中報(bào)connection holder is null,說(shuō)明是在使用過(guò)程中connection holder被置成null了,很大概率是被別的線(xiàn)程置成null了,因?yàn)楸揪€(xiàn)程只有在事務(wù)提交后還連接的時(shí)候才置null,在github issue上,作者也反復(fù)強(qiáng)調(diào)連接不要跨線(xiàn)程使用。而druid真的就有跨線(xiàn)程操作連接的地方,就是remove abandoned connection功能,這個(gè)功能是為了回收長(zhǎng)時(shí)間還沒(méi)還到池里的連接,多長(zhǎng)時(shí)間看你設(shè)置,而我們項(xiàng)目設(shè)置的60秒沒(méi)還就強(qiáng)制回收,這樣就會(huì)報(bào)上面的錯(cuò)誤了。

  • 建議在生產(chǎn)環(huán)境關(guān)閉remove abandoned功能,如果數(shù)據(jù)庫(kù)負(fù)載不重的話(huà),可以開(kāi)啟testOnBorrow。 testWhileIde不建議開(kāi),因?yàn)椴l(fā)請(qǐng)求多的話(huà),數(shù)組后面的連接都不是idle狀態(tài),開(kāi)沒(méi)開(kāi)testWhileIdle沒(méi)啥區(qū)別。

第二次系統(tǒng)中有的事務(wù)長(zhǎng)時(shí)間未提交,DBA會(huì)把這個(gè)連接kill掉,后面請(qǐng)求會(huì)報(bào)conneciton holder is null

  • 為什么有長(zhǎng)時(shí)間未提交的事務(wù),這個(gè)問(wèn)題還沒(méi)找到原因,從Mysql的innodb_trx和lock表里沒(méi)看到有價(jià)值線(xiàn)索,后面想跟蹤事務(wù)和連接來(lái)看看有沒(méi)有收獲。
  • 連接被kill了,應(yīng)用端會(huì)報(bào)這樣的異常:
  1. Can not read response from server. Expected to read 4 bytes, read 0 bytes before connection was unexpectedly lost.
  2. com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure. The last packet successfully received from the server was 20,840 milliseconds ago. The last packet sent successfully to the server was 20,840 milliseconds ago.
  3. connection holder is null

前面2個(gè)異常很好理解,tcp連接斷了,應(yīng)用端讀不到數(shù)據(jù)報(bào)錯(cuò),然后druid捕獲到異常,要去判斷這個(gè)異常是可恢復(fù)異常還是不可恢復(fù)異常。因?yàn)檎驹谶B接池的角度來(lái)說(shuō),數(shù)據(jù)庫(kù)拋異常太普遍了,可能是唯一索引重復(fù)也可能是連接斷了,對(duì)于不同的異常處理方式也是不一樣的,唯一索引重復(fù)需要調(diào)用connection.rollback(),然后再把連接還到池里,因?yàn)檫@個(gè)連接還是好的,不影響下次繼續(xù)使用。而連接斷了,則要把這個(gè)連接踢出去,druid用了ExceptionSorter來(lái)判斷這個(gè)異常是不是不可恢復(fù)異常,在轉(zhuǎn)換異常的時(shí)候要用當(dāng)前連接獲取數(shù)據(jù)庫(kù)的metadata,而當(dāng)前連接已經(jīng)斷了,所以報(bào)connection holder is null。
但是這個(gè)connection holder is null只會(huì)報(bào)一次,和項(xiàng)目中大量報(bào)connection holder is null不是一個(gè)東西,目前還沒(méi)找到原因。而這個(gè)問(wèn)題在本地卻重現(xiàn)不了。

PS:數(shù)據(jù)庫(kù)有一個(gè)設(shè)置 rollback_on_timeout,默認(rèn)是off,這個(gè)值是說(shuō)當(dāng)事務(wù)超時(shí)(如超過(guò)50秒還沒(méi)獲取到鎖),默認(rèn)off是回滾最后一條sql語(yǔ)句,on是回滾整個(gè)事務(wù)。這個(gè)值一般不需要設(shè)置成on,交由應(yīng)用去處理,應(yīng)用在獲取不到 can't acquire lock的時(shí)候,一般會(huì)去調(diào)connection.rollback(),當(dāng)然前提是要你的應(yīng)用開(kāi)啟事務(wù)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容