歡迎訪問(wèn)我的博客,同步更新: 楓山別院
源代碼版本2.4.5-SNAPSHOT
HikariPool的初始化
在上一節(jié),我們說(shuō)到了pool = fastPathPool = new HikariPool(this);中的new HikariPool(this)。我們來(lái)看下代碼:
public HikariPool(final HikariConfig config) {
//①
//PoolBase
super(config);
//②
// 構(gòu)建一個(gè)connectionBag用于保存連接, connectionBag是連接池的核心
this.connectionBag = new ConcurrentBag<>(this);
//初始化連接計(jì)數(shù)器, 用于統(tǒng)計(jì)連接池中的連接數(shù)量
this.totalConnections = new AtomicInteger();
//根據(jù)是否允許掛起連接池, 初始化鎖
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
//③
//連接池統(tǒng)計(jì)
if (config.getMetricsTrackerFactory() != null) {
setMetricsTrackerFactory(config.getMetricsTrackerFactory());
} else {
setMetricRegistry(config.getMetricRegistry());
}
setHealthCheckRegistry(config.getHealthCheckRegistry());
//注冊(cè) 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();
}
//⑥
//默認(rèn) 30s 運(yùn)行一次
this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
//⑦
this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
}
可以看到代碼非常的長(zhǎng),也比較復(fù)雜,不要緊,我們慢慢分析。
①初始化父類
super(config);中的 super代表的是com.zaxxer.hikari.pool.PoolBase。PoolBase是一個(gè)更接近底層的一個(gè)連接池抽象。它里面定義了一些數(shù)據(jù)庫(kù)連接相關(guān)的配置,比如:是否自動(dòng)提交事務(wù),是否連接只讀,是否使用 JDBC4,網(wǎng)絡(luò)請(qǐng)求超時(shí)時(shí)間等。一些比較重要的方法:初始化 JDBC 的dataSource,驗(yàn)證連接是否存活,重置連接默認(rèn)配置等等。調(diào)用super(config);的目的,就是初始化PoolBase中的這些數(shù)據(jù)庫(kù)配置。
通過(guò)這個(gè)super我們可以發(fā)現(xiàn),HikariCP的初始化是逐層傳遞的,假如某個(gè)子類繼承了父類,父類又繼承了它的父類,那么初始化的時(shí)候,是用同一個(gè)配置類,先傳遞到子類,再到父類,再到祖父類,每一層都使用HikariConfig來(lái)初始化跟自己相關(guān)的配置,我們可以學(xué)習(xí)這種初始化方式,非常優(yōu)雅。
具體的PoolBase初始化過(guò)程,我們不深入了,不是很復(fù)雜,大家可以結(jié)合我的代碼注釋來(lái)看一下,注釋的非常明白。
②初始化ConcurrentBag
ConcurrentBag是一個(gè)通用的池模型的容器,是整個(gè) HikariCP 的核心,我們要單獨(dú)章節(jié)分析,此處大家只是明白這里初始化了用于保存數(shù)據(jù)庫(kù)連接的容器,它的內(nèi)部是一個(gè)CopyOnWriteArrayList,用于保存連接。
totalConnections呢,從字面就可以理解,是一個(gè)連接的計(jì)數(shù)器,用于記錄連接池中的連接數(shù)量。它的類型是AtomicInteger,關(guān)于Atomic開(kāi)頭的原子類,我們?cè)凇禜ikariCP源碼分析之獲取連接流程一》中詳細(xì)分析過(guò)AtomicBoolean的原理,這個(gè)是差不多的,大家可以看前面的文章。totalConnections這個(gè)計(jì)數(shù)器,會(huì)在向連接池中添加新連接的時(shí)候加1,連接池中的連接被關(guān)閉之后會(huì)減 1。
suspendResumeLock是我們?cè)凇禜ikariCP源碼分析之獲取連接流程二》中分析的重點(diǎn),此處不贅述了。這里是創(chuàng)建一個(gè)連接池掛起的鎖,或者說(shuō)令牌桶,用于連接池掛起的時(shí)候,控制用戶不能從連接池獲取連接的。如果用戶沒(méi)有開(kāi)啟連接池掛起功能,就創(chuàng)建一個(gè)空的鎖實(shí)現(xiàn)FAUX_LOCK,方便 JIT 將它優(yōu)化掉。
③監(jiān)控初始化
我們?cè)谥暗墨@取連接的分析文章中提到過(guò),獲取連接的時(shí)候,會(huì)向監(jiān)控平臺(tái)上報(bào)自己的狀態(tài),這里就是初始化監(jiān)控平臺(tái)的相關(guān)配置。用戶可以自定義監(jiān)控平臺(tái)的實(shí)現(xiàn),將它注冊(cè)到 HikariCP 中,就可以被 HikariCP 調(diào)用。
值得一提的是registerMBeans(this);這一句代碼。這里是注冊(cè) JMX 相關(guān)的 MBean,只有配置了數(shù)據(jù)庫(kù)的isRegisterMbeans配置項(xiàng),HikariCP 才會(huì)注冊(cè)MBean,我們才能使用 JMX 在運(yùn)行期間修改連接池的配置。如果不配置isRegisterMbeans,那么使用 JMX 修改配置會(huì)報(bào)錯(cuò)。對(duì) JMX 感興趣的同學(xué),可以自行學(xué)習(xí)下相關(guān)內(nèi)容。
④快速失敗
這里只有一行代碼checkFailFast();,但是我們單獨(dú)拿出來(lái)了,這說(shuō)明這里有點(diǎn)意思。
直接看看代碼:
private void checkFailFast() {
if (config.isInitializationFailFast()) {
try {
newConnection().close();
} catch (Throwable e) {
try {
shutdown();
} catch (Throwable ex) {
e.addSuppressed(ex);
}
throw new PoolInitializationException(e);
}
}
}
代碼看著不少,其實(shí)關(guān)鍵的沒(méi)有多少。isInitializationFailFast是一個(gè) HikariCP的配置項(xiàng),它的默認(rèn)值是 true。老規(guī)矩,先從字面意思猜測(cè)一下,好像是:初始化的時(shí)候快速失敗的意思。再看一下下面的代碼newConnection().close();,這是創(chuàng)建了一個(gè)連接,然后立即關(guān)閉了呀!綜合以上線索,這是什么意思?其實(shí)非常好理解。就是在初始化 HikariCP 的時(shí)候,建立一個(gè)連接,然后立即關(guān)閉,如果有報(bào)錯(cuò)建立不了,就關(guān)閉整個(gè)連接池,拋錯(cuò)。
目的就是在啟動(dòng)期間,創(chuàng)建連接來(lái)驗(yàn)證關(guān)鍵參數(shù)是否有錯(cuò)誤,如果不能建立連接,立即拋出錯(cuò)誤,方便用戶及時(shí)發(fā)現(xiàn)問(wèn)題。比如:我們的數(shù)據(jù)庫(kù)密碼寫(xiě)錯(cuò)了。如果沒(méi)有這個(gè)立即失敗的驗(yàn)證,等你上線部署成功之后,第一次獲取連接才能發(fā)現(xiàn)問(wèn)題,這不就悲催了嘛,搞不好要挨罵的。
⑤初始化線程池
HikariCP 中有幾個(gè)線程池:
closeConnectionExecutor :用于執(zhí)行關(guān)閉底層連接的線程池,只有一個(gè)線程,線程任務(wù)隊(duì)列最大是連接池最大連接數(shù),超出隊(duì)列的任務(wù),會(huì)不斷重試添加。
addConnectionExecutor:用于執(zhí)行添加新連接的線程池,只有一個(gè)線程,線程任務(wù)隊(duì)列最大是連接池最大連接數(shù),超出隊(duì)列的任務(wù),直接拋棄。
houseKeepingExecutorService:這是一個(gè)定時(shí)線程池,默認(rèn)只有一個(gè)線程,它的作用比較多:用于執(zhí)行檢測(cè)連接泄露、關(guān)閉生存時(shí)間到期的連接、回收空閑連接、檢測(cè)時(shí)間回?fù)堋?/p>
closeConnectionExecutor的隊(duì)列任務(wù)拋棄策略有點(diǎn)不一樣,它會(huì)不斷重試,是基于連接必須關(guān)閉的考慮,其他的任務(wù)直接拋棄是影響不大。
這里有兩項(xiàng)配置可以影響線程池,一個(gè)是scheduledExecutor:用于提供給houseKeepingExecutorService用的線程池,如果用戶不自定義,就使用默認(rèn)的 1 個(gè)線程的線程池。另一個(gè)是threadFactory:用于生成線程池中的線程,HikariCP 會(huì)在生成線程池的時(shí)候,調(diào)用該線程工廠獲取線程。
⑥啟動(dòng)連接管理任務(wù)
看代碼:
this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
這里向houseKeepingExecutorService線程池里提交了一個(gè)任務(wù):每隔 30 秒,就執(zhí)行一次HouseKeeper任務(wù)。這個(gè)任務(wù)的功能主要是:檢測(cè)時(shí)間回?fù)?,調(diào)整連接池里的連接。什么是時(shí)間回?fù)??比如服?wù)器的系統(tǒng)時(shí)間不準(zhǔn),后來(lái)用戶修改了服務(wù)器的系統(tǒng)時(shí)間,因?yàn)?HikariCP 是對(duì)時(shí)間敏感的框架,它靠定時(shí)任務(wù)來(lái)管理連接,如果系統(tǒng)時(shí)間變了,那么定時(shí)任務(wù)就不準(zhǔn)確了。
有兩種情況:
一是用戶調(diào)快了時(shí)間,這個(gè)時(shí)候,HikariCP 什么都不做,因?yàn)闀r(shí)間快了,只是加快了定時(shí)任務(wù)的執(zhí)行,使連接更早過(guò)期,這個(gè)對(duì)連接池影響不大,因?yàn)檫B接池會(huì)自動(dòng)添加新連接。
二是用戶調(diào)慢了時(shí)間,也就是回退了時(shí)間?;赝藭r(shí)間對(duì) HikariCP 是有極大影響的,比如原來(lái)還差 1 秒執(zhí)行的任務(wù),現(xiàn)在可能要過(guò) 15秒之后才能執(zhí)行了,這可能引發(fā)本來(lái)該存活時(shí)間到期的連接,不會(huì)過(guò)期了。所以,這個(gè)時(shí)候,HikariCP 會(huì)把連接池中所有的連接都軟驅(qū)逐掉,使所有的連接都不可用,然后重新創(chuàng)建新連接。
由于HouseKeeper任務(wù)比較復(fù)雜,我們單獨(dú)的章節(jié)分析。
⑦創(chuàng)建連接泄露檢測(cè)任務(wù)的父任務(wù)
看代碼:
this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
我們?cè)凇禜ikariCP源碼分析之獲取連接流程三》中分析連接泄露檢測(cè)時(shí)候,提到過(guò),用戶獲取到每個(gè)連接的時(shí)候,都會(huì)為該連接創(chuàng)建一個(gè)連接泄露檢測(cè)的定時(shí)任務(wù),在指定的時(shí)間內(nèi),拋出連接泄露警告。
在創(chuàng)建連接泄露檢測(cè)任務(wù)的時(shí)候,會(huì)使用一個(gè)父任務(wù)的參數(shù),從這個(gè)父任務(wù)中拿連接泄露的最大時(shí)間和用于執(zhí)行任務(wù)的線程池,然后使用這兩個(gè)參數(shù)創(chuàng)建任務(wù)。這個(gè)父任務(wù),就是在這里創(chuàng)建的,創(chuàng)建的時(shí)候就是傳了這兩個(gè)參數(shù):連接泄露的最大時(shí)間和用于執(zhí)行任務(wù)的線程池。
至此,HikariDataSource初始化就分析完成了。大家有任何問(wèn)題,可以提出來(lái),我們一起討論學(xué)習(xí)。