三坟奥、HikariCP源碼分析之獲取連接流程三

歡迎訪問我的博客鸯绿,同步更新: 楓山別院
話接上篇,我們繼續(xù)分析HikariCP獲取連接的過程昂儒。

③拿到一個連接

//③
//獲取連接的時候, 判斷連接是否已經(jīng)被標(biāo)記移除
if (poolEntry.isMarkedEvicted() || (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) {
    //如果連接超出maxLifetime, 或者連接測試不通過, 就關(guān)閉連接
    closeConnection(poolEntry, "(connection is evicted or dead)"); // Throw away the dead connection (passed max age or failed alive test)
    //剩余超時時間
    timeout = hardTimeout - clockSource.elapsedMillis(startTime);
} 

如果connectionBag給我們返回了一個連接沟使,那么需要判斷兩個條件:

  1. 該連接是否被軟驅(qū)逐了,poolEntry.isMarkedEvicted()

  2. 該連接是否已經(jīng)不可用了或者說已經(jīng)不能通過連接檢查渊跋,!isConnectionAlive(poolEntry.connection)

為什么需要判斷呢格带?連接池里的連接不應(yīng)該都是可用的狀態(tài)嗎?

這里涉及到 HikariCP 的一個設(shè)計點(diǎn)刹枉,HikariCP的連接不是實(shí)時從連接池里剔除的,只是給連接上打個標(biāo)記而已屈呕,都是在獲取連接的時候檢查是否可用微宝,如果不可用的時候才直接從連接池里刪除。如果在 HikariCP的任何地方都可能剔除連接虎眨,那么剔除連接的地方會比較多蟋软,會很亂镶摘,也容易引發(fā) bug。反之岳守,把剔除鏈接的操作收縮到某幾個固定的邏輯中凄敢,就比較好管理。

  • 軟驅(qū)逐

我們在上面提到一個軟驅(qū)逐的地方湿痢, 就是掛起連接池修改配置的時候涝缝,修改完之后要軟驅(qū)逐所以的連接,使新配置生效譬重。

其實(shí)軟驅(qū)逐是一個標(biāo)記狀態(tài)拒逮,是一個軟刪除,在PoolEntry上臀规,有個狀態(tài)叫做evict滩援,如果是 true,那么塔嬉,該連接已經(jīng)被標(biāo)記刪除玩徊,不能使用了。然后某個線程在獲取連接的時候谨究,正好拿到了這個連接恩袱,判斷出來它已經(jīng)被軟驅(qū)逐,就觸發(fā)從連接池刪除該連接的邏輯记盒。

關(guān)閉連接的邏輯我們后面單獨(dú)分析憎蛤,此處就不深入了。

  • 連接可用檢查

檢查連接是否可用的條件纪吮,其實(shí)是兩個:(clockSource.elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection) 俩檬。它們使用 and 連接,也就是這兩個條件都必須成立碾盟。isConnectionAlive方法比較好理解棚辽,我們從字面也能看出這個方法的作用,是判斷連接是否還活著冰肴。那么前面的條件是什么呢屈藐?

我看其他的解析文章根本沒有提到這里,我們是要解釋一下的熙尉。

clockSource.elapsedMillis(poolEntry.lastAccessed, now)這句代碼里联逻,poolEntry.lastAccessed是獲取連接上次使用的時間,now是當(dāng)前時間检痰,那么elapsedMillis其實(shí)就是計算連接到現(xiàn)在多長時間沒有被使用過了包归,結(jié)果是個毫秒數(shù)。

ALIVE_BYPASS_WINDOW_MS的定義是private final long ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", MILLISECONDS.toMillis(500));铅歼,它看起來像是一個配置項(xiàng)公壤,默認(rèn)值是 500 毫秒换可。這個配置你要是從文檔里找的話,是沒有的厦幅,因?yàn)檫@個配置作者沒有透出給用戶使用沾鳄。但是你要是配置了,是管用的确憨,只是作者不建議用戶修改译荞,所以不透出。它是什么呢缚态?既然跟檢查連接要同時成立磁椒,隨便猜猜也知道跟它有關(guān)。不賣關(guān)子玫芦,它是檢查連接是否活著的空窗期浆熔,也就是說,如果這個連接從上次使用到現(xiàn)在桥帆,不到 500 毫秒医增,就不檢查它是否活著了,默認(rèn)它活著老虫;超過 500 毫秒叶骨,才檢查一下。

看起來又是一個優(yōu)化點(diǎn)對吧祈匙?是的忽刽,是一個優(yōu)化點(diǎn)。因?yàn)闄z查連接是否還存活夺欲,是比較耗時的跪帝,要使用該連接跟數(shù)據(jù)庫通信一次。

