项目中使用hakari 连接池管理conn,在使用过程中遇到如果没有声明事物,连接不会关闭的情况,故花时间看了hakari的源码
首先,hikari有一堆配置,这个配置的注意事项可以去网上找一下,这里提供一个地址 https://blog.51cto.com/1197822/2298344、
另外如果要看具体问题与解决方法最好去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); @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); } } }
那么重点就在于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); }