五、HikariCP源碼分析之初始化分析二

歡迎訪問我的博客,同步更新: 楓山別院

源代碼版本2.4.5-SNAPSHOT

HikariPool的初始化

在上一節(jié),我們說到了pool = fastPathPool = new HikariPool(this);中的new HikariPool(this)。我們來看下代碼:

public HikariPool(final HikariConfig config) {
  //①
  //PoolBase
  super(config);
  //②
  // 構(gòu)建一個connectionBag用于保存連接, connectionBag是連接池的核心
  this.connectionBag = new ConcurrentBag<>(this);
  //初始化連接計數(shù)器, 用于統(tǒng)計連接池中的連接數(shù)量
  this.totalConnections = new AtomicInteger();
  //根據(jù)是否允許掛起連接池, 初始化鎖
  this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
  //③
  //連接池統(tǒng)計
  if (config.getMetricsTrackerFactory() != null) {
     setMetricsTrackerFactory(config.getMetricsTrackerFactory());
  } else {
     setMetricRegistry(config.getMetricRegistry());
  }

  setHealthCheckRegistry(config.getHealthCheckRegistry());
  //注冊 JMX 相關(guān)的 bean
  registerMBeans(this);
  //④
  checkFailFast();
  //⑤
  ThreadFactory threadFactory = config.getThreadFactory();
  this.addConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
  this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());

  if (config.getScheduledExecutorService() == null) {
     threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(poolName + " housekeeper", true);
     this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
     this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
     this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);
  } else {
     this.houseKeepingExecutorService = config.getScheduledExecutorService();
  }
  //⑥
  //默認 30s 運行一次
  this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
  //⑦
  this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
}

可以看到代碼非常的長,也比較復(fù)雜,不要緊,我們慢慢分析。

①初始化父類

super(config);中的 super代表的是com.zaxxer.hikari.pool.PoolBase。PoolBase是一個更接近底層的一個連接池抽象。它里面定義了一些數(shù)據(jù)庫連接相關(guān)的配置,比如:是否自動提交事務(wù),是否連接只讀,是否使用 JDBC4,網(wǎng)絡(luò)請求超時時間等。一些比較重要的方法:初始化 JDBC 的dataSource,驗證連接是否存活,重置連接默認配置等等。調(diào)用super(config);的目的,就是初始化PoolBase中的這些數(shù)據(jù)庫配置。

通過這個super我們可以發(fā)現(xiàn),HikariCP的初始化是逐層傳遞的,假如某個子類繼承了父類,父類又繼承了它的父類,那么初始化的時候,是用同一個配置類,先傳遞到子類,再到父類,再到祖父類,每一層都使用HikariConfig來初始化跟自己相關(guān)的配置,我們可以學(xué)習(xí)這種初始化方式,非常優(yōu)雅。

具體的PoolBase初始化過程,我們不深入了,不是很復(fù)雜,大家可以結(jié)合我的代碼注釋來看一下,注釋的非常明白。

②初始化ConcurrentBag

ConcurrentBag是一個通用的池模型的容器,是整個 HikariCP 的核心,我們要單獨章節(jié)分析,此處大家只是明白這里初始化了用于保存數(shù)據(jù)庫連接的容器,它的內(nèi)部是一個CopyOnWriteArrayList,用于保存連接。

totalConnections呢,從字面就可以理解,是一個連接的計數(shù)器,用于記錄連接池中的連接數(shù)量。它的類型是AtomicInteger,關(guān)于Atomic開頭的原子類,我們在《HikariCP源碼分析之獲取連接流程一》中詳細分析過AtomicBoolean的原理,這個是差不多的,大家可以看前面的文章。totalConnections這個計數(shù)器,會在向連接池中添加新連接的時候加1,連接池中的連接被關(guān)閉之后會減 1。

suspendResumeLock是我們在《HikariCP源碼分析之獲取連接流程二》中分析的重點,此處不贅述了。這里是創(chuàng)建一個連接池掛起的鎖,或者說令牌桶,用于連接池掛起的時候,控制用戶不能從連接池獲取連接的。如果用戶沒有開啟連接池掛起功能,就創(chuàng)建一個空的鎖實現(xiàn)FAUX_LOCK,方便 JIT 將它優(yōu)化掉。

③監(jiān)控初始化

我們在之前的獲取連接的分析文章中提到過,獲取連接的時候,會向監(jiān)控平臺上報自己的狀態(tài),這里就是初始化監(jiān)控平臺的相關(guān)配置。用戶可以自定義監(jiān)控平臺的實現(xiàn),將它注冊到 HikariCP 中,就可以被 HikariCP 調(diào)用。

值得一提的是registerMBeans(this);這一句代碼。這里是注冊 JMX 相關(guān)的 MBean,只有配置了數(shù)據(jù)庫的isRegisterMbeans配置項,HikariCP 才會注冊MBean,我們才能使用 JMX 在運行期間修改連接池的配置。如果不配置isRegisterMbeans,那么使用 JMX 修改配置會報錯。對 JMX 感興趣的同學(xué),可以自行學(xué)習(xí)下相關(guān)內(nèi)容。

④快速失敗

這里只有一行代碼checkFailFast();,但是我們單獨拿出來了,這說明這里有點意思。