有兩種通信方式:

  1. JDBC4 以下版本的驅(qū)動些阅,使用用戶配置的connectionTestQuery中的 sql 來檢查伞剑。

connectionTestQuery是獲取連接的時候,用于檢查連接是否可用的一個 sql市埋,大家可能用過黎泣,常見的是配置一個select 1

  1. JDBC4 以上缤谎,如果不配置connectionTestQuery抒倚, 默認(rèn)使用 ping 命令檢查。

如果使用的是 JDBC4 以上的驅(qū)動坷澡,建議大家不用配置connectionTestQuery衡便,因?yàn)?ping 命令的方式比執(zhí)行一個 sql 要高效很多。

不管是使用較慢的執(zhí)行 sql 檢查還是 較快的ping 命令檢查,這都是一個耗時操作镣陕,所以作者設(shè)置了一個空窗期,不需要每次獲取連接都檢查姻政,500毫秒內(nèi)用過該連接呆抑,那么連接還正常的可能性極大,就不檢查了汁展,提高性能鹊碍。

后面closeConnection我們先不說,后面的文章統(tǒng)一分析連接關(guān)閉食绿。

④連接可用

//④
//記錄連接借用
metricsTracker.recordBorrowStats(poolEntry, startTime);
//創(chuàng)建ProxyConnection, ProxyConnection是Connection的包裝, 同時也創(chuàng)建一個泄露檢測的定時任務(wù)
return poolEntry.createProxyConnection(leakTask.schedule(poolEntry), now);

如果第 3 步的檢查全部通過侈咕,也就是拿到的連接是可用的,我們就要執(zhí)行第 4 步了器紧。

  • 上報監(jiān)控平臺

metricsTracker這一句耀销,其實(shí)是記錄連接的借用,不是我們通常使用的打印一下日志铲汪,而是上報給監(jiān)控平臺熊尉,HikariCP 是支持對接監(jiān)控平臺的。這里大家先知道這個邏輯掌腰,后面我們統(tǒng)一分析上報監(jiān)控平臺狰住。

  • 為什么用代理連接?

最主要的就是return 的這一句代碼了吧齿梁。我們說過poolEntry是底層數(shù)據(jù)庫連接的一個包裝類催植,代表一個數(shù)據(jù)庫連接。那么從createProxyConnection字面來看勺择,這個方法并不是直接返回數(shù)據(jù)庫連接給用戶使用创南,而是創(chuàng)建了一個代理連接,這個代理連接是什么酵幕?為什么不直接返回數(shù)據(jù)庫連接給用戶使用扰藕?

不管我們使用 Spring 還是自己寫的代碼從 HikariCP 連接池里拿連接,都是拿到一個java.sql.Connection類型的對象沒錯吧芳撒?它是一個 java 統(tǒng)一的數(shù)據(jù)庫連接接口邓深,不管你使用的是 mysql 還是oracle 等數(shù)據(jù)庫,都是統(tǒng)一對接這個接口笔刹,都必須返回一個這個類型的連接給用戶使用芥备,相當(dāng)于一個門面模式的設(shè)計,這樣用戶可以不理會底層使用什么數(shù)據(jù)庫舌菜,代碼都是一個樣的萌壳。既然如此,HikariCP應(yīng)該直接返回一個java.sql.Connection對吧?

