Hikari 连接池关闭连接问题
HIkari是java中最快的连接池,也是springboot默认的连接池。项目中使用hakari 连接池管理conn,在使用过程中遇到如果没有声明事物,连接不会关闭的情况,故花时间看了hakari的源码
另外如果要看具体问题与解决方法最好去github 上找一下。
HikariConfig 另外配置类中也做了详细说明,推荐看下这个类,其中作者标名了某些配置的默认值、执行过程中可改变与不可改变的值
private static final char[] ID_CHARACTERS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();private static final long CONNECTION_TIMEOUT = SECONDS.toMillis(30);private static final long VALIDATION_TIMEOUT = SECONDS.toMillis(5);private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);private static final long MAX_LIFETIME = MINUTES.toMillis(30);private static final int DEFAULT_POOL_SIZE = 10;private static boolean unitTest = false;// Properties changeable at runtime through the HikariConfigMXBean//private volatile long connectionTimeout;private volatile long validationTimeout;private volatile long idleTimeout;private volatile long leakDetectionThreshold;private volatile long maxLifetime;private volatile int maxPoolSize;private volatile int minIdle;private volatile String username;private volatile String password;// Properties NOT changeable at runtime//private long initializationFailTimeout;private String catalog;private String connectionInitSql;private String connectionTestQuery;private String dataSourceClassName;private String dataSourceJndiName;private String driverClassName;private String jdbcUrl;private String poolName;private String schema;private String transactionIsolationName;private boolean isAutoCommit;private boolean isReadOnly;private boolean isIsolateInternalQueries;private boolean isRegisterMbeans;private boolean isAllowPoolSuspension;private DataSource dataSource;private Properties dataSourceProperties;private ThreadFactory threadFactory;private ScheduledExecutorService scheduledExecutor;private MetricsTrackerFactory metricsTrackerFactory;private Object metricRegistry;private Object healthCheckRegistry;private Properties healthCheckProperties;
关于hikari 如何处理空闲连接的:
hikaripool类中有一个内部类,huosekeeper,它干的活就是维持空闲连接与消除过于的连接。下面是源码
它的工作机制就是,在程序启动后,datasource 交给 hikari 池代理,初始化空闲连接后(根据你的最小空闲连接配置决定),启动一个schedule,周期默认是30S
在调度任务执行的过程中会检查连接池包(currentBag)中连接的状态,如果是 NOT_IN_USER 的连接,那么将会进行释放。
如果当前空闲连接小于预定义的连接数,hikari会create 空闲连接,直接满足最小空闲连接要求(默认最小是10)
private final class HouseKeeper implements Runnable{private volatile long previous = plusMillis(currentTime(), -HOUSEKEEPING_PERIOD_MS);public void run(){try {// refresh timeouts in case they changed via MBeanconnectionTimeout = 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 anywayLOGGER.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);}}}
那么重点就在于NOT_IN_USE 状态什么时候设置的,只有一个地方会直接把IN_USER 设置为NOT_IN_USER
在currentBag 中,那么什么时候会调用requite 方法,当conn 关闭的时候,通过hikari 代理最终close 会执行到requite 方法,这时候hikari 会把当前连接池中的连接,
也就是下图中的bagEntry 设置为NOT_IN_USE, 设置完毕后,具体清理工作交给housekeeper 处理。
另外当我们使用conn 的时候,hikari 同样会获取连接池中的当前空闲连接交给你使用。
而我遇到的问题是,当调用一个自定义的service 方法,并且其中有操作数据库的操作(个人调用的是一个oracle 存储过程),由于spring jpa 事物管理中对于自定义的数据库操作并没有加上
我定义的方法相关配置,于是在调用存储过程后没有调用close 方法,故hikari 不会设置激活状态下的连接为空闲状态,随着时间的推移,最终导致连接池中的数量达到了maxpoolsize,无法再次获取连接导致connecttimeout异常。
最后一点是:遇到hikari 的问题,最好把hikari的log日志打开(设置debug),这样默认情况下每30S ,hikari 的housekeeper 会打印当前池中的连接情况,包括连接总数,激活数,空闲数与等待数量。
<Logger name="com.zaxxer.hikari" level="debug"></Logger>
另外这个问题解决方法就是让conn 每次使用完后管理连接,从而通知hikari 代理把当前使用的连接设置为空闲连接进行回收。
在方法上加上声明式事物注解,即可解决。
@Override
@Transactional(rollbackFor = Exception.class)
public void callLogStatistic(String inBeginTime, String inSiteId) {
repository.logStatistic(inBeginTime, inSiteId);
}
