SpringBoot?HikariCP配置項及源碼解析
前言
在SpringBoot2.0之后,采用的默認數(shù)據(jù)庫連接池就是Hikari,是一款非常強大,高效,并且號稱“史上最快連接池”。我們知道的連接池有C3P0,DBCP,Druid它們都比較成熟穩(wěn)定,但性能不是十分好。
我們在日常的編碼中,通常會將一些對象保存起來,這主要考慮的是對象的創(chuàng)建成本;比如像線程資源、數(shù)據(jù)庫連接資源或者 TCP 連接等,這類對象的初始化通常要花費比較長的時間,如果頻繁地申請和銷毀,就會耗費大量的系統(tǒng)資源,造成不必要的性能損失,于是在Java 中,池化技術(shù)應用非常廣泛。在軟件行開發(fā)中,軟件的性能是占主導地位的,于是HikariCP就在眾多數(shù)據(jù)庫連接池中脫穎而出。
為什么HikariCP性能高
- 優(yōu)化代理和攔截器:減少代碼。
- 字節(jié)碼精簡 :優(yōu)化代碼(
HikariCP利用了一個第三方的Java字節(jié)碼修改類庫Javassist來生成委托實現(xiàn)動態(tài)代理,動態(tài)代理的實現(xiàn)在ProxyFactory類),直到編譯后的字節(jié)碼最少,這樣,CPU緩存可以加載更多的程序代碼。 - 通過代碼設(shè)計和優(yōu)化大幅減少線程間的鎖競爭。這點主要通過
ConcurrentBag來實現(xiàn)。 - 自定義數(shù)組類型(
FastStatementList)代替ArrayList:避免每次get()調(diào)用都要進行range check,避免調(diào)用remove()時的從頭到尾的掃描,相對與ArrayList極大地提升了性能,而其中的區(qū)別是,ArrayList在每次執(zhí)行get(Index)方法時,都需要對List的范圍進行檢查,而FastStatementList不需要,在能確保范圍的合法性的情況下,可以省去范圍檢查的開銷。自定義集合類型(ConcurrentBag):支持快速插入和刪除,特別是在同一線程既添加又刪除項時,提高并發(fā)讀寫的效率; - 關(guān)于
Connection的操作:另外在Java代碼中,很多都是在使用完之后直接關(guān)閉連接,以前都是從頭到尾遍歷,來關(guān)閉對應的Connection,而HikariCP則是從尾部對Connection集合進行掃描,整體上來說,從尾部開始的性能更好一些。 - 針對連接中斷的情況:比其他CP響應時間上有了極好的優(yōu)化,響應時間為5S,會拋出
SqlException異常,并且后續(xù)的getConnection()可以正常進行
下面為大家附上一張官方的性能測試圖,我們可以從圖上很直觀的看出HikariCP的性能卓越:

