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 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);
}
}
}
那么重点就在于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);
}