直接看看代碼:

private void checkFailFast() {
  if (config.isInitializationFailFast()) {
     try {
        newConnection().close();
     } catch (Throwable e) {
        try {
           shutdown();
        } catch (Throwable ex) {
           e.addSuppressed(ex);
        }
        throw new PoolInitializationException(e);
     }
  }
}

代碼看著不少,其實關(guān)鍵的沒有多少。isInitializationFailFast是一個 HikariCP的配置項,它的默認值是 true。老規(guī)矩,先從字面意思猜測一下,好像是:初始化的時候快速失敗的意思。再看一下下面的代碼newConnection().close();,這是創(chuàng)建了一個連接,然后立即關(guān)閉了呀!綜合以上線索,這是什么意思?其實非常好理解。就是在初始化 HikariCP 的時候,建立一個連接,然后立即關(guān)閉,如果有報錯建立不了,就關(guān)閉整個連接池,拋錯。

目的就是在啟動期間,創(chuàng)建連接來驗證關(guān)鍵參數(shù)是否有錯誤,如果不能建立連接,立即拋出錯誤,方便用戶及時發(fā)現(xiàn)問題。比如:我們的數(shù)據(jù)庫密碼寫錯了。如果沒有這個立即失敗的驗證,等你上線部署成功之后,第一次獲取連接才能發(fā)現(xiàn)問題,這不就悲催了嘛,搞不好要挨罵的。

⑤初始化線程池

HikariCP 中有幾個線程池:

  • closeConnectionExecutor :用于執(zhí)行關(guān)閉底層連接的線程池,只有一個線程,線程任務(wù)隊列最大是連接池最大連接數(shù),超出隊列的任務(wù),會不斷重試添加。

  • addConnectionExecutor:用于執(zhí)行添加新連接的線程池,只有一個線程,線程任務(wù)隊列最大是連接池最大連接數(shù),超出隊列的任務(wù),直接拋棄。

  • houseKeepingExecutorService:這是一個定時線程池,默認只有一個線程,它的作用比較多:用于執(zhí)行檢測連接泄露、關(guān)閉生存時間到期的連接、回收空閑連接、檢測時間回撥。

closeConnectionExecutor的隊列任務(wù)拋棄策略有點不一樣,它會不斷重試,是基于連接必須關(guān)閉的考慮,其他的任務(wù)直接拋棄是影響不大。

這里有兩項配置可以影響線程池,一個是scheduledExecutor:用于提供給houseKeepingExecutorService用的線程池,如果用戶不自定義,就使用默認的 1 個線程的線程池。另一個是threadFactory:用于生成線程池中的線程,HikariCP 會在生成線程池的時候,調(diào)用該線程工廠獲取線程。

⑥啟動連接管理任務(wù)

看代碼:

this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);

這里向houseKeepingExecutorService線程池里提交了一個任務(wù):每隔 30 秒,就執(zhí)行一次HouseKeeper任務(wù)。這個任務(wù)的功能主要是:檢測時間回撥,調(diào)整連接池里的連接。什么是時間回撥?比如服務(wù)器的系統(tǒng)時間不準,后來用戶修改了服務(wù)器的系統(tǒng)時間,因為 HikariCP 是對時間敏感的框架,它靠定時任務(wù)來管理連接,如果系統(tǒng)時間變了,那么定時任務(wù)就不準確了。

有兩種情況:

  • 一是用戶調(diào)快了時間,這個時候,HikariCP 什么都不做,因為時間快了,只是加快了定時任務(wù)的執(zhí)行,使連接更早過期,這個對連接池影響不大,因為連接池會自動添加新連接。

  • 二是用戶調(diào)慢了時間,也就是回退了時間?;赝藭r間對 HikariCP 是有極大影響的,比如原來還差 1 秒執(zhí)行的任務(wù),現(xiàn)在可能要過 15秒之后才能執(zhí)行了,這可能引發(fā)本來該存活時間到期的連接,不會過期了。所以,這個時候,HikariCP 會把連接池中所有的連接都軟驅(qū)逐掉,使所有的連接都不可用,然后重新創(chuàng)建新連接。

由于HouseKeeper任務(wù)比較復(fù)雜,我們單獨的章節(jié)分析。

⑦創(chuàng)建連接泄露檢測任務(wù)的父任務(wù)

看代碼:

this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);

我們在《HikariCP源碼分析之獲取連接流程三》中分析連接泄露檢測時候,提到過,用戶獲取到每個連接的時候,都會為該連接創(chuàng)建一個連接泄露檢測的定時任務(wù),在指定的時間內(nèi),拋出連接泄露警告。

在創(chuàng)建連接泄露檢測任務(wù)的時候,會使用一個父任務(wù)的參數(shù),從這個父任務(wù)中拿連接泄露的最大時間和用于執(zhí)行任務(wù)的線程池,然后使用這兩個參數(shù)創(chuàng)建任務(wù)。這個父任務(wù),就是在這里創(chuàng)建的,創(chuàng)建的時候就是傳了這兩個參數(shù):連接泄露的最大時間和用于執(zhí)行任務(wù)的線程池。

至此,HikariDataSource初始化就分析完成了。大家有任何問題,可以提出來,我們一起討論學(xué)習(xí)。

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

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