SpringBoot?HikariCP配置項(xiàng)及源碼解析
前言
在SpringBoot2.0之后,采用的默認(rèn)數(shù)據(jù)庫(kù)連接池就是Hikari,是一款非常強(qiáng)大,高效,并且號(hào)稱(chēng)“史上最快連接池”。我們知道的連接池有C3P0,DBCP,Druid它們都比較成熟穩(wěn)定,但性能不是十分好。
我們?cè)谌粘5木幋a中,通常會(huì)將一些對(duì)象保存起來(lái),這主要考慮的是對(duì)象的創(chuàng)建成本;比如像線(xiàn)程資源、數(shù)據(jù)庫(kù)連接資源或者 TCP 連接等,這類(lèi)對(duì)象的初始化通常要花費(fèi)比較長(zhǎng)的時(shí)間,如果頻繁地申請(qǐng)和銷(xiāo)毀,就會(huì)耗費(fèi)大量的系統(tǒng)資源,造成不必要的性能損失,于是在Java 中,池化技術(shù)應(yīng)用非常廣泛。在軟件行開(kāi)發(fā)中,軟件的性能是占主導(dǎo)地位的,于是HikariCP就在眾多數(shù)據(jù)庫(kù)連接池中脫穎而出。
為什么HikariCP性能高
- 優(yōu)化代理和攔截器:減少代碼。
- 字節(jié)碼精簡(jiǎn) :優(yōu)化代碼(
HikariCP
利用了一個(gè)第三方的Java字節(jié)碼修改類(lèi)庫(kù)Javassist
來(lái)生成委托實(shí)現(xiàn)動(dòng)態(tài)代理,動(dòng)態(tài)代理的實(shí)現(xiàn)在ProxyFactor
y類(lèi)),直到編譯后的字節(jié)碼最少,這樣,CPU
緩存可以加載更多的程序代碼。 - 通過(guò)代碼設(shè)計(jì)和優(yōu)化大幅減少線(xiàn)程間的鎖競(jìng)爭(zhēng)。這點(diǎn)主要通過(guò)
ConcurrentBag
來(lái)實(shí)現(xiàn)。 - 自定義數(shù)組類(lèi)型(
FastStatementList
)代替ArrayList:避免每次get()調(diào)用都要進(jìn)行range check,避免調(diào)用remove()時(shí)的從頭到尾的掃描,相對(duì)與ArrayList
極大地提升了性能,而其中的區(qū)別是,ArrayList
在每次執(zhí)行get(Index)
方法時(shí),都需要對(duì)List
的范圍進(jìn)行檢查,而FastStatementList
不需要,在能確保范圍的合法性的情況下,可以省去范圍檢查的開(kāi)銷(xiāo)。自定義集合類(lèi)型(ConcurrentBag
):支持快速插入和刪除,特別是在同一線(xiàn)程既添加又刪除項(xiàng)時(shí),提高并發(fā)讀寫(xiě)的效率; - 關(guān)于
Connection的
操作:另外在Java
代碼中,很多都是在使用完之后直接關(guān)閉連接,以前都是從頭到尾遍歷,來(lái)關(guān)閉對(duì)應(yīng)的Connection
,而HikariCP
則是從尾部對(duì)Connection
集合進(jìn)行掃描,整體上來(lái)說(shuō),從尾部開(kāi)始的性能更好一些。 - 針對(duì)連接中斷的情況:比其他CP響應(yīng)時(shí)間上有了極好的優(yōu)化,響應(yīng)時(shí)間為5S,會(huì)拋出
SqlException
異常,并且后續(xù)的getConnection()可以正常進(jìn)行
下面為大家附上一張官方的性能測(cè)試圖,我們可以從圖上很直觀的看出HikariCP的性能卓越:
常用配置項(xiàng)
autoCommit
控制從池返回的連接的默認(rèn)自動(dòng)提交行為,默認(rèn)為true
connectionTimeout
控制客戶(hù)端等待來(lái)自池的連接的最大毫秒數(shù)。
如果在沒(méi)有連接可用的情況下超過(guò)此時(shí)間,則將拋出 SQLException
??山邮艿淖畹瓦B接超時(shí)時(shí)間為 250 毫秒
。默認(rèn)值:30000(30 秒)
idleTimeout
連接允許在池中閑置的最長(zhǎng)時(shí)間
如果idleTimeout+1秒>maxLifetime
且 maxLifetime>0,則會(huì)被重置為0(代表永遠(yuǎn)不會(huì)退出);如果idleTimeout!=0且小于10秒,則會(huì)被重置為10秒
這是HikariCP
用來(lái)判斷是否應(yīng)該從連接池移除空閑連接的一個(gè)重要的配置。負(fù)責(zé)剔除的也還是HouseKeeper
這個(gè)定時(shí)任務(wù),值為0時(shí),HouseKeeper
不會(huì)移除空閑連接,直到到達(dá)maxLifetime
后,才會(huì)移除,默認(rèn)值也就是0。
正常情況下,HouseKeeper
會(huì)找到所有狀態(tài)為空閑的連接隊(duì)列,遍歷一遍,將空閑超時(shí)到達(dá)idleTimeout
且未超過(guò)minimumIdle
數(shù)量的連接的批量移除。
maxLifetime
池中連接最長(zhǎng)生命周期;如果不等于0且小于30秒則會(huì)被重置回30分鐘
了解這個(gè)值的作用前,先了解一下MySQLwait_timeout
的作用:MySQL 為了防止空閑連接浪費(fèi),占用資源,在超過(guò)wait_timeout
時(shí)間后,會(huì)主動(dòng)關(guān)閉該連接,清理資源;默認(rèn)是28800s
,也就是8小時(shí)。簡(jiǎn)而言之就是MySQL會(huì)在某個(gè)連接超過(guò)8小時(shí)還沒(méi)有任何請(qǐng)求時(shí)自動(dòng)斷開(kāi)連接,但是HikariCP如何知道池子里的連接有沒(méi)有超過(guò)這個(gè)時(shí)間呢?所以就有了maxLifetime
,配置后HikariCP會(huì)把空閑鏈接超過(guò)這個(gè)時(shí)間的給剔除掉,防止獲取到已經(jīng)關(guān)閉的連接導(dǎo)致異常。
connectionTestQuery
將在從池中向您提供連接之前執(zhí)行的查詢(xún),以驗(yàn)證與數(shù)據(jù)庫(kù)的連接是否仍然有效,如select 1
minimumIdle
池中維護(hù)的最小空閑連接數(shù);minIdle<0或者minIdle>maxPoolSize,則被重置為maxPoolSize
在HikariCP Pool
創(chuàng)建時(shí),會(huì)啟動(dòng)一個(gè)HouseKeeper
定時(shí)任務(wù),每隔30s,判斷空閑線(xiàn)程數(shù)低于minimumIdle
,并且當(dāng)前線(xiàn)程池總連接數(shù)小于maximumPoolSize
,就建立和MySQL的一個(gè)長(zhǎng)連接,然后加入到連接池中。官方建議minimumIdle
和maximumPoolSize
保持一致。 因?yàn)?code>HikariCP的HouseKeeper
在發(fā)現(xiàn)idleTimeout>0 并且 minimumIdle < maximumPoolSize時(shí),先會(huì)去掃描一遍需要移除空閑連接,和MySQL斷開(kāi)連接。然后再一次性補(bǔ)滿(mǎn)空閑連接數(shù)至到minimumIdle
。
maximumPoolSize
池中最大連接數(shù),其實(shí)就是線(xiàn)程池中隊(duì)列的大小,默認(rèn)大小為10(包括閑置和使用中的連接)
如果maxPoolSize
小于1,則會(huì)被重置。當(dāng)minIdle<=0被重置為DEFAULT_POOL_SIZE
則為10;如果minIdle>0則重置為minIdle
的值
HikariCP架構(gòu)
分析源碼之前,先給大家介紹一下HikariCP的整體架構(gòu),整體架構(gòu)和DBCP2 的有點(diǎn)類(lèi)似(由此可見(jiàn) HikariCP 與 DBCP2 性能差異并不是由于架構(gòu)設(shè)計(jì)),下面我總結(jié)了幾點(diǎn),來(lái)和大家一起探討下:
HikariCP
通過(guò)JMX
調(diào)用HikariPoolMXBean
來(lái)獲取連接池的連接數(shù)、獲取等待連接的線(xiàn)程數(shù)、丟棄未使用連接、掛起和恢復(fù)連接池等。HikariCP
通過(guò)JMX
調(diào)用HikariConfigMXBean
來(lái)動(dòng)態(tài)修改配置。HikariCP
使用HikariConfig
加載配置文件,一般會(huì)作為入?yún)?lái)構(gòu)造 HikariDataSource 對(duì)象。HikariPool
是一個(gè)非常重要的類(lèi),它負(fù)責(zé)管理連接,涉及到比較多的代碼邏輯。HikariDataSource
主要用于操作HikariPool
獲取連接。ConcurrentBag
用于優(yōu)化大幅減少線(xiàn)程間的鎖競(jìng)爭(zhēng)。PoolBase
是HikariPool
的父類(lèi),主要負(fù)責(zé)操作實(shí)際的DataSource
獲取連接,并設(shè)置連接的一些屬性。
源碼解析
HikariConfig
HikariConfig
保存了所有連接池配置,另外實(shí)現(xiàn)了HikariConfigMXBean
接口,有些配置可以利用JMX
運(yùn)行時(shí)變更。核心配置項(xiàng)屬性會(huì)在下面給大家介紹,這邊Dong哥就簡(jiǎn)單介紹一下了。
HikariPool
getConnection
public Connection getConnection(final long hardTimeout) throws SQLException { //這里是防止線(xiàn)程池處于暫停狀態(tài)(通常不允許線(xiàn)程池可暫停) suspendResumeLock.acquire(); final long startTime = currentTime(); try { long timeout = hardTimeout; do { //PoolEntry 用于跟蹤connection實(shí)例,里面包裝了Connection; //從connectionBag中獲取一個(gè)對(duì)象,并且檢測(cè)是否可用 PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS); if (poolEntry == null) { break; // We timed out... break and throw exception } final long now = currentTime(); //1、已被標(biāo)記為驅(qū)逐 2、已超過(guò)最大存活時(shí)間 3、鏈接已死 if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > ALIVE_BYPASS_WINDOW_MS && !isConnectionAlive(poolEntry.connection))) { closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE); //刷新超時(shí)時(shí)間 timeout = hardTimeout - elapsedMillis(startTime); } else { metricsTracker.recordBorrowStats(poolEntry, startTime); return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now); } //如果沒(méi)超時(shí)則再次獲取 } while (timeout > 0L); //超時(shí)時(shí)間到仍未獲取到鏈接則拋出 TimeoutException metricsTracker.recordBorrowTimeoutStats(startTime); throw createTimeoutException(startTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new SQLException(poolName + " - Interrupted during connection acquisition", e); } finally { suspendResumeLock.release(); } }
校驗(yàn):
- isMarkedEvicted:檢查當(dāng)前鏈接是否已被驅(qū)逐
- elapsedMillis(poolEntry.lastAccessed, now):檢查鏈接是否超過(guò)最大存活時(shí)間(
maxLifetime
配置時(shí)間)
/** * startTime 上次使用時(shí)間 * endTime 當(dāng)前時(shí)間 */ static long elapsedMillis(long startTime, long endTime) { return CLOCK.elapsedMillis0(startTime, endTime); }
- isConnectionAlive:連接是否還是存活狀態(tài)
boolean isConnectionAlive(final Connection connection) { try { try { //如果支持Connection networkTimeout,則優(yōu)先使用并設(shè)置 setNetworkTimeout(connection, validationTimeout); final int validationSeconds = (int) Math.max(1000L, validationTimeout) / 1000; //如果jdbc實(shí)現(xiàn)支持jdbc4 則使用jdbc4 Connection的isValid方法檢測(cè) if (isUseJdbc4Validation) { return connection.isValid(validationSeconds); } //查詢(xún)數(shù)據(jù)庫(kù)檢測(cè)連接可用性 try (Statement statement = connection.createStatement()) { //如果不支持Connection networkTimeout 則設(shè)置Statement queryTimeout if (isNetworkTimeoutSupported != TRUE) { setQueryTimeout(statement, validationSeconds); } statement.execute(config.getConnectionTestQuery()); } } finally { setNetworkTimeout(connection, networkTimeout); if (isIsolateInternalQueries && !isAutoCommit) { connection.rollback(); } } return true; } catch (Exception e) { lastConnectionFailure.set(e); LOGGER.warn("{} - Failed to validate connection {} ({}). Possibly consider using a shorter maxLifetime value.", poolName, connection, e.getMessage()); //捕獲到異常,說(shuō)明鏈接不可用。(connection is unavailable) return false; } }
HouseKeeper
HouseKeeper
負(fù)責(zé)保持,我們始終有minimumIdle空閑鏈接可用
private final class HouseKeeper implements Runnable { //默認(rèn)30s,執(zhí)行一次 private volatile long previous = plusMillis(currentTime(), -HOUSEKEEPING_PERIOD_MS); @Override public void run() { try { //省略...... String afterPrefix = "Pool "; if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) { logPoolState("Before cleanup "); afterPrefix = "After cleanup "; //空閑鏈接數(shù) final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE); int toRemove = notInUse.size() - config.getMinimumIdle(); for (PoolEntry entry : notInUse) { if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) { //關(guān)閉過(guò)多的空閑超時(shí)鏈接 closeConnection(entry, "(connection has passed idleTimeout)"); toRemove--; } } } //記錄pool狀態(tài)信息 logPoolState(afterPrefix); //補(bǔ)充空閑鏈接 fillPool(); } catch (Exception e) { LOGGER.error("Unexpected exception in housekeeping task", e); } } }
HouseKeeper
其實(shí)是一個(gè)線(xiàn)程,也是寫(xiě)在HikariPool
類(lèi)里面的一個(gè)內(nèi)部類(lèi),主要負(fù)責(zé)保持 minimumIdle
的空閑鏈接。HouseKeeper
也用到了validationTimeout
, 并且會(huì)根據(jù)minimumIdle
配置,通過(guò)fill 或者 remove保持最少空閑鏈接數(shù)。HouseKeeper
線(xiàn)程初始化:
public HikariPool(final HikariConfig config) { super(config); this.connectionBag = new ConcurrentBag<>(this); this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK; //執(zhí)行初始化 this.houseKeepingExecutorService = initializeHouseKeepingExecutorService(); //省略...... } }
private ScheduledExecutorService initializeHouseKeepingExecutorService() { if (this.config.getScheduledExecutor() == null) { ThreadFactory threadFactory = (ThreadFactory)Optional.ofNullable(this.config.getThreadFactory()).orElseGet(() -> { return new DefaultThreadFactory(this.poolName + " housekeeper", true); }); //ScheduledThreadPoolExecutor是ThreadPoolExecutor類(lèi)的子類(lèi),Java推薦僅在開(kāi)發(fā)定時(shí)任務(wù)程序時(shí)采用ScheduledThreadPoolExecutor類(lèi) ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new DiscardPolicy()); //傳入false,則執(zhí)行shutdown()方法之后,待處理的任務(wù)將不會(huì)被執(zhí)行 executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); //取消任務(wù)后,判斷是否需要從阻塞隊(duì)列中移除任務(wù) executor.setRemoveOnCancelPolicy(true); return executor; } else { return this.config.getScheduledExecutor(); } }
HikariDataSource
HikariDataSource
非常重要,主要用于操作HikariPool
獲取連接,并且能夠清除空閑連接。
public class HikariDataSource extends HikariConfig implements DataSource, Closeable { private final AtomicBoolean isShutdown = new AtomicBoolean(); //final修飾,構(gòu)造時(shí)決定,如果使用無(wú)參構(gòu)造為null,使用有參構(gòu)造和pool一樣 private final HikariPool fastPathPool; //volatile修飾,無(wú)參構(gòu)造不會(huì)設(shè)置pool,在getConnection時(shí)構(gòu)造pool,有參構(gòu)造和fastPathPool一樣。 private volatile HikariPool pool; public HikariDataSource() { super(); fastPathPool = null; } public HikariDataSource(HikariConfig configuration) { configuration.validate(); configuration.copyStateTo(this); pool = fastPathPool = new HikariPool(this); this.seal(); } }
以上就是SpringBoot HikariCP配置項(xiàng)及源碼解析的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot HikariCP配置的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ConcurrentHashMap線(xiàn)程安全及實(shí)現(xiàn)原理實(shí)例解析
這篇文章主要介紹了ConcurrentHashMap線(xiàn)程安全及實(shí)現(xiàn)原理實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之高級(jí)排序
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之高級(jí)排序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-01-01SpringBoot下RabbitMq實(shí)現(xiàn)定時(shí)任務(wù)
這篇文章主要為大家詳細(xì)介紹了SpringBoot下RabbitMq實(shí)現(xiàn)定時(shí)任務(wù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-11-11java調(diào)用opencv身份證號(hào)識(shí)別詳解
這篇文章主要為大家詳細(xì)介紹了java如何調(diào)用opencv實(shí)現(xiàn)身份證號(hào)的識(shí)別,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03java實(shí)現(xiàn)選中刪除功能的實(shí)例代碼
這篇文章主要介紹了java實(shí)現(xiàn)選中刪除功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02使用Spring Data JDBC實(shí)現(xiàn)DDD聚合的示例代碼
這篇文章主要介紹了使用Spring Data JDBC實(shí)現(xiàn)DDD聚合的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-09-09Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟詳解
這篇文章主要介紹了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟,本文分步驟給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09詳解Spring連接數(shù)據(jù)庫(kù)的幾種常用的方式
本篇文章主要介紹了Spring連接數(shù)據(jù)庫(kù)的幾種常用的方式,具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12