本文主要研究一下hikari链接池的fixed pool designjava
hikari的做者比较倾向于fixed pool design的理念,即建议minimumIdle与maximumPoolSize设置成同样,当作固定链接大小的链接池。做者认为minimumIdle小于maximumPoolSize的话,在流量激增的时候须要额外的链接,此时在请求方法里头再去处理新建链接会形成性能损失,即会致使数据库一方面下降链接创建的速度,另外一方面也会影响既有的链接事务的完成,间接影响了这些既有链接归还到链接池的速度。做者认为minimumIdle与maximumPoolSize设置成同样,多余的空闲链接不会对总体的性能有什么严重影响,若是说设置minimumIdle小于maximumPoolSize是为了在必要的时候能够释放链接以释放内存给其余功能用,可是在高峰时期,链接池可能也会到达maximumPoolSize,于是这个目的彷佛没起到效果。git
HikariCP-2.7.6-sources.jar!/com/zaxxer/hikari/pool/HikariPool.javagithub
private final long HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", SECONDS.toMillis(30)); /** * Construct a HikariPool with the specified configuration. * * @param config a HikariConfig instance */ 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<Runnable> 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
HikariCP-2.7.6-sources.jar!/com/zaxxer/hikari/pool/HikariPool.javasql
/** * The house keeping task to retire and maintain minimum idle connections. */ 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<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)) { 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
/** * Fill pool up from current idle connections (as they are perceived at the point of execution) to minimumIdle connections. */ 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大小链接
有个initial-size参数来指定最开始的时候初始化多少个链接,有min-idle及max-idle来控制空闲链接的最小值及最大值,有max-active来控制链接池总大小。min-evictable-idle-time-millis用来指定空闲链接的时长,time-between-eviction-runs-millis用来指定清理空闲链接的任务的调度时间间隔。
有minIdle来指定空闲链接的最小数量,maxPoolSize指定链接池链接最大值,默认初始化的时候,是初始化minIdle大小的链接,若是minIdle与maxPoolSize值相等那就是初始化时把链接池填满。idleTimeout用来指定空闲链接的时长,maxLifetime用来指定全部链接的时长。com.zaxxer.hikari.housekeeping.periodMs用来指定链接池空闲链接处理及链接池数补充的HouseKeeper任务的调度时间间隔。也就是说hikari比tomcat jdbc pool多了个maxLifetime,也就是全部的链接在maxLifetime以后都得重连一次,保证链接池的活性。数据库