前言
現(xiàn)在已經(jīng)有很多公司在使用HikariCP了,HikariCP還成為了SpringBoot默認(rèn)的連接池扳肛,伴隨著SpringBoot和微服務(wù)傻挂,HikariCP 必將迎來廣泛的普及。
下面陳某帶大家從源碼角度分析一下HikariCP為什么能夠被Spring Boot 青睞挖息,文章目錄如下:
零金拒、類圖和流程圖
開始前先來了解下HikariCP獲取一個(gè)連接時(shí)類間的交互流程,方便下面詳細(xì)流程的閱讀套腹。
獲取連接時(shí)的類間交互:
一绪抛、主流程1:獲取連接流程
HikariCP獲取連接時(shí)的入口是HikariDataSource
里的getConnection
方法,現(xiàn)在來看下該方法的具體流程:
上述為HikariCP獲取連接時(shí)的流程圖电禀,由圖1可知幢码,每個(gè)datasource
對(duì)象里都會(huì)持有一個(gè)HikariPool
對(duì)象,記為pool尖飞,初始化后的datasource對(duì)象pool是空的症副,所以第一次getConnection
的時(shí)候會(huì)進(jìn)行實(shí)例化pool
屬性(參考主流程1
),初始化的時(shí)候需要將當(dāng)前datasource里的config屬性
傳過去政基,用于pool的初始化贞铣,最終標(biāo)記sealed
,然后根據(jù)pool對(duì)象調(diào)用getConnection
方法(參考流程1.1
)沮明,獲取成功后返回連接對(duì)象辕坝。
二、主流程2:初始化池對(duì)象
該流程用于初始化整個(gè)連接池
荐健,這個(gè)流程會(huì)給連接池內(nèi)所有的屬性做初始化的工作酱畅,其中比較主要的幾個(gè)流程上圖已經(jīng)指出,簡(jiǎn)單概括一下:
- 利用
config
初始化各種連接池屬性江场,并且產(chǎn)生一個(gè)用于生產(chǎn)物理連接
的數(shù)據(jù)源DriverDataSource
- 初始化存放連接對(duì)象的核心類
connectionBag
- 初始化一個(gè)延時(shí)任務(wù)線程池類型的對(duì)象
houseKeepingExecutorService
纺酸,用于后續(xù)執(zhí)行一些延時(shí)/定時(shí)類任務(wù)(比如連接泄漏檢查延時(shí)任務(wù),參考流程2.2
以及主流程4
扛稽,除此之外maxLifeTime
后主動(dòng)回收關(guān)閉連接也是交由該對(duì)象來執(zhí)行的吁峻,這個(gè)過程可以參考主流程3
) - 預(yù)熱連接池,HikariCP會(huì)在該流程的
checkFailFast
里初始化好一個(gè)連接對(duì)象放進(jìn)池子內(nèi)在张,當(dāng)然觸發(fā)該流程得保證initializationTimeout > 0
時(shí)(默認(rèn)值1)用含,這個(gè)配置屬性表示留給預(yù)熱操作的時(shí)間(默認(rèn)值1在預(yù)熱失敗時(shí)不會(huì)發(fā)生重試)。與Druid
通過initialSize
控制預(yù)熱連接對(duì)象數(shù)不一樣的是帮匾,HikariCP僅預(yù)熱進(jìn)池一個(gè)連接對(duì)象啄骇。 - 初始化一個(gè)線程池對(duì)象
addConnectionExecutor
,用于后續(xù)擴(kuò)充連接對(duì)象 - 初始化一個(gè)線程池對(duì)象
closeConnectionExecutor
瘟斜,用于關(guān)閉一些連接對(duì)象缸夹,怎么觸發(fā)關(guān)閉任務(wù)呢痪寻?可以參考流程1.1.2
三、流程1.1:通過HikariPool獲取連接對(duì)象
從最開始的結(jié)構(gòu)圖可知虽惭,每個(gè)HikariPool
里都維護(hù)一個(gè)ConcurrentBag
對(duì)象橡类,用于存放連接對(duì)象,由上圖可以看到芽唇,實(shí)際上HikariPool
的getConnection
就是從ConcurrentBag
里獲取連接的(調(diào)用其borrow
方法獲得顾画,對(duì)應(yīng)ConnectionBag主流程
),在長(zhǎng)連接檢查這塊匆笤,與之前說的Druid
不同研侣,這里的長(zhǎng)連接判活檢查在連接對(duì)象沒有被標(biāo)記為“已丟棄
”時(shí),只要距離上次使用超過500ms
每次取出都會(huì)進(jìn)行檢查(500ms是默認(rèn)值炮捧,可通過配置com.zaxxer.hikari.aliveBypassWindowMs
的系統(tǒng)參數(shù)來控制)庶诡,emmmm,也就是說HikariCP
對(duì)長(zhǎng)連接的活性檢查很頻繁咆课,但是其并發(fā)性能依舊優(yōu)于Druid
末誓,說明頻繁的長(zhǎng)連接檢查并不是導(dǎo)致連接池性能高低的關(guān)鍵所在。
這個(gè)其實(shí)是由于HikariCP的無鎖
實(shí)現(xiàn)傀蚌,在高并發(fā)時(shí)對(duì)CPU的負(fù)載沒有其他連接池那么高而產(chǎn)生的并發(fā)性能差異基显,后面會(huì)說HikariCP的具體做法,即使是Druid
善炫,在獲取連接
、生成連接
库继、歸還連接
時(shí)都進(jìn)行了鎖控制
箩艺,因?yàn)橥ㄟ^上篇解析Druid
的文章可以知道,Druid
里的連接池資源是多線程共享的宪萄,不可避免的會(huì)有鎖競(jìng)爭(zhēng)艺谆,有鎖競(jìng)爭(zhēng)意味著線程狀態(tài)的變化會(huì)很頻繁,線程狀態(tài)變化頻繁意味著CPU上下文切換也將會(huì)很頻繁拜英。
回到流程1.1
静汤,如果拿到的連接為空,直接報(bào)錯(cuò)居凶,不為空則進(jìn)行相應(yīng)的檢查虫给,如果檢查通過,則包裝成ConnectionProxy
對(duì)象返回給業(yè)務(wù)方侠碧,不通過則調(diào)用closeConnection
方法關(guān)閉連接(對(duì)應(yīng)流程1.1.2
抹估,該流程會(huì)觸發(fā)ConcurrentBag
的remove
方法丟棄該連接,然后把實(shí)際的驅(qū)動(dòng)連接交給closeConnectionExecutor
線程池弄兜,異步關(guān)閉驅(qū)動(dòng)連接)药蜻。
四瓷式、流程1.1.1:連接判活
承接上面的流程1.1
里的判活流程,來看下判活是如何做的语泽,首先說驗(yàn)證方法(注意這里該方法接受的這個(gè)connection
對(duì)象不是poolEntry
贸典,而是poolEntry
持有的實(shí)際驅(qū)動(dòng)的連接對(duì)象),在之前介紹Druid的時(shí)候就知道踱卵,Druid是根據(jù)驅(qū)動(dòng)程序里是否存在ping方法
來判斷是否啟用ping的方式判斷連接是否存活廊驼,但是到了HikariCP則更加簡(jiǎn)單粗暴,僅根據(jù)是否配置了connectionTestQuery
覺定是否啟用ping:
this.isUseJdbc4Validation = config.getConnectionTestQuery() == null;
所以一般驅(qū)動(dòng)如果不是特別低的版本颊埃,不建議配置該項(xiàng)蔬充,否則便會(huì)走createStatement+excute
的方式,相比ping
簡(jiǎn)單發(fā)送心跳數(shù)據(jù)班利,這種方式顯然更低效饥漫。
此外,這里在剛進(jìn)來還會(huì)通過驅(qū)動(dòng)的連接對(duì)象重新給它設(shè)置一遍networkTimeout
的值罗标,使之變成validationTimeout
庸队,表示一次驗(yàn)證的超時(shí)時(shí)間,為啥這里要重新設(shè)置這個(gè)屬性呢闯割?因?yàn)樵谑褂胮ing方法校驗(yàn)時(shí)彻消,是沒辦法通過類似statement
那樣可以setQueryTimeout
的,所以只能由網(wǎng)絡(luò)通信的超時(shí)時(shí)間來控制宙拉,這個(gè)時(shí)間可以通過jdbc
的連接參數(shù)socketTimeout
來控制:
jdbc:mysql://127.0.0.1:3306/xxx?socketTimeout=250
這個(gè)值最終會(huì)被賦值給HikariCP的networkTimeout
字段宾尚,這就是為什么最后那一步使用這個(gè)字段來還原驅(qū)動(dòng)連接超時(shí)屬性的原因;說到這里谢澈,最后那里為啥要再次還原呢煌贴?這就很容易理解了,因?yàn)轵?yàn)證結(jié)束了锥忿,連接對(duì)象還存活的情況下牛郑,它的networkTimeout
的值這時(shí)仍然等于validationTimeout
(不合預(yù)期),顯然在拿出去用之前敬鬓,需要恢復(fù)成本來的值淹朋,也就是HikariCP里的networkTimeout
屬性。
五钉答、流程1.1.2:關(guān)閉連接對(duì)象
這個(gè)流程簡(jiǎn)單來說就是把流程1.1.1
中驗(yàn)證不通過的死連接础芍,主動(dòng)關(guān)閉的一個(gè)流程,首先會(huì)把這個(gè)連接對(duì)象從ConnectionBag
里移除
希痴,然后把實(shí)際的物理連接交給一個(gè)線程池去異步執(zhí)行者甲,這個(gè)線程池就是在主流程2
里初始化池的時(shí)候初始化的線程池closeConnectionExecutor
,然后異步任務(wù)內(nèi)開始實(shí)際的關(guān)連接操作砌创,因?yàn)橹鲃?dòng)關(guān)閉了一個(gè)連接相當(dāng)于少了一個(gè)連接虏缸,所以還會(huì)觸發(fā)一次擴(kuò)充連接池(參考主流程5
)操作鲫懒。
六、流程2.1:HikariCP監(jiān)控設(shè)置
不同于Druid那樣監(jiān)控指標(biāo)那么多刽辙,HikariCP會(huì)把我們非常關(guān)心的幾項(xiàng)指標(biāo)暴露給我們窥岩,比如當(dāng)前連接池內(nèi)閑置連接數(shù)、總連接數(shù)宰缤、一個(gè)連接被用了多久歸還颂翼、創(chuàng)建一個(gè)物理連接花費(fèi)多久等,HikariCP的連接池的監(jiān)控我們這一節(jié)專門詳細(xì)的分解一下慨灭,首先找到HikariCP下面的metrics
文件夾朦乏,這下面放置了一些規(guī)范實(shí)現(xiàn)的監(jiān)控接口等,還有一些現(xiàn)成的實(shí)現(xiàn)(比如HikariCP自帶對(duì)prometheus
氧骤、micrometer
呻疹、dropwizard
的支持,不太了解后面兩個(gè)筹陵,prometheus
下文直接稱為普羅米修斯
):
下面刽锤,來著重看下接口的定義:
//這個(gè)接口的實(shí)現(xiàn)主要負(fù)責(zé)收集一些動(dòng)作的耗時(shí)
public interface IMetricsTracker extends AutoCloseable {
//這個(gè)方法觸發(fā)點(diǎn)在創(chuàng)建實(shí)際的物理連接時(shí)(主流程3),用于記錄一個(gè)實(shí)際的物理連接創(chuàng)建所耗費(fèi)的時(shí)間
default void recordConnectionCreatedMillis(long connectionCreatedMillis) {}
//這個(gè)方法觸發(fā)點(diǎn)在getConnection時(shí)(主流程1)朦佩,用于記錄獲取一個(gè)連接時(shí)實(shí)際的耗時(shí)
default void recordConnectionAcquiredNanos(final long elapsedAcquiredNanos) {}
//這個(gè)方法觸發(fā)點(diǎn)在回收連接時(shí)(主流程6)并思,用于記錄一個(gè)連接從被獲取到被回收時(shí)所消耗的時(shí)間
default void recordConnectionUsageMillis(final long elapsedBorrowedMillis) {}
//這個(gè)方法觸發(fā)點(diǎn)也在getConnection時(shí)(主流程1),用于記錄獲取連接超時(shí)的次數(shù)语稠,每發(fā)生一次獲取連接超時(shí)宋彼,就會(huì)觸發(fā)一次該方法的調(diào)用
default void recordConnectionTimeout() {}
@Override
default void close() {}
}
觸發(fā)點(diǎn)都了解清楚后,再來看看MetricsTrackerFactory
的接口定義:
//用于創(chuàng)建IMetricsTracker實(shí)例仙畦,并且按需記錄PoolStats對(duì)象里的屬性(這個(gè)對(duì)象里的屬性就是類似連接池當(dāng)前閑置連接數(shù)之類的線程池狀態(tài)類指標(biāo))
public interface MetricsTrackerFactory {
//返回一個(gè)IMetricsTracker對(duì)象宙暇,并且把PoolStats傳了過去
IMetricsTracker create(String poolName, PoolStats poolStats);
}
上面的接口用法見注釋,針對(duì)新出現(xiàn)的PoolStats
類议泵,我們來看看它做了什么:
public abstract class PoolStats {
private final AtomicLong reloadAt; //觸發(fā)下次刷新的時(shí)間(時(shí)間戳)
private final long timeoutMs; //刷新下面的各項(xiàng)屬性值的頻率,默認(rèn)1s桃熄,無法改變
// 總連接數(shù)
protected volatile int totalConnections;
// 閑置連接數(shù)
protected volatile int idleConnections;
// 活動(dòng)連接數(shù)
protected volatile int activeConnections;
// 由于無法獲取到可用連接而阻塞的業(yè)務(wù)線程數(shù)
protected volatile int pendingThreads;
// 最大連接數(shù)
protected volatile int maxConnections;
// 最小連接數(shù)
protected volatile int minConnections;
public PoolStats(final long timeoutMs) {
this.timeoutMs = timeoutMs;
this.reloadAt = new AtomicLong();
}
//這里以獲取最大連接數(shù)為例先口,其他的跟這個(gè)差不多
public int getMaxConnections() {
if (shouldLoad()) { //是否應(yīng)該刷新
update(); //刷新屬性值,注意這個(gè)update的實(shí)現(xiàn)在HikariPool里瞳收,因?yàn)檫@些屬性值的直接或間接來源都是HikariPool
}
return maxConnections;
}
protected abstract void update(); //實(shí)現(xiàn)在↑上面已經(jīng)說了
private boolean shouldLoad() { //按照更新頻率來決定是否刷新屬性值
for (; ; ) {
final long now = currentTime();
final long reloadTime = reloadAt.get();
if (reloadTime > now) {
return false;
} else if (reloadAt.compareAndSet(reloadTime, plusMillis(now, timeoutMs))) {
return true;
}
}
}
}
實(shí)際上這里就是這些屬性獲取和觸發(fā)刷新的地方碉京,那么這個(gè)對(duì)象是在哪里被生成并且丟給MetricsTrackerFactory
的create
方法的呢?這就是本節(jié)所需要講述的要點(diǎn):主流程2
里的設(shè)置監(jiān)控器的流程螟深,來看看那里發(fā)生了什么事吧:
//監(jiān)控器設(shè)置方法(此方法在HikariPool中谐宙,metricsTracker屬性就是HikariPool用來觸發(fā)IMetricsTracker里方法調(diào)用的)
public void setMetricsTrackerFactory(MetricsTrackerFactory metricsTrackerFactory) {
if (metricsTrackerFactory != null) {
//MetricsTrackerDelegate是包裝類,是HikariPool的一個(gè)靜態(tài)內(nèi)部類界弧,是實(shí)際持有IMetricsTracker對(duì)象的類凡蜻,也是實(shí)際觸發(fā)IMetricsTracker里方法調(diào)用的類
//這里首先會(huì)觸發(fā)MetricsTrackerFactory類的create方法拿到IMetricsTracker對(duì)象搭综,然后利用getPoolStats初始化PoolStat對(duì)象,然后也一并傳給MetricsTrackerFactory
this.metricsTracker = new MetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(), getPoolStats()));
} else {
//不啟用監(jiān)控划栓,直接等于一個(gè)沒有實(shí)現(xiàn)方法的空類
this.metricsTracker = new NopMetricsTrackerDelegate();
}
}
private PoolStats getPoolStats() {
//初始化PoolStats對(duì)象兑巾,并且規(guī)定1s觸發(fā)一次屬性值刷新的update方法
return new PoolStats(SECONDS.toMillis(1)) {
@Override
protected void update() {
//實(shí)現(xiàn)了PoolStat的update方法,刷新各個(gè)屬性的值
this.pendingThreads = HikariPool.this.getThreadsAwaitingConnection();
this.idleConnections = HikariPool.this.getIdleConnections();
this.totalConnections = HikariPool.this.getTotalConnections();
this.activeConnections = HikariPool.this.getActiveConnections();
this.maxConnections = config.getMaximumPoolSize();
this.minConnections = config.getMinimumIdle();
}
};
}
到這里HikariCP的監(jiān)控器就算是注冊(cè)進(jìn)去了忠荞,所以要想實(shí)現(xiàn)自己的監(jiān)控器拿到上面的指標(biāo)蒋歌,要經(jīng)過如下步驟:
- 新建一個(gè)類實(shí)現(xiàn)
IMetricsTracker
接口,我們這里將該類記為IMetricsTrackerImpl
- 新建一個(gè)類實(shí)現(xiàn)
MetricsTrackerFactory
接口委煤,我們這里將該類記為MetricsTrackerFactoryImpl
堂油,并且將上面的IMetricsTrackerImpl
在其create方法
內(nèi)實(shí)例化 - 將
MetricsTrackerFactoryImpl
實(shí)例化后調(diào)用HikariPool的setMetricsTrackerFactory
方法注冊(cè)到Hikari連接池。
上面沒有提到PoolStats
里的屬性怎么監(jiān)控碧绞,這里來說下府框,由于create方法
是調(diào)用一次就沒了,create方法
只是接收了PoolStats
對(duì)象的實(shí)例头遭,如果不處理寓免,那么隨著create調(diào)用的結(jié)束,這個(gè)實(shí)例針對(duì)監(jiān)控模塊來說就失去持有了计维,所以這里如果想要拿到PoolStats
里的屬性袜香,就需要開啟一個(gè)守護(hù)線程
,讓其持有PoolStats
對(duì)象實(shí)例鲫惶,并且定時(shí)獲取其內(nèi)部屬性值蜈首,然后push
給監(jiān)控系統(tǒng),如果是普羅米修斯等使用pull方式
獲取監(jiān)控?cái)?shù)據(jù)的監(jiān)控系統(tǒng)欠母,可以效仿HikariCP原生普羅米修斯監(jiān)控的實(shí)現(xiàn)欢策,自定義一個(gè)Collector
對(duì)象來接收PoolStats
實(shí)例,這樣普羅米修斯就可以定期拉取了赏淌,比如HikariCP根據(jù)普羅米修斯監(jiān)控系統(tǒng)自己定義的MetricsTrackerFactory
實(shí)現(xiàn)(對(duì)應(yīng)圖2
里的PrometheusMetricsTrackerFactory
類):
@Override
public IMetricsTracker create(String poolName, PoolStats poolStats) {
getCollector().add(poolName, poolStats); //將接收到的PoolStats對(duì)象直接交給Collector踩寇,這樣普羅米修斯服務(wù)端每觸發(fā)一次采集接口的調(diào)用,PoolStats都會(huì)跟著執(zhí)行一遍內(nèi)部屬性獲取流程
return new PrometheusMetricsTracker(poolName, this.collectorRegistry); //返回IMetricsTracker接口的實(shí)現(xiàn)類
}
//自定義的Collector
private HikariCPCollector getCollector() {
if (collector == null) {
//注冊(cè)到普羅米修斯收集中心
collector = new HikariCPCollector().register(this.collectorRegistry);
}
return collector;
通過上面的解釋可以知道在HikariCP中如何自定義一個(gè)自己的監(jiān)控器六水,以及相比Druid的監(jiān)控俺孙,有什么區(qū)別。 工作中很多時(shí)候都是需要自定義的掷贾,我司雖然也是用的普羅米修斯監(jiān)控睛榄,但是因?yàn)镠ikariCP原生的普羅米修斯收集器里面對(duì)監(jiān)控指標(biāo)的命名并不符合我司的規(guī)范
,所以就自定義
了一個(gè)想帅,有類似問題的不妨也試一試场靴。
?? 這一節(jié)沒有畫圖,純代碼,因?yàn)楫媹D不太好解釋這部分的東西旨剥,這部分內(nèi)容與連接池整體流程關(guān)系也不大咧欣,充其量獲取了連接池本身的一些屬性,在連接池里的觸發(fā)點(diǎn)也在上面代碼段的注釋里說清楚了泞边,看代碼定義可能更好理解一些该押。
七、流程2.2:連接泄漏的檢測(cè)與告警
本節(jié)對(duì)應(yīng)主流程2
里的子流程2.2
阵谚,在初始化池對(duì)象時(shí)蚕礼,初始化了一個(gè)叫做leakTaskFactory
的屬性,本節(jié)來看下它具體是用來做什么的梢什。
7.1:它是做什么的奠蹬?
一個(gè)連接被拿出去使用時(shí)間超過leakDetectionThreshold
(可配置,默認(rèn)0)未歸還的嗡午,會(huì)觸發(fā)一個(gè)連接泄漏警告囤躁,通知業(yè)務(wù)方目前存在連接泄漏的問題。
7.2:過程詳解
該屬性是ProxyLeakTaskFactory
類型對(duì)象荔睹,且它還會(huì)持有houseKeepingExecutorService
這個(gè)線程池對(duì)象狸演,用于生產(chǎn)ProxyLeakTask
對(duì)象,然后利用上面的houseKeepingExecutorService
延時(shí)運(yùn)行該對(duì)象里的run
方法僻他。該流程的觸發(fā)點(diǎn)在上面的流程1.1
最后包裝成ProxyConnection
對(duì)象的那一步宵距,來看看具體的流程圖:
每次在流程1.1
那里生成ProxyConnection
對(duì)象時(shí),都會(huì)觸發(fā)上面的流程吨拗,由流程圖可以知道满哪,ProxyConnection
對(duì)象持有PoolEntry
和ProxyLeakTask
的對(duì)象,其中初始化ProxyLeakTask
對(duì)象時(shí)就用到了leakTaskFactory
對(duì)象劝篷,通過其schedule
方法可以進(jìn)行ProxyLeakTask
的初始化哨鸭,并將其實(shí)例傳遞給ProxyConnection
進(jìn)行初始化賦值(ps:由圖知ProxyConnection
在觸發(fā)回收事件時(shí),會(huì)主動(dòng)取消這個(gè)泄漏檢查任務(wù)娇妓,這也是ProxyConnection
需要持有ProxyLeakTask
對(duì)象的原因)像鸡。
在上面的流程圖中可以知道,只有在leakDetectionThreshold
不等于0的時(shí)候才會(huì)生成一個(gè)帶有實(shí)際延時(shí)任務(wù)的ProxyLeakTask
對(duì)象哈恰,否則返回?zé)o實(shí)際意義的空對(duì)象坟桅。所以要想啟用連接泄漏檢查,首先要把leakDetectionThreshold
配置設(shè)置上蕊蝗,這個(gè)屬性表示經(jīng)過該時(shí)間后借出去的連接仍未歸還,則觸發(fā)連接泄漏告警赖舟。
ProxyConnection
之所以要持有ProxyLeakTask
對(duì)象蓬戚,是因?yàn)樗梢员O(jiān)聽到連接是否觸發(fā)歸還操作,如果觸發(fā)宾抓,則調(diào)用cancel
方法取消延時(shí)任務(wù)子漩,防止誤告豫喧。
由此流程可以知道,跟Druid一樣幢泼,HikariCP也有連接對(duì)象泄漏檢查紧显,與Druid主動(dòng)回收連接相比,HikariCP實(shí)現(xiàn)更加簡(jiǎn)單缕棵,僅僅是在觸發(fā)時(shí)打印警告日志孵班,不會(huì)采取具體的強(qiáng)制回收的措施。
與Druid一樣招驴,默認(rèn)也是關(guān)閉這個(gè)流程的篙程,因?yàn)閷?shí)際開發(fā)中一般使用第三方框架,框架本身會(huì)保證及時(shí)的close連接别厘,防止連接對(duì)象泄漏虱饿,開啟與否還是取決于業(yè)務(wù)是否需要,如果一定要開啟触趴,如何設(shè)置leakDetectionThreshold
的大小也是需要考慮的一件事氮发。
八、主流程3:生成連接對(duì)象
本節(jié)來講下主流程2
里的createEntry
方法冗懦,這個(gè)方法利用PoolBase里的DriverDataSource
對(duì)象生成一個(gè)實(shí)際的連接對(duì)象(如果忘記DriverDatasource
是哪里初始化的了爽冕,可以看下主流程2
里PoolBase
的initializeDataSource
方法的作用),然后用PoolEntry
類包裝成PoolEntry
對(duì)象批狐,現(xiàn)在來看下這個(gè)包裝類有哪些主要屬性:
final class PoolEntry implements IConcurrentBagEntry {
private static final Logger LOGGER = LoggerFactory.getLogger(PoolEntry.class);
//通過cas來修改state屬性
private static final AtomicIntegerFieldUpdater stateUpdater;
Connection connection; //實(shí)際的物理連接對(duì)象
long lastAccessed; //觸發(fā)回收時(shí)刷新該時(shí)間扇售,表示“最近一次使用時(shí)間”
long lastBorrowed; //getConnection里borrow成功后刷新該時(shí)間,表示“最近一次借出的時(shí)間”
@SuppressWarnings("FieldCanBeLocal")
private volatile int state = 0; //連接狀態(tài)嚣艇,枚舉值:IN_USE(使用中)承冰、NOT_IN_USE(閑置中)、REMOVED(已移除)食零、RESERVED(標(biāo)記為保留中)
private volatile boolean evict; //是否被標(biāo)記為廢棄困乒,很多地方用到(比如流程1.1靠這個(gè)判斷連接是否已被廢棄,再比如主流程4里時(shí)鐘回?fù)軙r(shí)觸發(fā)的直接廢棄邏輯)
private volatile ScheduledFuture<?> endOfLife; //用于在超過連接生命周期(maxLifeTime)時(shí)廢棄連接的延時(shí)任務(wù)贰谣,這里poolEntry要持有該對(duì)象娜搂,主要是因?yàn)樵趯?duì)象主動(dòng)被關(guān)閉時(shí)(意味著不需要在超過maxLifeTime時(shí)主動(dòng)失效了),需要cancel掉該任務(wù)
private final FastList openStatements; //當(dāng)前該連接對(duì)象上生成的所有的statement對(duì)象吱抚,用于在回收連接時(shí)主動(dòng)關(guān)閉這些對(duì)象百宇,防止存在漏關(guān)的statement
private final HikariPool hikariPool; //持有pool對(duì)象
private final boolean isReadOnly; //是否為只讀
private final boolean isAutoCommit; //是否存在事務(wù)
}
上面就是整個(gè)PoolEntry
對(duì)象里所有的屬性,這里再說下endOfLife
對(duì)象秘豹,它是一個(gè)利用houseKeepingExecutorService
這個(gè)線程池對(duì)象做的延時(shí)任務(wù)携御,這個(gè)延時(shí)任務(wù)一般在創(chuàng)建好連接對(duì)象后maxLifeTime
左右的時(shí)間觸發(fā),具體來看下createEntry
代碼:
private PoolEntry createPoolEntry() {
final PoolEntry poolEntry = newPoolEntry(); //生成實(shí)際的連接對(duì)象
final long maxLifetime = config.getMaxLifetime(); //拿到配置好的maxLifetime
if (maxLifetime > 0) { //<=0的時(shí)候不啟用主動(dòng)過期策略
// 計(jì)算需要減去的隨機(jī)數(shù)
// 源注釋:variance up to 2.5% of the maxlifetime
final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong(maxLifetime / 40) : 0;
final long lifetime = maxLifetime - variance; //生成實(shí)際的延時(shí)時(shí)間
poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
() -> { //實(shí)際的延時(shí)任務(wù),這里直接觸發(fā)softEvictConnection啄刹,而softEvictConnection內(nèi)則會(huì)標(biāo)記該連接對(duì)象為廢棄狀態(tài)涮坐,然后嘗試修改其狀態(tài)為STATE_RESERVED,若成功誓军,則觸發(fā)closeConnection(對(duì)應(yīng)流程1.1.2)
if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
addBagItem(connectionBag.getWaitingThreadCount()); //回收完畢后袱讹,連接池內(nèi)少了一個(gè)連接,就會(huì)嘗試新增一個(gè)連接對(duì)象
}
},
lifetime, MILLISECONDS)); //給endOfLife賦值昵时,并且提交延時(shí)任務(wù)捷雕,lifetime后觸發(fā)
}
return poolEntry;
}
//觸發(fā)新增連接任務(wù)
public void addBagItem(final int waiting) {
//前排提示:addConnectionQueue和addConnectionExecutor的關(guān)系和初始化參考主流程2
//當(dāng)添加連接的隊(duì)列里已提交的任務(wù)超過那些因?yàn)楂@取不到連接而發(fā)生阻塞的線程個(gè)數(shù)時(shí),就進(jìn)行提交連接新增連接的任務(wù)
final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
if (shouldAdd) {
//提交任務(wù)給addConnectionExecutor這個(gè)線程池债查,PoolEntryCreator是一個(gè)實(shí)現(xiàn)了Callable接口的類非区,下面將通過流程圖的方式介紹該類的call方法
addConnectionExecutor.submit(poolEntryCreator);
}
}
通過上面的流程,可以知道盹廷,HikariCP一般通過createEntry
方法來新增一個(gè)連接入池征绸,每個(gè)連接被包裝成PoolEntry對(duì)象,在創(chuàng)建好對(duì)象時(shí)俄占,同時(shí)會(huì)提交一個(gè)延時(shí)任務(wù)來關(guān)閉廢棄該連接管怠,這個(gè)時(shí)間就是我們配置的maxLifeTime
,為了保證不在同一時(shí)間失效缸榄,HikariCP還會(huì)利用maxLifeTime
減去一個(gè)隨機(jī)數(shù)作為最終的延時(shí)任務(wù)延遲時(shí)間渤弛,然后在觸發(fā)廢棄任務(wù)時(shí)葫男,還會(huì)觸發(fā)addBagItem
隧出,進(jìn)行連接添加任務(wù)(因?yàn)閺U棄了一個(gè)連接晓避,需要往池子里補(bǔ)充一個(gè))尘盼,該任務(wù)則交給由主流程2
里定義好的addConnectionExecutor
線程池執(zhí)行,那么寓辱,現(xiàn)在來看下這個(gè)異步添加連接對(duì)象的任務(wù)流程:
這個(gè)流程就是往連接池里加連接用的压状,跟createEntry
結(jié)合起來說是因?yàn)檫@倆流程是緊密相關(guān)的魂贬,除此之外碉输,主流程5
(fillPool
籽前,擴(kuò)充連接池)也會(huì)觸發(fā)該任務(wù)。
九敷钾、主流程4:連接池縮容
HikariCP會(huì)按照minIdle
定時(shí)清理閑置過久的連接枝哄,這個(gè)定時(shí)任務(wù)在主流程2
初始化連接池對(duì)象時(shí)被啟用,跟上面的流程一樣阻荒,也是利用houseKeepingExecutorService
這個(gè)線程池對(duì)象做該定時(shí)任務(wù)的執(zhí)行器挠锥。
來看下主流程2
里是怎么啟用該任務(wù)的:
//housekeepingPeriodMs的默認(rèn)值是30s,所以定時(shí)任務(wù)的間隔為30s
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);
那么本節(jié)主要來說下HouseKeeper
這個(gè)類侨赡,該類實(shí)現(xiàn)了Runnable
接口瘪贱,回收邏輯主要在其run
方法內(nèi)纱控,來看看run
方法的邏輯流程圖:
上面的流程就是HouseKeeper
的run方法里具體做的事情,由于系統(tǒng)時(shí)間回?fù)軙?huì)導(dǎo)致該定時(shí)任務(wù)回收一些連接時(shí)產(chǎn)生誤差菜秦,因此存在如下判斷:
//now就是當(dāng)前系統(tǒng)時(shí)間,previous就是上次觸發(fā)該任務(wù)時(shí)的時(shí)間舶掖,housekeepingPeriodMs就是隔多久觸發(fā)該任務(wù)一次
//也就是說plusMillis(previous, housekeepingPeriodMs)表示當(dāng)前時(shí)間
//如果系統(tǒng)時(shí)間沒被回?fù)芮蜃颍敲磒lusMillis(now, 128)一定是大于當(dāng)前時(shí)間的,如果被系統(tǒng)時(shí)間被回?fù)?//回?fù)艿臅r(shí)間超過128ms眨攘,那么下面的判斷就成立主慰,否則永遠(yuǎn)不會(huì)成立
if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs))
這是hikariCP在解決系統(tǒng)時(shí)鐘被回?fù)軙r(shí)做出的一種措施,通過流程圖可以看到鲫售,它是直接把池子里所有的連接對(duì)象取出來挨個(gè)兒的標(biāo)記成廢棄共螺,并且嘗試把狀態(tài)值修改為STATE_RESERVED
(后面會(huì)說明這些狀態(tài),這里先不深究)情竹。如果系統(tǒng)時(shí)鐘沒有發(fā)生改變(絕大多數(shù)情況會(huì)命中這一塊的邏輯)藐不,由圖知,會(huì)把當(dāng)前池內(nèi)所有處于閑置狀態(tài)(STATE_NOT_IN_USE
)的連接拿出來秦效,然后計(jì)算需要檢查的范圍雏蛮,然后循環(huán)著修改連接的狀態(tài):
//拿到所有處于閑置狀態(tài)的連接
final List notInUse = connectionBag.values(STATE_NOT_IN_USE);
//計(jì)算出需要被檢查閑置時(shí)間的數(shù)量,簡(jiǎn)單來說阱州,池內(nèi)需要保證最小minIdle個(gè)連接活著挑秉,所以需要計(jì)算出超出這個(gè)范圍的閑置對(duì)象進(jìn)行檢查
int toRemove = notInUse.size() - config.getMinIdle();
for (PoolEntry entry : notInUse) {
//在檢查范圍內(nèi),且閑置時(shí)間超出idleTimeout苔货,然后嘗試將連接對(duì)象狀態(tài)由STATE_NOT_IN_USE變?yōu)镾TATE_RESERVED成功
if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
closeConnection(entry, "(connection has passed idleTimeout)"); //滿足上述條件犀概,進(jìn)行連接關(guān)閉
toRemove--;
}
}
fillPool(); //因?yàn)榭赡芑厥樟艘恍┻B接,所以要再次觸發(fā)連接池?cái)U(kuò)充流程檢查下是否需要新增連接夜惭。
上面的代碼就是流程圖里對(duì)應(yīng)的沒有回?fù)芟到y(tǒng)時(shí)間時(shí)的流程邏輯姻灶。該流程在idleTimeout
大于0(默認(rèn)等于0)并且minIdle
小于maxPoolSize
的時(shí)候才會(huì)啟用,默認(rèn)是不啟用的滥嘴,若需要啟用木蹬,可以按照條件來配置。
十若皱、主流程5:擴(kuò)充連接池
這個(gè)流程主要依附HikariPool里的fillPool
方法镊叁,這個(gè)方法已經(jīng)在上面很多流程里出現(xiàn)過了,它的作用就是在觸發(fā)連接廢棄走触、連接池連接不夠用時(shí)晦譬,發(fā)起擴(kuò)充連接數(shù)的操作,這是個(gè)很簡(jiǎn)單的過程互广,下面看下源碼(為了使代碼結(jié)構(gòu)更加清晰敛腌,對(duì)源碼做了細(xì)微改動(dòng)):
// PoolEntryCreator關(guān)于call方法的實(shí)現(xiàn)流程在主流程3里已經(jīng)看過了卧土,但是這里卻有倆PoolEntryCreator對(duì)象,
// 這是個(gè)較細(xì)節(jié)的地方像樊,用于打日志用尤莺,不再說這部分,為了便于理解生棍,只需要知道這倆對(duì)象執(zhí)行的是同一塊call方法即可
private final PoolEntryCreator poolEntryCreator = new PoolEntryCreator(null);
private final PoolEntryCreator postFillPoolEntryCreator = new PoolEntryCreator("After adding ");
private synchronized void fillPool() {
// 這個(gè)判斷就是根據(jù)當(dāng)前池子里相關(guān)數(shù)據(jù)颤霎,推算出需要擴(kuò)充的連接數(shù),
// 判斷方式就是利用最大連接數(shù)跟當(dāng)前連接總數(shù)的差值涂滴,與最小連接數(shù)與當(dāng)前池內(nèi)閑置的連接數(shù)的差值友酱,取其最小的那一個(gè)得到
int needAdd = Math.min(maxPoolSize - connectionBag.size(),
minIdle - connectionBag.getCount(STATE_NOT_IN_USE));
//減去當(dāng)前排隊(duì)的任務(wù),就是最終需要新增的連接數(shù)
final int connectionsToAdd = needAdd - addConnectionQueue.size();
for (int i = 0; i < connectionsToAdd; i++) {
//一般循環(huán)的最后一次會(huì)命中postFillPoolEntryCreator任務(wù)柔纵,其實(shí)就是在最后一次會(huì)打印一次日志而已(可以忽略該干擾邏輯)
addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
}
}
由該過程可以知道缔杉,最終這個(gè)新增連接的任務(wù)也是交由addConnectionExecutor
線程池來處理的,而任務(wù)的主題也是PoolEntryCreator
搁料,這個(gè)流程可以參考主流程3.
然后needAdd
的推算:
Math.min(最大連接數(shù) - 池內(nèi)當(dāng)前連接總數(shù), 最小連接數(shù) - 池內(nèi)閑置的連接數(shù))
根據(jù)這個(gè)方式判斷或详,可以保證池內(nèi)的連接數(shù)永遠(yuǎn)不會(huì)超過maxPoolSize
,也永遠(yuǎn)不會(huì)低于minIdle
加缘。在連接吃緊的時(shí)候鸭叙,可以保證每次觸發(fā)都以minIdle
的數(shù)量擴(kuò)容。因此如果在maxPoolSize
跟minIdle
配置的值一樣的話拣宏,在池內(nèi)連接吃緊的時(shí)候沈贝,就不會(huì)發(fā)生任何擴(kuò)容了。
十一勋乾、主流程6:連接回收
最開始說過宋下,最終真實(shí)的物理連接對(duì)象會(huì)被包裝成PoolEntry
對(duì)象,存放進(jìn)ConcurrentBag
辑莫,然后獲取時(shí)学歧,PoolEntry對(duì)象又會(huì)被再次包裝成ProxyConnection
對(duì)象暴露給使用方的,那么觸發(fā)連接回收各吨,實(shí)際上就是觸發(fā)ProxyConnection里的close方法:
public final void close() throws SQLException {
// 原注釋:Closing statements can cause connection eviction, so this must run before the conditional below
closeStatements(); //此連接對(duì)象在業(yè)務(wù)方使用過程中產(chǎn)生的所有statement對(duì)象枝笨,進(jìn)行統(tǒng)一close,防止漏close的情況
if (delegate != ClosedConnection.CLOSED_CONNECTION) {
leakTask.cancel(); //取消連接泄漏檢查任務(wù)揭蜒,參考流程2.2
try {
if (isCommitStateDirty && !isAutoCommit) { //在存在執(zhí)行語句后并且還打開了事務(wù)横浑,調(diào)用close時(shí)需要主動(dòng)回滾事務(wù)
delegate.rollback(); //回滾
lastAccess = currentTime(); //刷新"最后一次使用時(shí)間"
}
} finally {
delegate = ClosedConnection.CLOSED_CONNECTION;
poolEntry.recycle(lastAccess); //觸發(fā)回收
}
}
}
這個(gè)就是ProxyConnection里的close方法,可以看到它最終會(huì)調(diào)用PoolEntry的recycle方法進(jìn)行回收屉更,除此之外徙融,連接對(duì)象的最后一次使用時(shí)間也是在這個(gè)時(shí)候刷新的,該時(shí)間是個(gè)很重要的屬性瑰谜,可以用來判斷一個(gè)連接對(duì)象的閑置時(shí)間欺冀,來看下PoolEntry的recycle
方法:
void recycle(final long lastAccessed) {
if (connection != null) {
this.lastAccessed = lastAccessed; //刷新最后使用時(shí)間
hikariPool.recycle(this); //觸發(fā)HikariPool的回收方法树绩,把自己傳過去
}
}
之前有說過,每個(gè)PoolEntry對(duì)象都持有HikariPool的對(duì)象隐轩,方便觸發(fā)連接池的一些操作饺饭,由上述代碼可以看到,最終還是會(huì)觸發(fā)HikariPool里的recycle方法职车,再來看下HikariPool的recycle方法:
void recycle(final PoolEntry poolEntry) {
metricsTracker.recordConnectionUsage(poolEntry); //監(jiān)控指標(biāo)相關(guān)砰奕,忽略
connectionBag.requite(poolEntry); //最終觸發(fā)connectionBag的requite方法歸還連接,該流程參考ConnectionBag主流程里的requite方法部分
}
以上就是連接回收部分的邏輯提鸟,相比其他流程,還是比較簡(jiǎn)潔的仅淑。
十二称勋、ConcurrentBag主流程
這個(gè)類用來存放最終的PoolEntry類型的連接對(duì)象,提供了基本的增刪查的功能涯竟,被HikariPool持有赡鲜,上面那么多的操作,幾乎都是在HikariPool中完成的庐船,HikariPool用來管理實(shí)際的連接生產(chǎn)動(dòng)作和回收動(dòng)作银酬,實(shí)際操作的卻是ConcurrentBag類,梳理下上面所有流程的觸發(fā)點(diǎn):
- 主流程2:初始化HikariPool時(shí)初始化
ConcurrentBag(構(gòu)造方法)
筐钟,預(yù)熱時(shí)通過createEntry
拿到連接對(duì)象揩瞪,調(diào)用ConcurrentBag.add
添加連接到ConcurrentBag。 - 流程1.1:通過HikariPool獲取連接時(shí)篓冲,通過調(diào)用
ConcurrentBag.borrow
拿到一個(gè)連接對(duì)象李破。 - 主流程6:通過
ConcurrentBag.requite
歸還一個(gè)連接。 - 流程1.1.2:觸發(fā)關(guān)閉連接時(shí)壹将,會(huì)通過
ConcurrentBag.remove
移除連接對(duì)象嗤攻,由前面的流程可知關(guān)閉連接觸發(fā)點(diǎn)為:連接超過最大生命周期maxLifeTime主動(dòng)廢棄、健康檢查不通過主動(dòng)廢棄诽俯、連接池縮容妇菱。 - 主流程3:通過異步添加連接時(shí),通過調(diào)用
ConcurrentBag.add
添加連接到ConcurrentBag暴区,由前面的流程可知添加連接觸發(fā)點(diǎn)為:連接超過最大生命周期maxLifeTime主動(dòng)廢棄連接后闯团、連接池?cái)U(kuò)容。 - 主流程4:連接池縮容任務(wù)颜启,通過調(diào)用
ConcurrentBag.values
篩選出需要的做操作的連接對(duì)象偷俭,然后再通過ConcurrentBag.reserve
完成對(duì)連接對(duì)象狀態(tài)的修改,然后會(huì)通過流程1.1.2
觸發(fā)關(guān)閉和移除連接操作缰盏。
通過觸發(fā)點(diǎn)整理涌萤,可以知道該結(jié)構(gòu)里的主要方法淹遵,就是上面觸發(fā)點(diǎn)里標(biāo)記為標(biāo)簽色
的部分,然后來具體看下該類的基本定義和主要方法:
public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable {
private final CopyOnWriteArrayList<T> sharedList; //最終存放PoolEntry對(duì)象的地方负溪,它是一個(gè)CopyOnWriteArrayList
private final boolean weakThreadLocals; //默認(rèn)false透揣,為true時(shí)可以讓一個(gè)連接對(duì)象在下方threadList里的list內(nèi)處于弱引用狀態(tài),防止內(nèi)存泄漏(參見備注1)
private final ThreadLocal<List<Object>> threadList; //線程級(jí)的緩存川抡,從sharedList拿到的連接對(duì)象辐真,會(huì)被緩存進(jìn)當(dāng)前線程內(nèi),borrow時(shí)會(huì)先從緩存中拿崖堤,從而達(dá)到池內(nèi)無鎖實(shí)現(xiàn)
private final IBagStateListener listener; //內(nèi)部接口侍咱,HikariPool實(shí)現(xiàn)了該接口,主要用于ConcurrentBag主動(dòng)通知HikariPool觸發(fā)添加連接對(duì)象的異步操作(也就是主流程3里的addConnectionExecutor所觸發(fā)的流程)
private final AtomicInteger waiters; //當(dāng)前因?yàn)楂@取不到連接而發(fā)生阻塞的業(yè)務(wù)線程數(shù)密幔,這個(gè)在之前的流程里也出現(xiàn)過楔脯,比如主流程3里addBagItem就會(huì)根據(jù)該指標(biāo)進(jìn)行判斷是否需要新增連接
private volatile boolean closed; //標(biāo)記當(dāng)前ConcurrentBag是否已被關(guān)閉
private final SynchronousQueue<T> handoffQueue; //這是個(gè)即產(chǎn)即銷的隊(duì)列,用于在連接不夠用時(shí)胯甩,及時(shí)獲取到add方法里新創(chuàng)建的連接對(duì)象昧廷,詳情可以參考下面borrow和add的代碼
//內(nèi)部接口,PoolEntry類實(shí)現(xiàn)了該接口
public interface IConcurrentBagEntry {
//連接對(duì)象的狀態(tài)偎箫,前面的流程很多地方都已經(jīng)涉及到了木柬,比如主流程4的縮容
int STATE_NOT_IN_USE = 0; //閑置
int STATE_IN_USE = 1; //使用中
int STATE_REMOVED = -1; //已廢棄
int STATE_RESERVED = -2; //標(biāo)記保留,介于閑置和廢棄之間的中間狀態(tài)淹办,主要由縮容那里觸發(fā)修改
boolean compareAndSet(int expectState, int newState); //嘗試?yán)胏as修改連接對(duì)象的狀態(tài)值
void setState(int newState); //設(shè)置狀態(tài)值
int getState(); //獲取狀態(tài)值
}
//參考上面listener屬性的解釋
public interface IBagStateListener {
void addBagItem(int waiting);
}
//獲取連接方法
public T borrow(long timeout, final TimeUnit timeUnit) {
// 省略...
}
//回收連接方法
public void requite(final T bagEntry) {
//省略...
}
//添加連接方法
public void add(final T bagEntry) {
//省略...
}
//移除連接方法
public boolean remove(final T bagEntry) {
//省略...
}
//根據(jù)連接狀態(tài)值獲取當(dāng)前池子內(nèi)所有符合條件的連接集合
public List values(final int state) {
//省略...
}
//獲取當(dāng)前池子內(nèi)所有的連接
public List values() {
//省略...
}
//利用cas把傳入的連接對(duì)象的state從 STATE_NOT_IN_USE 變?yōu)?STATE_RESERVED
public boolean reserve(final T bagEntry) {
//省略...
}
//獲取當(dāng)前池子內(nèi)符合傳入狀態(tài)值的連接數(shù)量
public int getCount(final int state) {
//省略...
}
}
從這個(gè)基本結(jié)構(gòu)就可以稍微看出HikariCP是如何優(yōu)化傳統(tǒng)連接池實(shí)現(xiàn)的了眉枕,相比Druid來說,HikariCP更加偏向無鎖實(shí)現(xiàn)娇唯,盡量避免鎖競(jìng)爭(zhēng)的發(fā)生齐遵。
12.1:borrow
這個(gè)方法用來獲取一個(gè)可用的連接對(duì)象,觸發(fā)點(diǎn)為流程1.1
塔插,HikariPool就是利用該方法獲取連接的梗摇,下面來看下該方法做了什么:
public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
// 源注釋:Try the thread-local list first
final List<Object> list = threadList.get(); //首先從當(dāng)前線程的緩存里拿到之前被緩存進(jìn)來的連接對(duì)象集合
for (int i = list.size() - 1; i >= 0; i--) {
final Object entry = list.remove(i); //先移除,回收方法那里會(huì)再次add進(jìn)來
final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry; //默認(rèn)不啟用弱引用
// 獲取到對(duì)象后想许,通過cas嘗試把其狀態(tài)從STATE_NOT_IN_USE 變?yōu)?STATE_IN_USE伶授,注意,這里如果其他線程也在使用這個(gè)連接對(duì)象流纹,
// 并且成功修改屬性糜烹,那么當(dāng)前線程的cas會(huì)失敗,那么就會(huì)繼續(xù)循環(huán)嘗試獲取下一個(gè)連接對(duì)象
if (bagEntry != null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry; //cas設(shè)置成功后漱凝,表示當(dāng)前線程繞過其他線程干擾疮蹦,成功獲取到該連接對(duì)象,直接返回
}
}
// 源注釋:Otherwise, scan the shared list ... then poll the handoff queue
final int waiting = waiters.incrementAndGet(); //如果緩存內(nèi)找不到一個(gè)可用的連接對(duì)象茸炒,則認(rèn)為需要“回源”愕乎,waiters+1
try {
for (T bagEntry : sharedList) {
//循環(huán)sharedList阵苇,嘗試把連接狀態(tài)值從STATE_NOT_IN_USE 變?yōu)?STATE_IN_USE
if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
// 源注釋:If we may have stolen another waiter's connection, request another bag add.
if (waiting > 1) { //阻塞線程數(shù)大于1時(shí),需要觸發(fā)HikariPool的addBagItem方法來進(jìn)行添加連接入池感论,這個(gè)方法的實(shí)現(xiàn)參考主流程3
listener.addBagItem(waiting - 1);
}
return bagEntry; //cas設(shè)置成功绅项,跟上面的邏輯一樣,表示當(dāng)前線程繞過其他線程干擾比肄,成功獲取到該連接對(duì)象快耿,直接返回
}
}
//走到這里說明不光線程緩存里的列表競(jìng)爭(zhēng)不到連接對(duì)象,連sharedList里也找不到可用的連接芳绩,這時(shí)則認(rèn)為需要通知HikariPool掀亥,該觸發(fā)添加連接操作了
listener.addBagItem(waiting);
timeout = timeUnit.toNanos(timeout); //這時(shí)候開始利用timeout控制獲取時(shí)間
do {
final long start = currentTime();
//嘗試從handoffQueue隊(duì)列里獲取最新被加進(jìn)來的連接對(duì)象(一般新入的連接對(duì)象除了加進(jìn)sharedList之外,還會(huì)被offer進(jìn)該隊(duì)列)
final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
//如果超出指定時(shí)間后仍然沒有獲取到可用的連接對(duì)象妥色,或者獲取到對(duì)象后通過cas設(shè)置成功铺浇,這兩種情況都不需要重試,直接返回對(duì)象
if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
return bagEntry;
}
//走到這里說明從隊(duì)列內(nèi)獲取到了連接對(duì)象垛膝,但是cas設(shè)置失敗,說明又該對(duì)象又被其他線程率先拿去用了丁稀,若時(shí)間還夠吼拥,則再次嘗試獲取
timeout -= elapsedNanos(start); //timeout減去消耗的時(shí)間,表示下次循環(huán)可用時(shí)間
} while (timeout > 10_000); //剩余時(shí)間大于10s時(shí)才繼續(xù)進(jìn)行线衫,一般情況下凿可,這個(gè)循環(huán)只會(huì)走一次,因?yàn)閠imeout很少會(huì)配的比10s還大
return null; //超時(shí)授账,仍然返回null
} finally {
waiters.decrementAndGet(); //這一步出去后枯跑,HikariPool收到borrow的結(jié)果,算是走出阻塞白热,所以waiters-1
}
}
仔細(xì)看下注釋敛助,該過程大致分成三個(gè)主要步驟:
- 從線程緩存獲取連接
- 獲取不到再?gòu)?code>sharedList里獲取
- 都獲取不到則觸發(fā)添加連接邏輯,并嘗試從隊(duì)列里獲取新生成的連接對(duì)象
12.2:add
這個(gè)流程會(huì)添加一個(gè)連接對(duì)象進(jìn)入bag屋确,通常由主流程3
里的addBagItem
方法通過addConnectionExecutor
異步任務(wù)觸發(fā)添加操作纳击,該方法主流程如下:
public void add(final T bagEntry) {
sharedList.add(bagEntry); //直接加到sharedList里去
// 源注釋:spin until a thread takes it or none are waiting
// 參考borrow流程,當(dāng)存在線程等待獲取可用連接攻臀,并且當(dāng)前新入的這個(gè)連接狀態(tài)仍然是閑置狀態(tài)焕数,且隊(duì)列里無消費(fèi)者等待獲取時(shí),發(fā)起一次線程調(diào)度
while (waiters.get() > 0 && bagEntry.getState() == STATE_NOT_IN_USE && !handoffQueue.offer(bagEntry)) { //注意這里會(huì)offer一個(gè)連接對(duì)象入隊(duì)列
yield();
}
}
結(jié)合borrow
來理解的話刨啸,這里在存在等待線程時(shí)會(huì)添加一個(gè)連接對(duì)象入隊(duì)列堡赔,可以讓borrow
里發(fā)生等待的地方更容易poll到這個(gè)連接對(duì)象。
12.3:requite
這個(gè)流程會(huì)回收一個(gè)連接设联,該方法的觸發(fā)點(diǎn)在主流程6
善已,具體代碼如下:
public void requite(final T bagEntry) {
bagEntry.setState(STATE_NOT_IN_USE); //回收意味著使用完畢灼捂,更改state為STATE_NOT_IN_USE狀態(tài)
for (int i = 0; waiters.get() > 0; i++) { //如果存在等待線程的話,嘗試傳給隊(duì)列雕拼,讓borrow獲取
if (bagEntry.getState() != STATE_NOT_IN_USE || handoffQueue.offer(bagEntry)) {
return;
}
else if ((i & 0xff) == 0xff) {
parkNanos(MICROSECONDS.toNanos(10));
}
else {
yield();
}
}
final List<Object> threadLocalList = threadList.get();
if (threadLocalList.size() < 50) { //線程內(nèi)連接集合的緩存最多50個(gè)纵东,這里回收連接時(shí)會(huì)再次加進(jìn)當(dāng)前線程的緩存里,方便下次borrow獲取
threadLocalList.add(weakThreadLocals ? new WeakReference<>(bagEntry) : bagEntry); //默認(rèn)不啟用弱引用啥寇,若啟用的話偎球,則緩存集合里的連接對(duì)象沒有內(nèi)存泄露的風(fēng)險(xiǎn)
}
}
12.4:remove
這個(gè)負(fù)責(zé)從池子里移除一個(gè)連接對(duì)象,觸發(fā)點(diǎn)在流程1.1.2
辑甜,代碼如下:
public boolean remove(final T bagEntry) {
// 下面兩個(gè)cas操作衰絮,都是從其他狀態(tài)變?yōu)橐瞥隣顟B(tài),任意一個(gè)成功磷醋,都不會(huì)走到下面的warn log
if (!bagEntry.compareAndSet(STATE_IN_USE, STATE_REMOVED) && !bagEntry.compareAndSet(STATE_RESERVED, STATE_REMOVED) && !closed) {
LOGGER.warn("Attempt to remove an object from the bag that was not borrowed or reserved: {}", bagEntry);
return false;
}
// 直接從sharedList移除掉
final boolean removed = sharedList.remove(bagEntry);
if (!removed && !closed) {
LOGGER.warn("Attempt to remove an object from the bag that does not exist: {}", bagEntry);
}
return removed;
}
這里需要注意的是猫牡,移除時(shí)僅僅移除了sharedList
里的對(duì)象,各個(gè)線程內(nèi)緩存的那一份集合里對(duì)應(yīng)的對(duì)象并沒有被移除邓线,這個(gè)時(shí)候會(huì)不會(huì)存在該連接再次從緩存里拿到呢淌友?會(huì)的,但是不會(huì)返回出去骇陈,而是直接remove
掉了震庭,仔細(xì)看borrow
的代碼發(fā)現(xiàn)狀態(tài)不是閑置狀態(tài)的時(shí)候,取出來時(shí)就會(huì)remove
掉你雌,然后也拿不出去器联,自然也不會(huì)觸發(fā)回收方法。
12.5:values
該方法存在重載方法婿崭,用于返回當(dāng)前池子內(nèi)連接對(duì)象的集合拨拓,觸發(fā)點(diǎn)在主流程4
,代碼如下:
public List values(final int state) {
//過濾出來符合狀態(tài)值的對(duì)象集合逆序后返回出去
final List list = sharedList.stream().filter(e -> e.getState() == state).collect(Collectors.toList());
Collections.reverse(list);
return list;
}
public List values() {
//返回全部連接對(duì)象(注意下方clone為淺拷貝)
return (List) sharedList.clone();
}
12.6:reserve
該方法單純將連接對(duì)象的狀態(tài)值由STATE_NOT_IN_USE
修改為STATE_RESERVED
氓栈,觸發(fā)點(diǎn)仍然是主流程4
渣磷,縮容時(shí)使用,代碼如下:
public boolean reserve(final T bagEntry){
return bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_RESERVED);
}
12.7:getCount
該方法用于返回池內(nèi)符合某個(gè)狀態(tài)值的連接的總數(shù)量授瘦,觸發(fā)點(diǎn)為主流程5
幸海,擴(kuò)充連接池時(shí)用于獲取閑置連接總數(shù),代碼如下:
public int getCount(final int state){
int count = 0;
for (IConcurrentBagEntry e : sharedList) {
if (e.getState() == state) {
count++;
}
}
return count;
}
以上就是ConcurrentBag
的主要方法和處理連接對(duì)象的主要流程奥务。
十三物独、總結(jié)
到這里基本上一個(gè)連接的生產(chǎn)到獲取到回收到廢棄一整個(gè)生命周期在HikariCP內(nèi)是如何管理的就說完了,相比之前的Druid的實(shí)現(xiàn)氯葬,有很大的不同挡篓,主要是HikariCP的無鎖
獲取連接,本篇沒有涉及FastList
的說明,因?yàn)閺倪B接管理這個(gè)角度確實(shí)很少用到該結(jié)構(gòu)官研,用到FastList
的地方主要在存儲(chǔ)連接對(duì)象生成的statement對(duì)象
以及用于存儲(chǔ)線程內(nèi)緩存起來的連接對(duì)象秽澳;
除此之外HikariCP還利用javassist
技術(shù)編譯期生成了ProxyConnection
的初始化,這里也沒有相關(guān)說明戏羽,網(wǎng)上有關(guān)HikariCP的優(yōu)化有很多文章担神,大多數(shù)都提到了字節(jié)碼優(yōu)化
、fastList
始花、concurrentBag
的實(shí)現(xiàn)妄讯,本篇主要通過深入解析HikariPool
和ConcurrentBag
的實(shí)現(xiàn),來說明HikariCP相比Druid具體做了哪些不一樣的操作酷宵。