关于Hikari连接池的源码理解与配置使用
1. 连接池初始化
1.1 初始化连接池
对于应用者来说,我们构造一个线程池就是构造一个HikariDataSource。
重点看一下获取连接以及相关连接管理的流程。
public Connection getConnection() throws SQLException {if (this.isClosed()) {throw new SQLException("HikariDataSource " + this + " has been closed.");} else if (this.fastPathPool != null) {return this.fastPathPool.getConnection();} else {HikariPool result = this.pool;if (result == null) {synchronized(this) {result = this.pool;if (result == null) {this.validate();LOGGER.info("{} - Starting...", this.getPoolName());try {this.pool = result = new HikariPool(this);this.seal();} catch (PoolInitializationException var5) {if (var5.getCause() instanceof SQLException) {throw (SQLException)var5.getCause();}throw var5;}LOGGER.info("{} - Start completed.", this.getPoolName());}}}return result.getConnection();}}
可以发现,连接池的构建是在首次获取连接的时候,继续往下查看代码,this.pool = result = new HikariPool(this);
HikariPool的构造函数中,有两个任务类需要注意的,一是PoolEntryCreator,二是HouseKeeper,前者是初始化连接类,后者是连接池的连接管理类。
private final class PoolEntryCreator implements Callable<Boolean> {private final String loggingPrefix;PoolEntryCreator(String loggingPrefix) {this.loggingPrefix = loggingPrefix;}public Boolean call() throws Exception {//循环创建连接池核心连接for(long sleepBackoff = 250L; HikariPool.this.poolState == 0 && this.shouldCreateAnotherConnection(); sleepBackoff = Math.min(TimeUnit.SECONDS.toMillis(10L), Math.min(HikariPool.this.connectionTimeout, (long)((double)sleepBackoff * 1.5D)))) {//创建连接PoolEntry poolEntry = HikariPool.this.createPoolEntry();if (poolEntry != null) {//添加连接到concurrentBagHikariPool.this.connectionBag.add(poolEntry);HikariPool.this.LOGGER.debug("{} - Added connection {}", HikariPool.this.poolName, poolEntry.connection);if (this.loggingPrefix != null) {HikariPool.this.logPoolState(this.loggingPrefix);}return Boolean.TRUE;}UtilityElf.quietlySleep(sleepBackoff);}return Boolean.FALSE;}private boolean shouldCreateAnotherConnection() {//判断当前连接数是否小于最大连接数,并且存在等待获取连接的线程或当前连接小于最大连接大于最小空闲连接数return HikariPool.this.getTotalConnections() < HikariPool.this.config.getMaximumPoolSize() && (HikariPool.this.connectionBag.getWaitingThreadCount() > 0 || HikariPool.this.getIdleConnections() < HikariPool.this.config.getMinimumIdle());}}
concurrentBag是存储poolEntity的容器。HouseKeeper会在后面3.1中进行说明。具体看一下createPoolEntry()的代码:
其中涉及到一个maxLifetime参数,即连接的生命周期。默认是30min,可以看到创建对象后,对获取配置的参数maxlifetime,进行一个随机数的处理,原因是避免所有连接的lifetime都相同,导致连接的同时失效,对数据库造成压力。官方推荐可以将maxlifetime的时间设置成比数据库连接断开时间短一点点,也是为了避免长时间未执行,导致连接的全部失效。
同时,每个连接都构建了一个监控任务,当超过了maxLifetime,将连接的状态标记为evict,真正的关闭在获取连接的时候执行,2.1中会说明。
private PoolEntry createPoolEntry() {try {PoolEntry poolEntry = this.newPoolEntry();long maxLifetime = this.config.getMaxLifetime();if (maxLifetime > 0L) {long variance = maxLifetime > 10000L ? ThreadLocalRandom.current().nextLong(maxLifetime / 40L) : 0L;long lifetime = maxLifetime - variance;poolEntry.setFutureEol(this.houseKeepingExecutorService.schedule(() -> {if (this.softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false)) {this.addBagItem(this.connectionBag.getWaitingThreadCount());}}, lifetime, TimeUnit.MILLISECONDS));}return poolEntry;} catch (Exception var8) {if (this.poolState == 0) {this.LOGGER.debug("{} - Cannot acquire connection from data source", this.poolName, var8 instanceof ConnectionSetupException ? var8.getCause() : var8);}return null;}}
2. 获取连接
2.1 HikariPool.getConnection()
public Connection getConnection() throws SQLException {return this.getConnection(this.connectionTimeout);}public Connection getConnection(long hardTimeout) throws SQLException {this.suspendResumeLock.acquire();long startTime = ClockSource.currentTime();try {long timeout = hardTimeout;//connectionTimeout内会进行重试,直到获取连接while(true) {//从concurrentBag中获取连接PoolEntry poolEntry = (PoolEntry)this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);if (poolEntry != null) {long now = ClockSource.currentTime();//判断连接是否打上evict标记(删除)并且当前连接状态是否正常或者是否超出存活时间if (!poolEntry.isMarkedEvicted() && (ClockSource.elapsedMillis(poolEntry.lastAccessed, now) <= this.ALIVE_BYPASS_WINDOW_MS || this.isConnectionAlive(poolEntry.connection))) {this.metricsTracker.recordBorrowStats(poolEntry, startTime);//获取连接,创建连接代理对象Connection var10 = poolEntry.createProxyConnection(this.leakTaskFactory.schedule(poolEntry), now);return var10;}//对于失效连接,关闭this.closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? "(connection was evicted)" : "(connection is dead)");timeout = hardTimeout - ClockSource.elapsedMillis(startTime);if (timeout > 0L) {continue;}}this.metricsTracker.recordBorrowTimeoutStats(startTime);throw this.createTimeoutException(startTime);}} catch (InterruptedException var14) {Thread.currentThread().interrupt();throw new SQLException(this.poolName + " - Interrupted during connection acquisition", var14);} finally {this.suspendResumeLock.release();}}
3. 管理连接
3.1 HouseKeeper
houseKeeper线程任务每30s执行一次,用来管理空闲连接,其中涉及的参数主要是idelTimeout,看管理空闲连接的代码,List notInUse = HikariPool.this.connectionBag.values(0);获取状态为0( NOT_IN_USER )的连接,关闭大于minimumIdel小于max的空闲连接。
同时,如果池内连接数小于min,则进行填充。
private final class HouseKeeper implements Runnable {private volatile long previous;private HouseKeeper() {this.previous = ClockSource.plusMillis(ClockSource.currentTime(), -HikariPool.this.HOUSEKEEPING_PERIOD_MS);}public void run() {try {HikariPool.this.connectionTimeout = HikariPool.this.config.getConnectionTimeout();HikariPool.this.validationTimeout = HikariPool.this.config.getValidationTimeout();HikariPool.this.leakTaskFactory.updateLeakDetectionThreshold(HikariPool.this.config.getLeakDetectionThreshold());long idleTimeout = HikariPool.this.config.getIdleTimeout();long now = ClockSource.currentTime();//管理时钟倒退,将连接设置为evictif (ClockSource.plusMillis(now, 128L) < ClockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, ClockSource.elapsedDisplayString(this.previous, now));this.previous = now;HikariPool.this.softEvictConnections();return;}if (now > ClockSource.plusMillis(this.previous, 3L * HikariPool.this.HOUSEKEEPING_PERIOD_MS / 2L)) {HikariPool.this.LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", HikariPool.this.poolName, ClockSource.elapsedDisplayString(this.previous, now));}this.previous = now;String afterPrefix = "Pool ";//管理空闲连接if (idleTimeout > 0L && HikariPool.this.config.getMinimumIdle() < HikariPool.this.config.getMaximumPoolSize()) {HikariPool.this.logPoolState("Before cleanup ");afterPrefix = "After cleanup ";List<PoolEntry> notInUse = HikariPool.this.connectionBag.values(0);int toRemove = notInUse.size() - HikariPool.this.config.getMinimumIdle();Iterator var8 = notInUse.iterator();while(var8.hasNext()) {PoolEntry entry = (PoolEntry)var8.next();if (toRemove > 0 && ClockSource.elapsedMillis(entry.lastAccessed, now) > idleTimeout && HikariPool.this.connectionBag.reserve(entry)) {HikariPool.this.closeConnection(entry, "(connection has passed idleTimeout)");--toRemove;}}}HikariPool.this.logPoolState(afterPrefix);HikariPool.this.fillPool();} catch (Exception var10) {HikariPool.this.LOGGER.error("Unexpected exception in housekeeping task", var10);}}}
4. 释放连接
4.1 代理释放连接
housekeeper会定时关闭空闲连接,那么怎么设置连接的状态为0呢?连接使用完后怎么释放?
创建连接的时候可以看到提供的连接是HikariProxyConnection代理。所以用户调用close方法,其实是执行的HikariProxyConnection.close()
ProxyConnection:public final void close() throws SQLException {this.closeStatements();if (this.delegate != ProxyConnection.ClosedConnection.CLOSED_CONNECTION) {this.leakTask.cancel();try {......} catch (SQLException var5) {if (!this.poolEntry.isMarkedEvicted()) {throw this.checkException(var5);}} finally {this.delegate = ProxyConnection.ClosedConnection.CLOSED_CONNECTION;this.poolEntry.recycle(this.lastAccess);}}}PoolEntity:void recycle(long lastAccessed) {if (this.connection != null) {this.lastAccessed = lastAccessed;this.hikariPool.recycle(this);}}HikariPool:void recycle(PoolEntry poolEntry) {this.metricsTracker.recordConnectionUsage(poolEntry);this.connectionBag.requite(poolEntry);}ConcurrentBag:public void requite(T bagEntry) {bagEntry.setState(0);......}
最终执行concurrentBag.requite()方法,bagEntry.setState(0);
5. 关闭连接
5.1 关闭连接
关闭连接主要有两个逻辑:一是标记软驱逐,二是关闭连接。
时钟倒退,maxlifetime超时等都会进行软驱逐标记。
housekeeper的空闲连接超时,以及getConncection时对驱逐标记连接对象都可以执行关闭操作。
看一下关闭的公共方法closeConnection()
void closeConnection(PoolEntry poolEntry, String closureReason) {//修改PoolEntry状态,从shareList和threadList中移除这个元素if (this.connectionBag.remove(poolEntry)) {//关闭MaxLifeTime超时检测任务,一些变量赋空值帮助GCConnection connection = poolEntry.close();//关闭任务提交到线程池,执行关闭this.closeConnectionExecutor.execute(() -> {this.quietlyCloseConnection(connection, closureReason);if (this.poolState == 0) {//如果连接池状态正常,维持最小连接this.fillPool();}});}}