前言
現(xiàn)在已經(jīng)有很多公司在使用HikariCP了,HikariCP還成為了SpringBoot默認(rèn)的連接池,伴隨著SpringBoot和微服務(wù),HikariCP 必將迎來廣泛的普及。
下面陳某帶大家從源碼角度分析一下HikariCP為什么能夠被Spring Boot 請(qǐng)來,文章目錄如下:
目錄
零、類圖和流程圖開始前先來了解下HikariCP獲取一個(gè)連接時(shí)類間的交互流程,方便下面詳細(xì)流程的閱讀。
獲取連接時(shí)的類間交互:
圖1
一、主流程1:獲取連接流程HikariCP獲取連接時(shí)的入口是HikariDataSource里的getConnection方法,現(xiàn)在來看下該方法的具體流程:
主流程1
上述為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)記aled,然后根據(jù)pool對(duì)象調(diào)用getConnection方法(參考流程1.1),獲取成功后返回連接對(duì)象。
二、主流程2:初始化池對(duì)象主流程2
該流程用于初始化整個(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ì)象houKeepingExecutorService,用于后續(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ì)象cloConnectionExecutor,用于關(guān)閉一些連接對(duì)象,怎么觸發(fā)關(guān)閉任務(wù)呢?可以參考流程1.1.2三、流程1.1:通過HikariPool獲取連接對(duì)象流程1.1
從最開始的結(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)用cloConnection方法關(guān)閉連接(對(duì)應(yīng)流程1.1.2,該流程會(huì)觸發(fā)ConcurrentBag的remove方法丟棄該連接,然后把實(shí)際的驅(qū)動(dòng)連接交給cloConnectionExecutor線程池,異步關(guān)閉驅(qū)動(dòng)連接)。
四、流程1.1.1:連接判活流程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.isUJdbc4Validation=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那樣可以tQueryTimeout的,所以只能由網(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ì)象流程1.1.2
這個(gè)流程簡(jiǎn)單來說就是把流程1.1.1中驗(yàn)證不通過的死連接,主動(dòng)關(guān)閉的一個(gè)流程,首先會(huì)把這個(gè)連接對(duì)象從ConnectionBag里移除,然后把實(shí)際的物理連接交給一個(gè)線程池去異步執(zhí)行,這個(gè)線程池就是在主流程2里初始化池的時(shí)候初始化的線程池cloConnectionExecutor,然后異步任務(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下文直接稱為普羅米修斯):
圖2
下面,來著重看下接口的定義:
//這個(gè)接口的實(shí)現(xiàn)主要負(fù)責(zé)收集一些動(dòng)作的耗時(shí)publicinterfaceIMetricsTrackerextendsAutoCloable{//這個(gè)方法觸發(fā)點(diǎn)在創(chuàng)建實(shí)際的物理連接時(shí)(主流程3),用于記錄一個(gè)實(shí)際的物理連接創(chuàng)建所耗費(fèi)的時(shí)間defaultvoidrecordConnectionCreatedMillis(longconnectionCreatedMillis){}//這個(gè)方法觸發(fā)點(diǎn)在getConnection時(shí)(主流程1),用于記錄獲取一個(gè)連接時(shí)實(shí)際的耗時(shí)defaultvoidrecordConnectionAcquiredNanos(finallongelapdAcquiredNanos){}//這個(gè)方法觸發(fā)點(diǎn)在回收連接時(shí)(主流程6),用于記錄一個(gè)連接從被獲取到被回收時(shí)所消耗的時(shí)間defaultvoidrecordConnectionUsageMillis(finallongelapdBorrowedMillis){}//這個(gè)方法觸發(fā)點(diǎn)也在getConnection時(shí)(主流程1),用于記錄獲取連接超時(shí)的次數(shù),每發(fā)生一次獲取連接超時(shí),就會(huì)觸發(fā)一次該方法的調(diào)用defaultvoidrecordConnectionTimeout(){}@Overridedefaultvoidclo(){}}
觸發(fā)點(diǎn)都了解清楚后,再來看看MetricsTrackerFactory的接口定義:
//用于創(chuàng)建IMetricsTracker實(shí)例,并且按需記錄PoolStats對(duì)象里的屬性(這個(gè)對(duì)象里的屬性就是類似連接池當(dāng)前閑置連接數(shù)之類的線程池狀態(tài)類指標(biāo))publicinterfaceMetricsTrackerFactory{//返回一個(gè)IMetricsTracker對(duì)象,并且把PoolStats傳了過去IMetricsTrackercreate(StringpoolName,PoolStatspoolStats);}
上面的接口用法見注釋,針對(duì)新出現(xiàn)的PoolStats類,我們來看看它做了什么:
publicabstractclassPoolStats{privatefinalAtomicLongreloadAt;//觸發(fā)下次刷新的時(shí)間(時(shí)間戳)privatefinallongtimeoutMs;//刷新下面的各項(xiàng)屬性值的頻率,默認(rèn)1s,無法改變//總連接數(shù)protectedvolatileinttotalConnections;//閑置連接數(shù)protectedvolatileintidleConnections;//活動(dòng)連接數(shù)protectedvolatileintactiveConnections;//由于無法獲取到可用連接而阻塞的業(yè)務(wù)線程數(shù)protectedvolatileintpendingThreads;//最大連接數(shù)protectedvolatileintmaxConnections;//最小連接數(shù)protectedvolatileintminConnections;publicPoolStats(finallongtimeoutMs){this.timeoutMs=timeoutMs;this.reloadAt=newAtomicLong();}//這里以獲取最大連接數(shù)為例,其他的跟這個(gè)差不多publicintgetMaxConnections(){if(shouldLoad()){//是否應(yīng)該刷新update();//刷新屬性值,注意這個(gè)update的實(shí)現(xiàn)在HikariPool里,因?yàn)檫@些屬性值的直接或間接來源都是HikariPool}returnmaxConnections;}protectedabstractvoidupdate();//實(shí)現(xiàn)在↑上面已經(jīng)說了privatebooleanshouldLoad(){//按照更新頻率來決定是否刷新屬性值for(;;){finallongnow=currentTime();finallongreloadTime=reloadAt.get();if(reloadTime>now){returnfal;}elif(reloadAt.compareAndSet(reloadTime,plusMillis(now,timeoutMs))){returntrue;}}}}
實(shí)際上這里就是這些屬性獲取和觸發(fā)刷新的地方,那么這個(gè)對(duì)象是在哪里被生成并且丟給MetricsTrackerFactory的create方法的呢?這就是本節(jié)所需要講述的要點(diǎn):主流程2里的設(shè)置監(jiān)控器的流程,來看看那里發(fā)生了什么事吧:
//監(jiān)控器設(shè)置方法(此方法在HikariPool中,metricsTracker屬性就是HikariPool用來觸發(fā)IMetricsTracker里方法調(diào)用的)publicvoidtMetricsTrackerFactory(MetricsTrackerFactorymetricsTrackerFactory){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ì)象,然后也一并傳給MetricsTrackerFactorythis.metricsTracker=newMetricsTrackerDelegate(metricsTrackerFactory.create(config.getPoolName(),getPoolStats()));}el{//不啟用監(jiān)控,直接等于一個(gè)沒有實(shí)現(xiàn)方法的空類this.metricsTracker=newNopMetricsTrackerDelegate();}}privatePoolStatsgetPoolStats(){//初始化PoolStats對(duì)象,并且規(guī)定1s觸發(fā)一次屬性值刷新的update方法returnnewPoolStats(SECONDS.toMillis(1)){@Overrideprotectedvoidupdate(){//實(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的tMetricsTrackerFactory方法注冊(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類):
@OverridepublicIMetricsTrackercreate(StringpoolName,PoolStatspoolStats){getCollector().add(poolName,poolStats);//將接收到的PoolStats對(duì)象直接交給Collector,這樣普羅米修斯服務(wù)端每觸發(fā)一次采集接口的調(diào)用,PoolStats都會(huì)跟著執(zhí)行一遍內(nèi)部屬性獲取流程returnnewPrometheusMetricsTracker(poolName,this.collectorRegistry);//返回IMetricsTracker接口的實(shí)現(xiàn)類}//自定義的CollectorprivateHikariCPCollectorgetCollector(){if(collector==null){//注冊(cè)到普羅米修斯收集中心collector=newHikariCPCollector().register(this.collectorRegistry);}returncollector;
通過上面的解釋可以知道在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ì)持有houKeepingExecutorService這個(gè)線程池對(duì)象,用于生產(chǎn)ProxyLeakTask對(duì)象,然后利用上面的houKeepingExecutorService延時(shí)運(yùn)行該對(duì)象里的run方法。該流程的觸發(fā)點(diǎn)在上面的流程1.1最后包裝成ProxyConnection對(duì)象的那一步,來看看具體的流程圖:
流程2.2
每次在流程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í)的clo連接,防止連接對(duì)象泄漏,開啟與否還是取決于業(yè)務(wù)是否需要,如果一定要開啟,如何設(shè)置leakDetectionThreshold的大小也是需要考慮的一件事。
八、主流程3:生成連接對(duì)象本節(jié)來講下主流程2里的createEntry方法,這個(gè)方法利用PoolBa里的DriverDataSource對(duì)象生成一個(gè)實(shí)際的連接對(duì)象(如果忘記DriverDatasource是哪里初始化的了,可以看下主流程2里PoolBa的initializeDataSource方法的作用),然后用PoolEntry類包裝成PoolEntry對(duì)象,現(xiàn)在來看下這個(gè)包裝類有哪些主要屬性:
finalclassPoolEntryimplementsIConcurrentBagEntry{privatestaticfinalLoggerLOGGER=LoggerFactory.getLogger(PoolEntry.class);//通過cas來修改state屬性privatestaticfinalAtomicIntegerFieldUpdaterstateUpdater;Connectionconnection;//實(shí)際的物理連接對(duì)象longlastAccesd;//觸發(fā)回收時(shí)刷新該時(shí)間,表示“最近一次使用時(shí)間”longlastBorrowed;//getConnection里borrow成功后刷新該時(shí)間,表示“最近一次借出的時(shí)間”@SuppressWarnings("FieldCanBeLocal")privatevolatileintstate=0;//連接狀態(tài),枚舉值:IN_USE(使用中)、NOT_IN_USE(閑置中)、REMOVED(已移除)、RESERVED(標(biāo)記為保留中)privatevolatilebooleanevict;//是否被標(biāo)記為廢棄,很多地方用到(比如流程1.1靠這個(gè)判斷連接是否已被廢棄,再比如主流程4里時(shí)鐘回?fù)軙r(shí)觸發(fā)的直接廢棄邏輯)privatevolatileScheduledFuture<?>endOfLife;//用于在超過連接生命周期(maxLifeTime)時(shí)廢棄連接的延時(shí)任務(wù),這里poolEntry要持有該對(duì)象,主要是因?yàn)樵趯?duì)象主動(dòng)被關(guān)閉時(shí)(意味著不需要在超過maxLifeTime時(shí)主動(dòng)失效了),需要cancel掉該任務(wù)privatefinalFastListopenStatements;//當(dāng)前該連接對(duì)象上生成的所有的statement對(duì)象,用于在回收連接時(shí)主動(dòng)關(guān)閉這些對(duì)象,防止存在漏關(guān)的statementprivatefinalHikariPoolhikariPool;//持有pool對(duì)象privatefinalbooleanisReadOnly;//是否為只讀privatefinalbooleanisAutoCommit;//是否存在事務(wù)}
上面就是整個(gè)PoolEntry對(duì)象里所有的屬性,這里再說下endOfLife對(duì)象,它是一個(gè)利用houKeepingExecutorService這個(gè)線程池對(duì)象做的延時(shí)任務(wù),這個(gè)延時(shí)任務(wù)一般在創(chuàng)建好連接對(duì)象后maxLifeTime左右的時(shí)間觸發(fā),具體來看下createEntry代碼:
privatePoolEntrycreatePoolEntry(){finalPoolEntrypoolEntry=newPoolEntry();//生成實(shí)際的連接對(duì)象finallongmaxLifetime=config.getMaxLifetime();//拿到配置好的maxLifetimeif(maxLifetime>0){//<=0的時(shí)候不啟用主動(dòng)過期策略//計(jì)算需要減去的隨機(jī)數(shù)//源注釋:varianceupto2.5%ofthemaxlifetimefinallongvariance=maxLifetime>10_000?ThreadLocalRandom.current().nextLong(maxLifetime/40):0;finallonglifetime=maxLifetime-variance;//生成實(shí)際的延時(shí)時(shí)間poolEntry.tFutureEol(houKeepingExecutorService.schedule(()->{//實(shí)際的延時(shí)任務(wù),這里直接觸發(fā)softEvictConnection,而softEvictConnection內(nèi)則會(huì)標(biāo)記該連接對(duì)象為廢棄狀態(tài),然后嘗試修改其狀態(tài)為STATE_RESERVED,若成功,則觸發(fā)cloConnection(對(duì)應(yīng)流程1.1.2)if(softEvictConnection(poolEntry,"(connectionhaspasdmaxLifetime)",fal/*notowner*/)){addBagItem(connectionBag.getWaitingThreadCount());//回收完畢后,連接池內(nèi)少了一個(gè)連接,就會(huì)嘗試新增一個(gè)連接對(duì)象}},lifetime,MILLISECONDS));//給endOfLife賦值,并且提交延時(shí)任務(wù),lifetime后觸發(fā)}returnpoolEntry;}//觸發(fā)新增連接任務(wù)publicvoidaddBagItem(finalintwaiting){//前排提示:addConnectionQueue和addConnectionExecutor的關(guān)系和初始化參考主流程2//當(dāng)添加連接的隊(duì)列里已提交的任務(wù)超過那些因?yàn)楂@取不到連接而發(fā)生阻塞的線程個(gè)數(shù)時(shí),就進(jìn)行提交連接新增連接的任務(wù)finalbooleanshouldAdd=waiting-addConnectionQueue.size()>=0;//Yes,>=isintentional.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ù)流程:
addConnectionExecutor的call流程
這個(gè)流程就是往連接池里加連接用的,跟createEntry結(jié)合起來說是因?yàn)檫@倆流程是緊密相關(guān)的,除此之外,主流程5(fillPool,擴(kuò)充連接池)也會(huì)觸發(fā)該任務(wù)。
九、主流程4:連接池縮容HikariCP會(huì)按照minIdle定時(shí)清理閑置過久的連接,這個(gè)定時(shí)任務(wù)在主流程2初始化連接池對(duì)象時(shí)被啟用,跟上面的流程一樣,也是利用houKeepingExecutorService這個(gè)線程池對(duì)象做該定時(shí)任務(wù)的執(zhí)行器。
來看下主流程2里是怎么啟用該任務(wù)的:
//houkeepingPeriodMs的默認(rèn)值是30s,所以定時(shí)任務(wù)的間隔為30sthis.houKeeperTask=houKeepingExecutorService.scheduleWithFixedDelay(newHouKeeper(),100L,houkeepingPeriodMs,MILLISECONDS);
那么本節(jié)主要來說下HouKeeper這個(gè)類,該類實(shí)現(xiàn)了Runnable接口,回收邏輯主要在其run方法內(nèi),來看看run方法的邏輯流程圖:
主流程4:連接池縮容
上面的流程就是HouKeeper的run方法里具體做的事情,由于系統(tǒng)時(shí)間回?fù)軙?huì)導(dǎo)致該定時(shí)任務(wù)回收一些連接時(shí)產(chǎn)生誤差,因此存在如下判斷:
//now就是當(dāng)前系統(tǒng)時(shí)間,previous就是上次觸發(fā)該任務(wù)時(shí)的時(shí)間,houkeepingPeriodMs就是隔多久觸發(fā)該任務(wù)一次//也就是說plusMillis(previous,houkeepingPeriodMs)表示當(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,houkeepingPeriodMs))
這是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)的連接finalListnotInU=connectionBag.values(STATE_NOT_IN_USE);//計(jì)算出需要被檢查閑置時(shí)間的數(shù)量,簡(jiǎn)單來說,池內(nèi)需要保證最小minIdle個(gè)連接活著,所以需要計(jì)算出超出這個(gè)范圍的閑置對(duì)象進(jìn)行檢查inttoRemove=notInU.size()-config.getMinIdle();for(PoolEntryentry:notInU){//在檢查范圍內(nèi),且閑置時(shí)間超出idleTimeout,然后嘗試將連接對(duì)象狀態(tài)由STATE_NOT_IN_USE變?yōu)镾TATE_RESERVED成功if(toRemove>0&&elapdMillis(entry.lastAccesd,now)>idleTimeout&&connectionBag.rerve(entry)){cloConnection(entry,"(connectionhaspasdidleTimeout)");//滿足上述條件,進(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方法即可privatefinalPoolEntryCreatorpoolEntryCreator=newPoolEntryCreator(null);privatefinalPoolEntryCreatorpostFillPoolEntryCreator=newPoolEntryCreator("Afteradding");privatesynchronizedvoidfillPool(){//這個(gè)判斷就是根據(jù)當(dāng)前池子里相關(guān)數(shù)據(jù),推算出需要擴(kuò)充的連接數(shù),//判斷方式就是利用最大連接數(shù)跟當(dāng)前連接總數(shù)的差值,與最小連接數(shù)與當(dāng)前池內(nèi)閑置的連接數(shù)的差值,取其最小的那一個(gè)得到intneedAdd=Math.min(maxPoolSize-connectionBag.size(),minIdle-connectionBag.getCount(STATE_NOT_IN_USE));//減去當(dāng)前排隊(duì)的任務(wù),就是最終需要新增的連接數(shù)finalintconnectionsToAdd=needAdd-addConnectionQueue.size();for(inti=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里的clo方法:
publicfinalvoidclo()throwsSQLException{//原注釋:Closingstatementscancauconnectioneviction,sothismustrunbeforetheconditionalbelowcloStatements();//此連接對(duì)象在業(yè)務(wù)方使用過程中產(chǎn)生的所有statement對(duì)象,進(jìn)行統(tǒng)一clo,防止漏clo的情況if(delegate!=ClodConnection.CLOSED_CONNECTION){leakTask.cancel();//取消連接泄漏檢查任務(wù),參考流程2.2try{if(isCommitStateDirty&&!isAutoCommit){//在存在執(zhí)行語(yǔ)句后并且還打開了事務(wù),調(diào)用clo時(shí)需要主動(dòng)回滾事務(wù)delegate.rollback();//回滾lastAccess=currentTime();//刷新"最后一次使用時(shí)間"}}finally{delegate=ClodConnection.CLOSED_CONNECTION;poolEntry.recycle(lastAccess);//觸發(fā)回收}}}
這個(gè)就是ProxyConnection里的clo方法,可以看到它最終會(huì)調(diào)用PoolEntry的recycle方法進(jìn)行回收,除此之外,連接對(duì)象的最后一次使用時(shí)間也是在這個(gè)時(shí)候刷新的,該時(shí)間是個(gè)很重要的屬性,可以用來判斷一個(gè)連接對(duì)象的閑置時(shí)間,來看下PoolEntry的recycle方法:
voidrecycle(finallonglastAccesd){if(connection!=null){this.lastAccesd=lastAccesd;//刷新最后使用時(shí)間hikariPool.recycle(this);//觸發(fā)HikariPool的回收方法,把自己傳過去}}
之前有說過,每個(gè)PoolEntry對(duì)象都持有HikariPool的對(duì)象,方便觸發(fā)連接池的一些操作,由上述代碼可以看到,最終還是會(huì)觸發(fā)HikariPool里的recycle方法,再來看下HikariPool的recycle方法:
voidrecycle(finalPoolEntrypoolEntry){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.rerve完成對(duì)連接對(duì)象狀態(tài)的修改,然后會(huì)通過流程1.1.2觸發(fā)關(guān)閉和移除連接操作。通過觸發(fā)點(diǎn)整理,可以知道該結(jié)構(gòu)里的主要方法,就是上面觸發(fā)點(diǎn)里標(biāo)記為標(biāo)簽色的部分,然后來具體看下該類的基本定義和主要方法:
publicclassConcurrentBag<TextendsIConcurrentBagEntry>implementsAutoCloable{privatefinalCopyOnWriteArrayList<T>sharedList;//最終存放PoolEntry對(duì)象的地方,它是一個(gè)CopyOnWriteArrayListprivatefinalbooleanweakThreadLocals;//默認(rèn)fal,為true時(shí)可以讓一個(gè)連接對(duì)象在下方threadList里的list內(nèi)處于弱引用狀態(tài),防止內(nèi)存泄漏(參見備注1)privatefinalThreadLocal<List<Object>>threadList;//線程級(jí)的緩存,從sharedList拿到的連接對(duì)象,會(huì)被緩存進(jìn)當(dāng)前線程內(nèi),borrow時(shí)會(huì)先從緩存中拿,從而達(dá)到池內(nèi)無鎖實(shí)現(xiàn)privatefinalIBagStateListenerlistener;//內(nèi)部接口,HikariPool實(shí)現(xiàn)了該接口,主要用于ConcurrentBag主動(dòng)通知HikariPool觸發(fā)添加連接對(duì)象的異步操作(也就是主流程3里的addConnectionExecutor所觸發(fā)的流程)privatefinalAtomicIntegerwaiters;//當(dāng)前因?yàn)楂@取不到連接而發(fā)生阻塞的業(yè)務(wù)線程數(shù),這個(gè)在之前的流程里也出現(xiàn)過,比如主流程3里addBagItem就會(huì)根據(jù)該指標(biāo)進(jìn)行判斷是否需要新增連接privatevolatilebooleanclod;//標(biāo)記當(dāng)前ConcurrentBag是否已被關(guān)閉privatefinalSynchronousQueue<T>handoffQueue;//這是個(gè)即產(chǎn)即銷的隊(duì)列,用于在連接不夠用時(shí),及時(shí)獲取到add方法里新創(chuàng)建的連接對(duì)象,詳情可以參考下面borrow和add的代碼//內(nèi)部接口,PoolEntry類實(shí)現(xiàn)了該接口publicinterfaceIConcurrentBagEntry{//連接對(duì)象的狀態(tài),前面的流程很多地方都已經(jīng)涉及到了,比如主流程4的縮容intSTATE_NOT_IN_USE=0;//閑置intSTATE_IN_USE=1;//使用中intSTATE_REMOVED=-1;//已廢棄intSTATE_RESERVED=-2;//標(biāo)記保留,介于閑置和廢棄之間的中間狀態(tài),主要由縮容那里觸發(fā)修改booleancompareAndSet(intexpectState,intnewState);//嘗試?yán)胏as修改連接對(duì)象的狀態(tài)值voidtState(intnewState);//設(shè)置狀態(tài)值intgetState();//獲取狀態(tài)值}//參考上面listener屬性的解釋publicinterfaceIBagStateListener{voidaddBagItem(intwaiting);}//獲取連接方法publicTborrow(longtimeout,finalTimeUnittimeUnit){//省略...}//回收連接方法publicvoidrequite(finalTbagEntry){//省略...}//添加連接方法publicvoidadd(finalTbagEntry){//省略...}//移除連接方法publicbooleanremove(finalTbagEntry){//省略...}//根據(jù)連接狀態(tài)值獲取當(dāng)前池子內(nèi)所有符合條件的連接集合publicListvalues(finalintstate){//省略...}//獲取當(dāng)前池子內(nèi)所有的連接publicListvalues(){//省略...}//利用cas把傳入的連接對(duì)象的state從STATE_NOT_IN_USE變?yōu)镾TATE_RESERVEDpublicbooleanrerve(finalTbagEntry){//省略...}//獲取當(dāng)前池子內(nèi)符合傳入狀態(tài)值的連接數(shù)量publicintgetCount(finalintstate){//省略...}}
從這個(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就是利用該方法獲取連接的,下面來看下該方法做了什么:
publicTborrow(longtimeout,finalTimeUnittimeUnit)throwsInterruptedException{//源注釋:Trythethread-locallistfirstfinalList<Object>list=threadList.get();//首先從當(dāng)前線程的緩存里拿到之前被緩存進(jìn)來的連接對(duì)象集合for(inti=list.size()-1;i>=0;i--){finalObjectentry=list.remove(i);//先移除,回收方法那里會(huì)再次add進(jìn)來finalTbagEntry=weakThreadLocals?((WeakReference<T>)entry).get():(T)entry;//默認(rèn)不啟用弱引用//獲取到對(duì)象后,通過cas嘗試把其狀態(tài)從STATE_NOT_IN_USE變?yōu)镾TATE_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)){returnbagEntry;//cas設(shè)置成功后,表示當(dāng)前線程繞過其他線程干擾,成功獲取到該連接對(duì)象,直接返回}}//源注釋:Otherwi,scanthesharedlist...thenpollthehandoffqueuefinalintwaiting=waiters.incrementAndGet();//如果緩存內(nèi)找不到一個(gè)可用的連接對(duì)象,則認(rèn)為需要“回源”,waiters+1try{for(TbagEntry:sharedList){//循環(huán)sharedList,嘗試把連接狀態(tài)值從STATE_NOT_IN_USE變?yōu)镾TATE_IN_USEif(bagEntry.compareAndSet(STATE_NOT_IN_USE,STATE_IN_USE)){//源注釋:Ifwemayhavestolenanotherwaiter'sconnection,requestanotherbagadd.if(waiting>1){//阻塞線程數(shù)大于1時(shí),需要觸發(fā)HikariPool的addBagItem方法來進(jìn)行添加連接入池,這個(gè)方法的實(shí)現(xiàn)參考主流程3listener.addBagItem(waiting-1);}returnbagEntry;//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{finallongstart=currentTime();//嘗試從handoffQueue隊(duì)列里獲取最新被加進(jìn)來的連接對(duì)象(一般新入的連接對(duì)象除了加進(jìn)sharedList之外,還會(huì)被offer進(jìn)該隊(duì)列)finalTbagEntry=handoffQueue.poll(timeout,NANOSECONDS);//如果超出指定時(shí)間后仍然沒有獲取到可用的連接對(duì)象,或者獲取到對(duì)象后通過cas設(shè)置成功,這兩種情況都不需要重試,直接返回對(duì)象if(bagEntry==null||bagEntry.compareAndSet(STATE_NOT_IN_USE,STATE_IN_USE)){returnbagEntry;}//走到這里說明從隊(duì)列內(nèi)獲取到了連接對(duì)象,但是cas設(shè)置失敗,說明又該對(duì)象又被其他線程率先拿去用了,若時(shí)間還夠,則再次嘗試獲取timeout-=elapdNanos(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還大returnnull;//超時(shí),仍然返回null}finally{waiters.decrementAndGet();//這一步出去后,HikariPool收到borrow的結(jié)果,算是走出阻塞,所以waiters-1}}
仔細(xì)看下注釋,該過程大致分成三個(gè)主要步驟:
從線程緩存獲取連接獲取不到再?gòu)膕haredList里獲取都獲取不到則觸發(fā)添加連接邏輯,并嘗試從隊(duì)列里獲取新生成的連接對(duì)象12.2:add這個(gè)流程會(huì)添加一個(gè)連接對(duì)象進(jìn)入bag,通常由主流程3里的addBagItem方法通過addConnectionExecutor異步任務(wù)觸發(fā)添加操作,該方法主流程如下:
publicvoidadd(finalTbagEntry){sharedList.add(bagEntry);//直接加到sharedList里去//源注釋:spinuntilathreadtakesitornonearewaiting//參考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,具體代碼如下:
publicvoidrequite(finalTbagEntry){bagEntry.tState(STATE_NOT_IN_USE);//回收意味著使用完畢,更改state為STATE_NOT_IN_USE狀態(tài)for(inti=0;waiters.get()>0;i++){//如果存在等待線程的話,嘗試傳給隊(duì)列,讓borrow獲取if(bagEntry.getState()!=STATE_NOT_IN_USE||handoffQueue.offer(bagEntry)){return;}elif((i&0xff)==0xff){parkNanos(MICROSECONDS.toNanos(10));}el{yield();}}finalList<Object>threadLocalList=threadList.get();if(threadLocalList.size()<50){//線程內(nèi)連接集合的緩存最多50個(gè),這里回收連接時(shí)會(huì)再次加進(jìn)當(dāng)前線程的緩存里,方便下次borrow獲取threadLocalList.add(weakThreadLocals?newWeakReference<>(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,代碼如下:
publicbooleanremove(finalTbagEntry){//下面兩個(gè)cas操作,都是從其他狀態(tài)變?yōu)橐瞥隣顟B(tài),任意一個(gè)成功,都不會(huì)走到下面的warnlogif(!bagEntry.compareAndSet(STATE_IN_USE,STATE_REMOVED)&&!bagEntry.compareAndSet(STATE_RESERVED,STATE_REMOVED)&&!clod){LOGGER.warn("Attempttoremoveanobjectfromthebagthatwasnotborrowedorrerved:{}",bagEntry);returnfal;}//直接從sharedList移除掉finalbooleanremoved=sharedList.remove(bagEntry);if(!removed&&!clod){LOGGER.warn("Attempttoremoveanobjectfromthebagthatdoesnotexist:{}",bagEntry);}returnremoved;}
這里需要注意的是,移除時(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,代碼如下:
publicListvalues(finalintstate){//過濾出來符合狀態(tài)值的對(duì)象集合逆序后返回出去finalListlist=sharedList.stream().filter(e->e.getState()==state).collect(Collectors.toList());Collections.rever(list);returnlist;}publicListvalues(){//返回全部連接對(duì)象(注意下方clone為淺拷貝)return(List)sharedList.clone();}12.6:rerve
該方法單純將連接對(duì)象的狀態(tài)值由STATE_NOT_IN_USE修改為STATE_RESERVED,觸發(fā)點(diǎn)仍然是主流程4,縮容時(shí)使用,代碼如下:
publicbooleanrerve(finalTbagEntry){returnbagEntry.compareAndSet(STATE_NOT_IN_USE,STATE_RESERVED);}12.7:getCount
該方法用于返回池內(nèi)符合某個(gè)狀態(tài)值的連接的總數(shù)量,觸發(fā)點(diǎn)為主流程5,擴(kuò)充連接池時(shí)用于獲取閑置連接總數(shù),代碼如下:
publicintgetCount(finalintstate){intcount=0;for(IConcurrentBagEntrye:sharedList){if(e.getState()==state){count++;}}returncount;}
以上就是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具體做了哪些不一樣的操作。
轉(zhuǎn)載于:https://mp.weixin.qq.com/s/-5ivWM6OK4RrJ1Tsn0oNjw
本文發(fā)布于:2023-02-28 21:18:00,感謝您對(duì)本站的認(rèn)可!
本文鏈接:http://www.newhan.cn/zhishi/a/1677745503105036.html
版權(quán)聲明:本站內(nèi)容均來自互聯(lián)網(wǎng),僅供演示用,請(qǐng)勿用于商業(yè)和其他非法用途。如果侵犯了您的權(quán)益請(qǐng)與我們聯(lián)系,我們將在24小時(shí)內(nèi)刪除。
本文word下載地址:hikari(hikari是什么意思).doc
本文 PDF 下載地址:hikari(hikari是什么意思).pdf
| 留言與評(píng)論(共有 0 條評(píng)論) |