沒有那么簡單袱瓮。試想一下缤骨,假如 HikariCP 直接返回底層的數(shù)據(jù)庫連接給用戶使用,那么尺借,如果用戶自己關(guān)閉了這個底層數(shù)據(jù)庫連接呢绊起?那么這個連接在連接池里相當(dāng)于已經(jīng)不可用了,其他線程也使用不了了燎斩。作為一個框架設(shè)計者虱歪,不能指望每個用戶都是高手,他們都能在用完數(shù)據(jù)庫連接不會關(guān)閉它并且要還回連接池中栅表,肯定有小白用戶或者很唬的不管三七二十一的人笋鄙。更何況除了關(guān)閉連接,還有你修改了連接的設(shè)置呢怪瓶,比如自動提交事務(wù)萧落,連接只讀這些設(shè)置,然后沒有恢復(fù)回原來的設(shè)置怎么辦劳殖?如此混亂的話铐尚,我們使用連接池就沒有意義了。所以我們不能把底層數(shù)據(jù)庫連接直接給用戶使用哆姻,這個大家理解了吧宣增?

如何來實(shí)現(xiàn)呢?我們可以繼承java.sql.Connection矛缨,創(chuàng)建一個它的子類爹脾,子類可以直接當(dāng)做父類來用,沒錯吧箕昭?然后我們在子類里覆蓋java.sql.Connection里面敏感的操作灵妨,比如關(guān)閉連接,如果用戶調(diào)用了關(guān)閉連接操作落竹,不是真正的關(guān)閉底層連接泌霍,而是將連接還回到連接池。怎么樣述召?我們解決了用戶瞎用的問題了吧朱转。作者就是這個目的,才設(shè)計了一個createProxyConnection方法來創(chuàng)建了一個連接的代理ProxyConnection积暖,將這個代理返回給用戶使用藤为。一切如我們所說的,ProxyConnection繼承了java.sql.Connection夺刑,覆蓋了一些方法缅疟,詳細(xì)的我們后面單獨(dú)的文章解析分别,這里很重要。

  • 泄露檢測

我之前寫過一個連接泄露檢測的文章存淫,是我寫的瀏覽量最大的文章耘斩,這說明,有不少人都遇到這個問題纫雁。在 HikariCP 檢測到連接泄露的時候煌往,會拋出一個 warn:java.lang.Exception: Apparent connection leak detected。我們在這里詳細(xì)說一下這個地方的邏輯轧邪。

  1. 連接泄露檢測的相關(guān)配置

有一個leakDetectionThreshold的配置,這個就是連接泄露檢測的最大時間羞海,默認(rèn)是 0忌愚,表示不啟用泄露檢測;最小值 2000 毫秒却邓,如果用戶設(shè)置的小于 2000 毫秒硕糊,默認(rèn)關(guān)閉泄露檢測,最大值不能超過連接的最大存活時間腊徙,也就是maxLifetime配置简十,超過的話也會自動禁用泄露檢測。

  1. 泄露檢測的定時任務(wù)

createProxyConnection方法中撬腾,我們可以看到傳了一個參數(shù)leakTask.schedule(poolEntry)螟蝙。leakTask的類型是ProxyLeakTask,它實(shí)現(xiàn)了Runnable接口民傻,是一個多線程的定時任務(wù)實(shí)現(xiàn)胰默。它的內(nèi)部持有幾個成員變量:ScheduledExecutorService,是用來執(zhí)行泄露檢測定時任務(wù)的線程池漓踢;leakDetectionThreshold牵署,是泄露檢測超時時間;

scheduledFuture是任務(wù)的 future 結(jié)果喧半,可以用來取消定時任務(wù)奴迅。

我們看下它的schedule方法:

ProxyLeakTask schedule(final PoolEntry bagEntry) {
      return (leakDetectionThreshold == 0) ? NO_LEAK : new ProxyLeakTask(this, bagEntry);
   }

這里判斷了下用戶有沒有開啟泄露檢測功能,如果是沒有開啟挺据,那么就返回一個NO_LEAK取具。大家還記得FAUX_LOCK吧?就是上面的①處令牌桶的實(shí)現(xiàn)吴菠,是提供了一個空實(shí)現(xiàn)對吧者填?這里也是同樣的道理,NO_LEAK是一個空實(shí)現(xiàn)做葵,如果用戶沒有開啟泄露檢測就方便 JIT 把這段邏輯優(yōu)化掉占哟。

OK,我們看下new ProxyLeakTask(this, bagEntry)的實(shí)現(xiàn):

private ProxyLeakTask(final ProxyLeakTask parent, final PoolEntry poolEntry) {
      this.exception = new Exception("Apparent connection leak detected");
      this.connectionName = poolEntry.connection.toString();
      scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);
}