常用配置項
autoCommit
控制從池返回的連接的默認自動提交行為,默認為true
connectionTimeout
控制客戶端等待來自池的連接的最大毫秒數(shù)。
如果在沒有連接可用的情況下超過此時間,則將拋出 SQLException。可接受的最低連接超時時間為 250 毫秒。默認值:30000(30 秒)
idleTimeout
連接允許在池中閑置的最長時間
如果idleTimeout+1秒>maxLifetime 且 maxLifetime>0,則會被重置為0(代表永遠不會退出);如果idleTimeout!=0且小于10秒,則會被重置為10秒
這是HikariCP用來判斷是否應該從連接池移除空閑連接的一個重要的配置。負責剔除的也還是HouseKeeper這個定時任務(wù),值為0時,HouseKeeper不會移除空閑連接,直到到達maxLifetime后,才會移除,默認值也就是0。
正常情況下,HouseKeeper會找到所有狀態(tài)為空閑的連接隊列,遍歷一遍,將空閑超時到達idleTimeout且未超過minimumIdle數(shù)量的連接的批量移除。
maxLifetime
池中連接最長生命周期;如果不等于0且小于30秒則會被重置回30分鐘
了解這個值的作用前,先了解一下MySQLwait_timeout的作用:MySQL 為了防止空閑連接浪費,占用資源,在超過wait_timeout時間后,會主動關(guān)閉該連接,清理資源;默認是28800s,也就是8小時。簡而言之就是MySQL會在某個連接超過8小時還沒有任何請求時自動斷開連接,但是HikariCP如何知道池子里的連接有沒有超過這個時間呢?所以就有了maxLifetime,配置后HikariCP會把空閑鏈接超過這個時間的給剔除掉,防止獲取到已經(jīng)關(guān)閉的連接導致異常。
connectionTestQuery
將在從池中向您提供連接之前執(zhí)行的查詢,以驗證與數(shù)據(jù)庫的連接是否仍然有效,如select 1
minimumIdle
池中維護的最小空閑連接數(shù);minIdle<0或者minIdle>maxPoolSize,則被重置為maxPoolSize
在HikariCP Pool創(chuàng)建時,會啟動一個HouseKeeper定時任務(wù),每隔30s,判斷空閑線程數(shù)低于minimumIdle,并且當前線程池總連接數(shù)小于maximumPoolSize,就建立和MySQL的一個長連接,然后加入到連接池中。官方建議minimumIdle和maximumPoolSize保持一致。 因為HikariCP的HouseKeeper在發(fā)現(xiàn)idleTimeout>0 并且 minimumIdle < maximumPoolSize時,先會去掃描一遍需要移除空閑連接,和MySQL斷開連接。然后再一次性補滿空閑連接數(shù)至到minimumIdle。
maximumPoolSize
池中最大連接數(shù),其實就是線程池中隊列的大小,默認大小為10(包括閑置和使用中的連接)
如果maxPoolSize小于1,則會被重置。當minIdle<=0被重置為DEFAULT_POOL_SIZE則為10;如果minIdle>0則重置為minIdle的值
HikariCP架構(gòu)

