SpringBoot?HikariCP配置項(xiàng)及源碼解析
前言
在SpringBoot2.0之后,采用的默認(rèn)數(shù)據(jù)庫(kù)連接池就是Hikari,是一款非常強(qiáng)大,高效,并且號(hào)稱“史上最快連接池”。我們知道的連接池有C3P0,DBCP,Druid它們都比較成熟穩(wěn)定,但性能不是十分好。
我們?cè)谌粘5木幋a中,通常會(huì)將一些對(duì)象保存起來,這主要考慮的是對(duì)象的創(chuàng)建成本;比如像線程資源、數(shù)據(jù)庫(kù)連接資源或者 TCP 連接等,這類對(duì)象的初始化通常要花費(fèi)比較長(zhǎng)的時(shí)間,如果頻繁地申請(qǐng)和銷毀,就會(huì)耗費(fèi)大量的系統(tǒng)資源,造成不必要的性能損失,于是在Java 中,池化技術(shù)應(yīng)用非常廣泛。在軟件行開發(fā)中,軟件的性能是占主導(dǎo)地位的,于是HikariCP就在眾多數(shù)據(jù)庫(kù)連接池中脫穎而出。
為什么HikariCP性能高
- 優(yōu)化代理和攔截器:減少代碼。
- 字節(jié)碼精簡(jiǎn) :優(yōu)化代碼(
HikariCP利用了一個(gè)第三方的Java字節(jié)碼修改類庫(kù)Javassist來生成委托實(shí)現(xiàn)動(dòng)態(tài)代理,動(dòng)態(tài)代理的實(shí)現(xiàn)在ProxyFactory類),直到編譯后的字節(jié)碼最少,這樣,CPU緩存可以加載更多的程序代碼。 - 通過代碼設(shè)計(jì)和優(yōu)化大幅減少線程間的鎖競(jìng)爭(zhēng)。這點(diǎn)主要通過
ConcurrentBag來實(shí)現(xiàn)。 - 自定義數(shù)組類型(
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不需要,在能確保范圍的合法性的情況下,可以省去范圍檢查的開銷。自定義集合類型(ConcurrentBag):支持快速插入和刪除,特別是在同一線程既添加又刪除項(xiàng)時(shí),提高并發(fā)讀寫的效率; - 關(guān)于
Connection的操作:另外在Java代碼中,很多都是在使用完之后直接關(guān)閉連接,以前都是從頭到尾遍歷,來關(guān)閉對(duì)應(yīng)的Connection,而HikariCP則是從尾部對(duì)Connection集合進(jìn)行掃描,整體上來說,從尾部開始的性能更好一些。 - 針對(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
控制客戶端等待來自池的連接的最大毫秒數(shù)。
如果在沒有連接可用的情況下超過此時(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用來判斷是否應(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且未超過minimumIdle數(shù)量的連接的批量移除。
maxLifetime
池中連接最長(zhǎng)生命周期;如果不等于0且小于30秒則會(huì)被重置回30分鐘
了解這個(gè)值的作用前,先了解一下MySQLwait_timeout的作用:MySQL 為了防止空閑連接浪費(fèi),占用資源,在超過wait_timeout時(shí)間后,會(huì)主動(dòng)關(guān)閉該連接,清理資源;默認(rèn)是28800s,也就是8小時(shí)。簡(jiǎn)而言之就是MySQL會(huì)在某個(gè)連接超過8小時(shí)還沒有任何請(qǐng)求時(shí)自動(dòng)斷開連接,但是HikariCP如何知道池子里的連接有沒有超過這個(gè)時(shí)間呢?所以就有了maxLifetime,配置后HikariCP會(huì)把空閑鏈接超過這個(gè)時(shí)間的給剔除掉,防止獲取到已經(jīng)關(guān)閉的連接導(dǎo)致異常。
connectionTestQuery
將在從池中向您提供連接之前執(zhí)行的查詢,以驗(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,判斷空閑線程數(shù)低于minimumIdle,并且當(dāng)前線程池總連接數(shù)小于maximumPoolSize,就建立和MySQL的一個(gè)長(zhǎng)連接,然后加入到連接池中。官方建議minimumIdle和maximumPoolSize保持一致。 因?yàn)?code>HikariCP的HouseKeeper在發(fā)現(xiàn)idleTimeout>0 并且 minimumIdle < maximumPoolSize時(shí),先會(huì)去掃描一遍需要移除空閑連接,和MySQL斷開連接。然后再一次性補(bǔ)滿空閑連接數(shù)至到minimumIdle。
maximumPoolSize
池中最大連接數(shù),其實(shí)就是線程池中隊(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)類似(由此可見 HikariCP 與 DBCP2 性能差異并不是由于架構(gòu)設(shè)計(jì)),下面我總結(jié)了幾點(diǎn),來和大家一起探討下:
HikariCP通過JMX調(diào)用HikariPoolMXBean來獲取連接池的連接數(shù)、獲取等待連接的線程數(shù)、丟棄未使用連接、掛起和恢復(fù)連接池等。HikariCP通過JMX調(diào)用HikariConfigMXBean來動(dòng)態(tài)修改配置。HikariCP使用HikariConfig加載配置文件,一般會(huì)作為入?yún)順?gòu)造 HikariDataSource 對(duì)象。HikariPool是一個(gè)非常重要的類,它負(fù)責(zé)管理連接,涉及到比較多的代碼邏輯。HikariDataSource主要用于操作HikariPool獲取連接。ConcurrentBag用于優(yōu)化大幅減少線程間的鎖競(jìng)爭(zhēng)。PoolBase是HikariPool的父類,主要負(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
{
//這里是防止線程池處于暫停狀態(tài)(通常不允許線程池可暫停)
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、已超過最大存活時(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);
}
//如果沒超時(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):檢查鏈接是否超過最大存活時(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);
}
//查詢數(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());
//捕獲到異常,說明鏈接不可用。(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)閉過多的空閑超時(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è)線程,也是寫在HikariPool類里面的一個(gè)內(nèi)部類,主要負(fù)責(zé)保持 minimumIdle 的空閑鏈接。HouseKeeper也用到了validationTimeout, 并且會(huì)根據(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ā)定時(shí)任務(wù)程序時(shí)采用ScheduledThreadPoolExecutor類
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í)決定,如果使用無參構(gòu)造為null,使用有參構(gòu)造和pool一樣
private final HikariPool fastPathPool;
//volatile修飾,無參構(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)文章
Java?ASM使用logback日志級(jí)別動(dòng)態(tài)切換方案展示
這篇文章主要介紹了Java?ASM使用logback日志級(jí)別動(dòng)態(tài)切換方案展示,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
使用JAXBContext輕松實(shí)現(xiàn)Java和xml的互相轉(zhuǎn)換方式
這篇文章主要介紹了依靠JAXBContext輕松實(shí)現(xiàn)Java和xml的互相轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
javaweb實(shí)戰(zhàn)之商城項(xiàng)目開發(fā)(二)
這篇文章主要針對(duì)javaweb商城項(xiàng)目開發(fā)進(jìn)行實(shí)戰(zhàn)演習(xí),利用mybatis創(chuàng)建DAO層,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
Java遞歸來實(shí)現(xiàn)漢諾塔游戲,注釋詳細(xì)
這篇文章介紹了Java遞歸來實(shí)現(xiàn)漢諾塔游戲的方法,文中的代碼注釋介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-11-11
Java精品項(xiàng)目瑞吉外賣之新增菜品與分頁(yè)查詢篇
這篇文章主要為大家詳細(xì)介紹了java精品項(xiàng)目-瑞吉外賣訂餐系統(tǒng),此項(xiàng)目過大,分為多章獨(dú)立講解,本篇內(nèi)容為新增菜品和分頁(yè)查詢功能的實(shí)現(xiàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Apache commons fileupload文件上傳實(shí)例講解
這篇文章主要為大家詳細(xì)介紹了Apache commons fileupload文件上傳實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Spring連接Mysql數(shù)據(jù)庫(kù)全過程
這篇文章主要介紹了Spring連接Mysql數(shù)據(jù)庫(kù)全過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
eclipse實(shí)現(xiàn)ECDSA數(shù)字簽名
這篇文章主要為大家詳細(xì)介紹了eclipse實(shí)現(xiàn)ECDSA數(shù)字簽名,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
SpringMvc實(shí)現(xiàn)簡(jiǎn)易計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了SpringMvc實(shí)現(xiàn)簡(jiǎn)易計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07