大家仔細(xì)觀察下這個構(gòu)造方法,第一個參數(shù)也是一個ProxyLeakTask榨乎,看名字parent是個父任務(wù)怎燥。這個父任務(wù)在連接池初始化的時候會創(chuàng)建,創(chuàng)建的時候需要兩個參數(shù)蜜暑,一個是用于執(zhí)行任務(wù)的線程池executorService铐姚,另一個是連接泄露超時時間leakDetectionThreshold。此處傳遞父任務(wù)進(jìn)來就是要使用父任務(wù)中的線程池和連接泄露超時時間肛捍。

我們看下超時檢測的任務(wù)實(shí)現(xiàn):

public void run() {
      final StackTraceElement[] stackTrace = exception.getStackTrace();
      final StackTraceElement[] trace = new StackTraceElement[stackTrace.length - 5];
      System.arraycopy(stackTrace, 5, trace, 0, trace.length);

      exception.setStackTrace(trace);
      LOGGER.warn("Connection leak detection triggered for {}, stack trace follows", connectionName, exception);
   }

由于這里不太重要隐绵,我們就不一句一句的分析了,整個run方法就是構(gòu)造一個異常拙毫,然后拋出一個 warn 異常棧依许。

到此,我們整個連接泄露的分析就結(jié)束了缀蹄。

  • 釋放鎖

有一個需要注意的是峭跳,我們在最開始的第一句,是申請了一個令牌缺前,現(xiàn)在上面已經(jīng)獲取到了可用連接蛀醉,我們需要釋放這個令牌。我們在使用其他鎖的時候也是一樣的衅码,一定要在最后釋放鎖拯刁,為了防止任何異常打斷代碼執(zhí)行,所以釋放鎖的代碼一定要放在 finally 中肆良,保證最后一定會把鎖釋放掉筛璧。

⑤獲取連接超時

上面整個獲取連接的過程②③④代碼是放在 do-while 中來執(zhí)行的,只要不超過設(shè)置的connectionTimeout惹恃,就會一直嘗試循環(huán)獲取連接夭谤,直到超過了connectionTimeout,就會執(zhí)行⑤的代碼巫糙。超時之后有兩個步驟:一是向監(jiān)控平臺上報獲取連接超時朗儒;二是構(gòu)造一個異常信息,然后拋出去参淹。

至此醉锄,整個獲取連接的邏輯就介紹完了,可能有一些沒有說到的細(xì)節(jié)浙值,大家可以發(fā)表意見恳不,我們一起學(xué)習(xí)討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末开呐,一起剝皮案震驚了整個濱河市烟勋,隨后出現(xiàn)的幾起案子规求,更是在濱河造成了極大的恐慌,老刑警劉巖卵惦,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阻肿,死亡現(xiàn)場離奇詭異,居然都是意外死亡沮尿,警方通過查閱死者的電腦和手機(jī)丛塌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來畜疾,“玉大人赴邻,你說我怎么就攤上這事》却罚” “怎么了乍楚?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長届慈。 經(jīng)常有香客問我,道長忿偷,這世上最難降的妖魔是什么金顿? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮鲤桥,結(jié)果婚禮上揍拆,老公的妹妹穿的比我還像新娘。我一直安慰自己茶凳,他們只是感情好嫂拴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著贮喧,像睡著了一般筒狠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上箱沦,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天辩恼,我揣著相機(jī)與錄音,去河邊找鬼谓形。 笑死灶伊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寒跳。 我是一名探鬼主播聘萨,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼童太!你這毒婦竟也來了米辐?” 一聲冷哼從身側(cè)響起胸完,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎儡循,沒想到半個月后舶吗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡择膝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年誓琼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡放祟,死狀恐怖巍糯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情傲隶,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布窃页,位于F島的核電站跺株,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脖卖。R本人自食惡果不足惜乒省,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望畦木。 院中可真熱鬧袖扛,春花似錦、人聲如沸十籍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勾栗。三九已至惨篱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間械姻,已是汗流浹背妒蛇。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留楷拳,地道東北人绣夺。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像欢揖,于是被迫代替她去往敵國和親陶耍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355