當前位置:
首頁 > 最新 > HikariCP源碼分析之leakDetectionThreshold及實戰解決Spark/Scala連接池泄漏

HikariCP源碼分析之leakDetectionThreshold及實戰解決Spark/Scala連接池泄漏

1.這是一個系列,有興趣的朋友可以持續關注

2.如果你有HikariCP使用上的問題,可以給我留言,我們一起溝通討論

3.希望大家可以提供我一些案例,我也希望可以支持你們做一些調優

1. 概念

2. 源碼分析

2.1.1 HikariConfig

2.1.2 HouseKeeper

2.1.3 小結

2.2.1 getConnection

2.2.2 leakTaskFactory、ProxyLeakTaskFactory、ProxyLeakTask

2.2.3 close

3. 測試模擬

4. Spark/Scala連接池泄漏問題排查

5. 參考資料

6. 系列文章

宛如清風

S.E.N.S.

00:00/00:00


概念

此屬性控制在記錄消息之前連接可能離開池的時間量,單位毫秒,默認為0,表明可能存在連接泄漏。

如果大於0且不是單元測試,則進一步判斷:(leakDetectionThreshold maxLifetime && maxLifetime > 0),會被重置為0。即如果要生效則必須>0,而且不能小於2秒,而且當maxLifetime > 0時不能大於maxLifetime(默認值1800000毫秒=30分鐘)。

leakDetectionThreshold

This property controls the amount of time that a connection can be out of the pool before a message is logged indicating a possible connection leak. A value of 0 means leak detection is disabled. Lowest acceptable value for enabling leak detection is 2000 (2 seconds). Default: 0


源碼解析

我們首先來看一下leakDetectionThreshold用在了哪裡的綱要圖:


還記得上一篇文章【追光者系列】HikariCP源碼分析之從validationTimeout來講講Hikari 2.7.5版本的那些故事提到:我們可以看到在兩處看到validationTimeout的寫入,一處是PoolBase構造函數,另一處是HouseKeeper線程。

leakDetectionThreshold的用法可以說是異曲同工,除了構造函數之外,也用了HouseKeeper線程去處理。

HikariConfig

validateNumerics方法中則是解釋了上文及官方文檔中該值validate的策略

該方法會被HikariConfig#validate所調用,而HikariConfig#validate會在HikariDataSource的specified configuration的構造函數使用到

也在每次getConnection的時候用到了,

這裡要特別提一下一個很牛逼的Double-checked_locking的實現,大家可以看一下這篇文章 https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

HouseKeeper

這裡簡要說明一下,ScheduledThreadPoolExecutor是ThreadPoolExecutor類的子類,因為繼承了ThreadPoolExecutor類所有的特性。但是,Java推薦僅在開發定時任務程序時採用ScheduledThreadPoolExecutor類。

在調用shutdown()方法而仍有待處理的任務需要執行時,可以配置ScheduledThreadPoolExecutor的行為。默認的行為是不論執行器是否結束,待處理的任務仍將被執行。但是,通過調用ScheduledThreadPoolExecutor類的setExecuteExistingDelayedTasksAfterShutdownPolicy()方法則可以改變這個行為。傳遞false參數給這個方法,執行shutdown()方法之後,待處理的任務將不會被執行。

取消任務後,判斷是否需要從阻塞隊列中移除任務。其中removeOnCancel參數通過setRemoveOnCancelPolicy()設置。之所以要在取消任務後移除阻塞隊列中任務,是為了防止隊列中積壓大量已被取消的任務

從這兩個參數配置大家可以了解到作者的對於HouseKeeper的配置初衷。

小結

Hikari通過構造函數和HouseKeeper對於一些配置參數進行初始化及動態賦值,動態賦值依賴於HikariConfigMXbean以及使用任務調度線程池ScheduledThreadPoolExecutor來不斷刷新配置的。

不允許在運行時進行改變的主要有


getConnection

leakTaskFactory、ProxyLeakTaskFactory、ProxyLeakTask

在HikariPool構造函數里,初始化了leakTaskFactory,以及houseKeepingExecutorService。

