序
本文主要研究一下hikari連接配接池的fixed pool design
fixed pool design
hikari的作者比較傾向于fixed pool design的理念,即建議minimumIdle與maximumPoolSize設定成一樣,當做固定連接配接大小的連接配接池。作者認為minimumIdle小于maximumPoolSize的話,在流量激增的時候需要額外的連接配接,此時在請求方法裡頭再去處理建立連接配接會造成性能損失,即會導緻資料庫一方面降低連接配接建立的速度,另一方面也會影響既有的連接配接事務的完成,間接影響了這些既有連接配接歸還到連接配接池的速度。
作者認為minimumIdle與maximumPoolSize設定成一樣,多餘的空閑連接配接不會對整體的性能有什麼嚴重影響,如果說設定minimumIdle小于maximumPoolSize是為了在必要的時候可以釋放連接配接以釋放記憶體給其他功能用,但是在高峰時期,連接配接池可能也會到達maximumPoolSize,因而這個目的似乎沒起到效果。
HikariPool.houseKeeperTask
HikariCP-2.7.6-sources.jar!/com/zaxxer/hikari/pool/HikariPool.java
private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30));
public HikariPool(final HikariConfig config)
{
super(config);
this.connectionBag = new ConcurrentBag<>(this);
this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();
checkFailFast();
if (config.getMetricsTrackerFactory() != null) {
setMetricsTrackerFactory(config.getMetricsTrackerFactory());
}
else {
setMetricRegistry(config.getMetricRegistry());
}
setHealthCheckRegistry(config.getHealthCheckRegistry());
registerMBeans(this);
ThreadFactory threadFactory = config.getThreadFactory();
LinkedBlockingQueue addConnectionQueue = new LinkedBlockingQueue<>(config.getMaximumPoolSize());
this.addConnectionQueue = unmodifiableCollection(addConnectionQueue);
this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());
this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
}
在初始化HikariPool的時候會初始化houseKeeperTask
HikariPool.HouseKeeper
HikariCP-2.7.6-sources.jar!/com/zaxxer/hikari/pool/HikariPool.java
private final class HouseKeeper implements Runnable
{
private volatile long previous = plusMillis(currentTime(), -HOUSEKEEPING_PERIOD_MS);
@Override
public void run()
{
try {
// refresh timeouts in case they changed via MBean
connectionTimeout = config.getConnectionTimeout();
validationTimeout = config.getValidationTimeout();
leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
final long idleTimeout = config.getIdleTimeout();
final long now = currentTime();
// Detect retrograde time, allowing +128ms as per NTP spec.
if (plusMillis(now, 128) < plusMillis(previous, HOUSEKEEPING_PERIOD_MS)) {
LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
poolName, elapsedDisplayString(previous, now));
previous = now;
softEvictConnections();
return;
}
else if (now > plusMillis(previous, (3 * HOUSEKEEPING_PERIOD_MS) / 2)) {
// No point evicting for forward clock motion, this merely accelerates connection retirement anyway
LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now));
}
previous = now;
String afterPrefix = "Pool ";
if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) {
logPoolState("Before cleanup ");
afterPrefix = "After cleanup ";
final List 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)) {
closeConnection(entry, "(connection has passed idleTimeout)");
toRemove--;
}
}
}
logPoolState(afterPrefix);
fillPool(); // Try to maintain minimum connections
}
catch (Exception e) {
LOGGER.error("Unexpected exception in housekeeping task", e);
}
}
}
假設minimumIdle與maximumPoolSize設定成一樣,那麼這個task在第一次執行的時候,直接執行fillPool
fillPool
private synchronized void fillPool()
{
final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
- addConnectionQueue.size();
for (int i = 0; i < connectionsToAdd; i++) {
addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : POST_FILL_POOL_ENTRY_CREATOR);
}
}
這個fillPool,在初始化時刻,minimumIdle與maximumPoolSize值一樣,totalConnections與idleConnections都為0,那麼connectionsToAdd的值就是maximumPoolSize
也就是說這個task會添加maximumPoolSize大小連接配接
小結
tomcat jdbc pool
有個initial-size參數來指定最開始的時候初始化多少個連接配接,有min-idle及max-idle來控制空閑連接配接的最小值及最大值,有max-active來控制連接配接池總大小。min-evictable-idle-time-millis用來指定空閑連接配接的時長,time-between-eviction-runs-millis用來指定清理空閑連接配接的任務的排程時間間隔。
hikari connection pool
有minIdle來指定空閑連接配接的最小數量,maxPoolSize指定連接配接池連接配接最大值,預設初始化的時候,是初始化minIdle大小的連接配接,如果minIdle與maxPoolSize值相等那就是初始化時把連接配接池填滿。idleTimeout用來指定空閑連接配接的時長,maxLifetime用來指定所有連接配接的時長。com.zaxxer.hikari.housekeeping.periodMs用來指定連接配接池空閑連接配接處理及連接配接池數補充的HouseKeeper任務的排程時間間隔。
也就是說hikari比tomcat jdbc pool多了個maxLifetime,也就是所有的連接配接在maxLifetime之後都得重連一次,保證連接配接池的活性。
doc