分析源碼之前,先給大家介紹一下HikariCP的整體架構(gòu),整體架構(gòu)和DBCP2 的有點類似(由此可見 HikariCP 與 DBCP2 性能差異并不是由于架構(gòu)設(shè)計),下面我總結(jié)了幾點,來和大家一起探討下:
HikariCP通過JMX調(diào)用HikariPoolMXBean來獲取連接池的連接數(shù)、獲取等待連接的線程數(shù)、丟棄未使用連接、掛起和恢復連接池等。HikariCP通過JMX調(diào)用HikariConfigMXBean來動態(tài)修改配置。HikariCP使用HikariConfig加載配置文件,一般會作為入?yún)順?gòu)造 HikariDataSource 對象。HikariPool是一個非常重要的類,它負責管理連接,涉及到比較多的代碼邏輯。HikariDataSource主要用于操作HikariPool獲取連接。ConcurrentBag用于優(yōu)化大幅減少線程間的鎖競爭。PoolBase是HikariPool的父類,主要負責操作實際的DataSource獲取連接,并設(shè)置連接的一些屬性。
源碼解析
HikariConfig
HikariConfig保存了所有連接池配置,另外實現(xiàn)了HikariConfigMXBean接口,有些配置可以利用JMX運行時變更。核心配置項屬性會在下面給大家介紹,這邊Dong哥就簡單介紹一下了。
HikariPool
getConnection
public Connection getConnection(final long hardTimeout) throws SQLException
{
//這里是防止線程池處于暫停狀態(tài)(通常不允許線程池可暫停)
suspendResumeLock.acquire();
final long startTime = currentTime();
try {
long timeout = hardTimeout;
do {
//PoolEntry 用于跟蹤connection實例,里面包裝了Connection;
//從connectionBag中獲取一個對象,并且檢測是否可用
PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
if (poolEntry == null) {
break; // We timed out... break and throw exception
}
final long now = currentTime();
//1、已被標記為驅(qū)逐 2、已超過最大存活時間 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);
//刷新超時時間
timeout = hardTimeout - elapsedMillis(startTime);
}
else {
metricsTracker.recordBorrowStats(poolEntry, startTime);
return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
}
//如果沒超時則再次獲取
} while (timeout > 0L);
//超時時間到仍未獲取到鏈接則拋出 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();
}
}
校驗:
- isMarkedEvicted:檢查當前鏈接是否已被驅(qū)逐
- elapsedMillis(poolEntry.lastAccessed, now):檢查鏈接是否超過最大存活時間(
maxLifetime配置時間)
/**
* startTime 上次使用時間
* endTime 當前時間
*/
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實現(xiàn)支持jdbc4 則使用jdbc4 Connection的isValid方法檢測
if (isUseJdbc4Validation) {
return connection.isValid(validationSeconds);
}
//查詢數(shù)據(jù)庫檢測連接可用性
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());
//捕獲到異常,說明鏈接不可用。(connection is unavailable)
return false;
}
}
HouseKeeper
HouseKeeper負責保持,我們始終有minimumIdle空閑鏈接可用
private final class HouseKeeper implements Runnable
{
//默認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)閉過多的空閑超時鏈接
closeConnection(entry, "(connection has passed idleTimeout)");
toRemove--;
}
}
}
//記錄pool狀態(tài)信息
logPoolState(afterPrefix);
//補充空閑鏈接
fillPool();
}
catch (Exception e) {
LOGGER.error("Unexpected exception in housekeeping task", e);
}
}
}
HouseKeeper其實是一個線程,也是寫在HikariPool類里面的一個內(nèi)部類,主要負責保持 minimumIdle 的空閑鏈接。HouseKeeper也用到了validationTimeout, 并且會根據(jù)minimumIdle配置,通過fill 或者 remove保持最少空閑鏈接數(shù)。HouseKeeper線程初始化:
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類的子類,Java推薦僅在開發(fā)定時任務(wù)程序時采用ScheduledThreadPoolExecutor類
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1, threadFactory, new DiscardPolicy());
//傳入false,則執(zhí)行shutdown()方法之后,待處理的任務(wù)將不會被執(zhí)行
executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
//取消任務(wù)后,判斷是否需要從阻塞隊列中移除任務(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)造時決定,如果使用無參構(gòu)造為null,使用有參構(gòu)造和pool一樣
private final HikariPool fastPathPool;
//volatile修飾,無參構(gòu)造不會設(shè)置pool,在getConnection時構(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配置項及源碼解析的詳細內(nèi)容,更多關(guān)于SpringBoot HikariCP配置的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ConcurrentHashMap線程安全及實現(xiàn)原理實例解析
這篇文章主要介紹了ConcurrentHashMap線程安全及實現(xiàn)原理實例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11
帶你了解Java數(shù)據(jù)結(jié)構(gòu)和算法之高級排序
這篇文章主要為大家介紹了Java數(shù)據(jù)結(jié)構(gòu)和算法之高級排序,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-01-01
SpringBoot下RabbitMq實現(xiàn)定時任務(wù)
這篇文章主要為大家詳細介紹了SpringBoot下RabbitMq實現(xiàn)定時任務(wù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-11-11
使用Spring Data JDBC實現(xiàn)DDD聚合的示例代碼
這篇文章主要介紹了使用Spring Data JDBC實現(xiàn)DDD聚合的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09
Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟詳解
這篇文章主要介紹了Intellij 下 mybatis 插件 MyBatisCodeHelperPro破解步驟,本文分步驟給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09
詳解Spring連接數(shù)據(jù)庫的幾種常用的方式
本篇文章主要介紹了Spring連接數(shù)據(jù)庫的幾種常用的方式,具有一定的參考價值,有需要的可以了解一下。2016-12-12

