項目中使用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); }