如果leakDetectionThreshold=0,即禁用連接泄露檢測,schedule返回的是ProxyLeakTask.NO_LEAK,否則則新建一個ProxyLeakTask,在leakDetectionThreshold時間後觸發

NO_LEAK類裡頭的方法都是空操作

一旦該task被觸發,則拋出Exception("Apparent connection leak detected")

我們想起了什麼,是不是想起了【追光者系列】HikariCP源碼分析之allowPoolSuspension那篇文章里有著一摸一樣的設計?

isAllowPoolSuspension默認值是false的,構造函數直接會創建SuspendResumeLock.FAUX_LOCK;只有isAllowPoolSuspension為true時,才會真正創建SuspendResumeLock。

由於Hikari的isAllowPoolSuspension默認值是false的,FAUX_LOCK只是一個空方法,acquisitionSemaphore對象也是空的;如果isAllowPoolSuspension值調整為true,當收到MBean的suspend調用時將會一次性acquisitionSemaphore.acquireUninterruptibly從此信號量獲取給定數目MAX_PERMITS 10000的許可,在提供這些許可前一直將線程阻塞。之後HikariPool的getConnection方法獲取不到連接,阻塞在suspendResumeLock.acquire(),除非resume方法釋放給定數目MAX_PERMITS 10000的許可,將其返回到信號量

close

在connection的close的時候,delegate != ClosedConnection.CLOSED_CONNECTION時會調用leakTask.cancel();取消檢測連接泄露的task。

在closeStatements中也會關閉:

在checkException中也會關閉

小結關閉任務如下圖所示:


測試模擬

當代碼執行到了quietlySleep(SECONDS.toMillis(4));時直接按照預期拋異常Apparent connection leak detected。

緊接著在close的過程中執行到了delegate != ClosedConnection.CLOSED_CONNECTION來進行leakTask.cancel()

完整的測試輸出模擬過程如下所示:


Spark/Scala連接池泄漏問題排查

金融中心大數據決策數據組的同學找到反饋了一個問題:

我們在同一個jvm 需要連接多個資料庫時,發現總體上 從連接池borrow 的 connection 多於 歸還的,一段時間後 連接池就會報出

Caused by: java.sql.SQLTransientConnectionException: HikariPool-0 - Connection is not available, request timed out after 30000ms的異常。

用戶使用的spark的場景有點特殊,單機上開的鏈接很小,但是有很多機器都會去連。用戶在一個jvm中就只會並發1個鏈接。

程序也會出現block的情況,發現是執行mysql時出現的,

mysql show processlist;發現大多停留在query end的情況,程序 thread dump 進程 持有monitor的線程。

DBA介入之後發現存在slow sql。

當然,這個問題出了是寫頻繁導致的,一次寫入的量有點大,每一個sql都巨大走的batch,寫入的 records 數在每秒 30-50條,一個record 有70多個欄位。一個解決方式是把 binlog 移到 ssd 盤;還有一個方式是innodb_flush_log_at_trx_commit把這個參數改成0了,估計可能會提高20%~30%。

修復了如上一些問題之後,又發現用戶反饋的問題,加了leakDetectionThreshold,得出的結論是存在連接泄漏(從池中借用後連接沒有關閉)。

針對這個問題,我們懷疑的連接池泄漏的點要麼在hikari中,要麼在spark/scala中。採用排除法使用了druid,依然存在這個問題;於是我們就去翻spark這塊的代碼,仔細分析之後定位到了問題:

因為scala map懶載入,一開始mapPartitions都落在一個stage中,我們調整代碼toList之後result.iterator就分在獨立的stage中,連接池泄漏問題就不再存在。

根本原因可以參見《Spark : How to use mapPartition and create/close connection per partition

》:

https://stackoverflow.com/questions/36545579/spark-how-to-use-mappartition-and-create-close-connection-per-partition/36545821#36545821

一開始以為這是一個連接池問題,或者是spark問題,但是實際上通過leakDetectionThreshold的定位,我們得知實際上這是一個scala問題 :)


參考資料

https://segmentfault.com/a/1190000013092894

系列文章

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 工匠小豬豬的技術世界 的精彩文章:

TAG:工匠小豬豬的技術世界 |