Разрешение пула соединений Hikari (версия: HikariCP-2.5.1.jar)

Spring Boot

параметр maxLifetime

  1. Параметр maxLifetime должен быть меньше time_wait базы данных, а значение по умолчанию — 1800000, что составляет 30 минут. Если установлено значение 0, это означает, что срок службы бесконечен. Если не равно 0 и меньше 30 секунд, оно будет сброшено обратно на 30 минут. В классе HikariConfig существуют правила проверки этого параметра.

  2. В классе HikariPool, когда мы инициализируем пул соединений, его конструктор создает экземплярthis.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();Этот класс реализует интерфейс Callable и используется для инициализации соединения.

public Boolean call() throws Exception {
            for(long sleepBackoff = 250L; HikariPool.this.poolState == 0 && HikariPool.this.totalConnections.get() < HikariPool.this.config.getMaximumPoolSize(); sleepBackoff = Math.min(TimeUnit.SECONDS.toMillis(10L), Math.min(HikariPool.this.connectionTimeout, (long)((double)sleepBackoff * 1.5D)))) {
                PoolEntry poolEntry = HikariPool.this.createPoolEntry();
                if (poolEntry != null) {
                    HikariPool.this.totalConnections.incrementAndGet();
                    HikariPool.this.connectionBag.add(poolEntry);
                    return Boolean.TRUE;
                }

                UtilityElf.quietlySleep(sleepBackoff);
            }

            return Boolean.FALSE;
        }

вызыватьcreatePoolEntry()Создайте соединение.

private PoolEntry createPoolEntry() {
        try {
            final PoolEntry poolEntry = this.newPoolEntry();
            long maxLifetime = this.config.getMaxLifetime();
            if (maxLifetime > 0L) {
                long variance = maxLifetime > 10000L ? ThreadLocalRandom.current().nextLong(maxLifetime / 40L) : 0L;
                long lifetime = maxLifetime - variance;
                poolEntry.setFutureEol(this.houseKeepingExecutorService.schedule(new Runnable() {
                    public void run() {
                        HikariPool.this.softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false);
                    }
                }, lifetime, TimeUnit.MILLISECONDS));
            }

            this.LOGGER.debug("{} - Added connection {}", this.poolName, poolEntry.connection);
            return poolEntry;
        } catch (Exception var8) {
            if (this.poolState == 0) {
                this.LOGGER.debug("{} - Cannot acquire connection from data source", this.poolName, var8);
            }

            return null;
        }
    }

В этом методе задается задача задержки, конкретное время выполнения задержки рассчитывается в соответствии с maxLifetime, а разница между временем триггера и maxlifetime определяется в соответствии с maxLifetime > 10_000?ThreadLocalRandom.current().nextLong(maxLifetime/40): 0; вычислять (up to 2.5% of the maxlifetime), запуская evit до того, как выживаемость соединения достигнет maxLifetime, чтобы предотвратить одновременный сбой большой области соединения из-за maxLifetime.

При срабатывании он отметитevictправда,Маркировали выселить пул соединения, но указанное соединение недоступно, но все равно в пуле соединений, оно будет заимствовано, но оценивается при getConnection, если оно MarkedEvicted, то соединение будет удалено из пула соединений, а затем закрыто.

  1. В HikariCP физическое соединение закрывается через независимый пул потоков closeConnectionExecutor. Автоматическое закрытие соединения срабатывает при выполнении следующих трех условий:

  2. Отключить;

  3. Время жизни соединения превышает максимальное время жизни (maxLifeTime)

  4. Время простоя соединения превышает максимальное время простоя (idleTimeout)

После закрытия соединения closeConnectionExecutor вызовет метод fillPool() для заполнения пула соединений.

validationTimeout

  1. Валидация используется для указания времени ожидания времени ожидания проверки достоверности соединения (默认是5秒,最小不能小于250毫秒),существуетHikariPool.getConnectionметод будет вызыватьсяisConnectionAlive(Connection connection)Аутентифицируйте соединение.

Если это jdbc4, вы можете использовать isUseJdbc4Validation, то есть напрямую использовать connection.isValid(validationSeconds) для проверки правильности соединения; в противном случае используйте оператор запроса connectionTestQuery для запроса проверки.

leakDetectionThreshold`

  1. Этот параметр в основном используется для включения обнаружения утечки соединения.getConnection()При получении соединения он продолжит вызывать другой метод createProxyConnection() для получения соединения, здесь мы обращаем внимание на входные параметрыthis.leakTask.schedule(poolEntry).

     public final Connection getConnection(long hardTimeout) throws SQLException {
            this.suspendResumeLock.acquire();
            long startTime = clockSource.currentTime();
    
            try {
                long timeout = hardTimeout;
    
                do {
                    PoolEntry poolEntry = (PoolEntry)this.connectionBag.borrow(timeout, TimeUnit.MILLISECONDS);
                    if (poolEntry == null) {
                        break;
                    }
    
                    long now = clockSource.currentTime();
                    if (!poolEntry.isMarkedEvicted() && (clockSource.elapsedMillis(poolEntry.lastAccessed, now) <= this.ALIVE_BYPASS_WINDOW_MS || this.isConnectionAlive(poolEntry.connection))) {
                        this.metricsTracker.recordBorrowStats(poolEntry, startTime);
                        //获取连接
                        Connection var10 = poolEntry.createProxyConnection(this.leakTask.schedule(poolEntry), now);
                        return var10;
                    }
    

    Долженscheduleметод возвращаетProxyLeakTaskобъект

    //返回    ProxyLeakTask
    ProxyLeakTask schedule(PoolEntry bagEntry) {
            return this.leakDetectionThreshold == 0L ? NO_LEAK : new ProxyLeakTask(this, bagEntry);
        }
    

    Судите здесьleakDetectionThresholdЕсли параметр равен 0, значение по умолчанию равно 0, и обнаружение не включено. В противном случае будет запущена задача с отложенным выполнением, и время будет точно заданным.leakDetectionThresholdзначение, роль задачи состоит в том, чтобы броситьApparent connection leak detectedаномальный.

       
    private ProxyLeakTask(ProxyLeakTask parent, PoolEntry poolEntry) {
            this.exception = new Exception("Apparent connection leak detected");
            this.connectionName = poolEntry.connection.toString();
        //延时执行
            this.scheduledFuture = parent.executorService.schedule(this, parent.leakDetectionThreshold, TimeUnit.MILLISECONDS);
        }
    

    Перехватите некоторые исключения, как показано ниже

    22:14:49.096 volte-cmd-service-test [HikariPool-1 housekeeper] WARN  com.zaxxer.hikari.pool.ProxyLeakTask - Connection leak detection triggered for com.mysql.jdbc.JDBC4Connection@429fe922, stack trace follows
    java.lang.Exception: Apparent connection leak detected
    	at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122)
    	at org.hibernate.internal.AbstractSessionImpl$NonContextualJdbcConnectionAccess.obtainConnection(AbstractSessionImpl.java:386)
    	at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.acquireConnectionIfNeeded(LogicalConnectionManagedImpl.java:87)
    	at org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl.getPhysicalConnection(LogicalConnectionManagedImpl.java:112)
    	at org.hibernate.internal.SessionImpl.connection(SessionImpl.java:489)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.lang.reflect.Method.invoke(Method.java:497)
    	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:215)
    	at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:200)
    	at org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle.doGetConnection(HibernateJpaDialect.java:414)
    	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.beginTransaction(HibernateJpaDialect.java:177)
    

    То есть с момента получения этого соединения до времени до возврата соединения, если оно превышаетleakDetectionThreshold, будет выдано указанное выше исключение.

HouseKeeper

этоHikariPoolВнутренний класс в , который реализуетRunnableИнтерфейс в основном для управления соединением. в инициализацииHikariPool, создастscheduleWithFixedDelayЗадачи (выполняются с фиксированным временем задержки, то есть временной интервал между двумя задачами фиксирован, но время выполнения каждой задачи может быть неопределенным, иscheduleFixedRateРазница в том, что независимо от того, завершена задача или нет, следующая задача будет выполняться в точке (по умолчанию выполняется каждые 30 секунд, обновляется конфигурация и выносится суждение).

public HikariPool(HikariConfig config) {
        super(config);
        this.ALIVE_BYPASS_WINDOW_MS = Long.getLong("com.zaxxer.hikari.aliveBypassWindowMs", TimeUnit.MILLISECONDS.toMillis(500L));
        this.HOUSEKEEPING_PERIOD_MS = Long.getLong("com.zaxxer.hikari.housekeeping.periodMs", TimeUnit.SECONDS.toMillis(30L));
        this.POOL_ENTRY_CREATOR = new HikariPool.PoolEntryCreator();
        this.connectionBag = new ConcurrentBag(this);
        this.totalConnections = new AtomicInteger();
        this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
        this.checkFailFast();
        if (config.getMetricsTrackerFactory() != null) {
            this.setMetricsTrackerFactory(config.getMetricsTrackerFactory());
        } else {
            this.setMetricRegistry(config.getMetricRegistry());
        }

        this.setHealthCheckRegistry(config.getHealthCheckRegistry());
        this.registerMBeans(this);
        ThreadFactory threadFactory = config.getThreadFactory();
        this.addConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection adder", threadFactory, new DiscardPolicy());
        this.closeConnectionExecutor = UtilityElf.createThreadPoolExecutor(config.getMaximumPoolSize(), this.poolName + " connection closer", threadFactory, new CallerRunsPolicy());
    //创建定时任务类
        if (config.getScheduledExecutorService() == null) {
            ThreadFactory threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(this.poolName + " housekeeper", true);
            this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, (ThreadFactory)threadFactory, new DiscardPolicy());         
            //传递false参数给这个方法,执行shutdown()方法之后,待处理的任务将不会被执行。
     this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
            //取消任务后,判断是否需要从阻塞队列中移除任务
            this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);
        } else {
            this.houseKeepingExecutorService = config.getScheduledExecutorService();
        }

        this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), this.houseKeepingExecutorService);
    //初始化HouseKeeper
        this.houseKeepingExecutorService.scheduleWithFixedDelay(new HikariPool.HouseKeeper(), 100L, this.HOUSEKEEPING_PERIOD_MS, TimeUnit.MILLISECONDS);
    }

Время обработки обратного вызова

существуетHouseKeeperизrunВ методе время будет оцениваться в первую очередь.

В основном следует определить, будет ли эта разница с временем, чтобы вернуться к возвращению разницы во времени, а временная точка генерируется следующим методом строительства при инициализации.

// previous=当前时间-30s(默认的定时任务间隔时间)
private HouseKeeper() {
            this.previous = HikariPool.clockSource.plusMillis(HikariPool.clockSource.currentTime(), -HikariPool.this.HOUSEKEEPING_PERIOD_MS);
        }

Когда инициализация завершена в течение первых 30 с или 30 с после выполнения последней задачи, задача выполняется снова.Если текущая отметка времени + 128 мс меньше предыдущей (отметка времени 30 с вычитается из последнего выполнения) + 30 с, значит есть время перезвонить

//检测逆行时间,根据NTP规范允许+128ms
if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {
                    HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));
                    this.previous = now;
                    HikariPool.this.softEvictConnections();
                    HikariPool.this.fillPool();
                    return;
                }

В этот момент журнал будет распечатан, предыдущее время сбрасывается на текущее время, соединение устанавливается как недоступное, и соединение создается заново.

держите минимум связи

  1. Если время не ошибается, оно рассудитidleTimeout, если больше 0, удалить текущее незанятое соединение

  2. Определить, превышает ли оно минимальное количество подключенийminimumIdle, если больше, перейти к текущему бездействующему соединению на основеlastAccessed(последнее время доступа) для сортировки, затем обхода

  3. Если время простоя каждого извлеченного соединения превысилоidleTimeout, и успешно подключите соединение сNOT_IN_USE(холостой) изменен наRESERVED(помечено как зарезервированное)

  4. затем закройте соединение

  5. Наконец, создайте новое соединение

    public void run() {
                try {
                    //刷新connectionTimeout、validationTimeout
                    HikariPool.this.connectionTimeout = HikariPool.this.config.getConnectionTimeout();
                    HikariPool.this.validationTimeout = HikariPool.this.config.getValidationTimeout();
    				 HikariPool.this.leakTask.updateLeakDetectionThreshold(HikariPool.this.config.getLeakDetectionThreshold());
                    long idleTimeout = HikariPool.this.config.getIdleTimeout();
                    long now = HikariPool.clockSource.currentTime();
    				//时钟回拨判断
                    if (HikariPool.clockSource.plusMillis(now, 128L) < HikariPool.clockSource.plusMillis(this.previous, HikariPool.this.HOUSEKEEPING_PERIOD_MS)) {
                        HikariPool.this.LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));
                        this.previous = now;
                        HikariPool.this.softEvictConnections();
                        HikariPool.this.fillPool();
                        return;
                    }
    				
                    if (now > HikariPool.clockSource.plusMillis(this.previous, 3L * HikariPool.this.HOUSEKEEPING_PERIOD_MS / 2L)) {
                        HikariPool.this.LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", HikariPool.this.poolName, HikariPool.clockSource.elapsedDisplayString(this.previous, now));
                    }
    
                    this.previous = now;
                    String afterPrefix = "Pool ";
                    if (idleTimeout > 0L) {
    				//取出空闲连接  连接状态,IN_USE(1:使用中)、NOT_IN_USE(0:闲置中)、REMOVED(-1:已移除)、RESERVED(-1:标记为保留中)
                        List<PoolEntry> idleList = HikariPool.this.connectionBag.values(0);
                        int removable = idleList.size() - HikariPool.this.config.getMinimumIdle();
                        if (removable > 0) {
                            HikariPool.this.logPoolState("Before cleanup ");
                            afterPrefix = "After cleanup  ";
    						//排序
                            idleList.sort(PoolEntry.LASTACCESS_COMPARABLE);
                            Iterator var8 = idleList.iterator();
    
                            while(var8.hasNext()) {
                                PoolEntry poolEntry = (PoolEntry)var8.next();
    							//idleTimeout判断,连接状态修改
                                if (HikariPool.clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && HikariPool.this.connectionBag.reserve(poolEntry)) {
                                    HikariPool.this.closeConnection(poolEntry, "(connection has passed idleTimeout)");
                                    --removable;
                                    if (removable == 0) {
                                        break;//keep min idle cons
                                    }
                                }
                            }
                        }
                    }
    
                    HikariPool.this.logPoolState(afterPrefix);
                    HikariPool.this.fillPool();
                } catch (Exception var10) {
                    HikariPool.this.LOGGER.error("Unexpected exception in housekeeping task", var10);
                }
    
            }
        }
    

вопрос

минимальная проблема несогласованности бездействия

В текущей версии при инициализации приложения будет инициализирован и пул соединений, но когда мы настроим свойства источника данныхminimumIdle<maximumPoolSizeкогда, напримерминимальныйIdle=4, максимальныйPoolSize=10, но когда инициализация приложения завершена,minimumIdle=5. Вероятно, когда приложение запускается,SpringManagedTransactionСоединение будет получено, и в это время будет инициализирован пул соединений, но это соединение не оценивается инициализацией пула соединений, и, наконец, создается дополнительное соединение. обновить версию до последней3.4.5После баг исчезает (какая версия модифицирована неизвестно).

//HikariCP-3.4.5.jar
private synchronized void fillPool()
   {
      final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
                                   - addConnectionQueueReadOnlyView.size();
      if (connectionsToAdd <= 0) logger.debug("{} - Fill pool skipped, pool is at sufficient level.", poolName);
		//生成的个数减去了1
      for (int i = 0; i < connectionsToAdd; i++) {
         addConnectionExecutor.submit((i < connectionsToAdd - 1) ? poolEntryCreator : postFillPoolEntryCreator);
      }
   }

//HikariCP-2.5.1.jar 的该方法
for (int i = 0; i < connectionsToAdd; i++) {
         addBagItem();
      }

проблема тайм-аута

Журнал ошибок выглядит следующим образом:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms.
  1. Сначала проверьте, нет ли проблем с конфигурацией,maxLifetimeне может быть больше базы данныхtime_wait, запрос конфигурации mysqlпоказывать переменные типа '%timeout%', значение по умолчанию — 8 часов.

  2. Если с конфигурацией проблем нет, возможно, дело не в HikariCP. Причина этой ошибки в том, что запрос к пулуborrowКогда нет доступных подключений, вызывается тайм-аут.Первая точка, в настоящее время мы должны подумать о том, разумно ли количество наших пулов соединений, что связано с объемом бизнеса;Второй момент, чтобы увидеть, есть ли в нашем коде медленный SQL;третья точка, связанный с используемой структурой сохраняемого уровня, анализирует, кто удерживает наше соединение, каков его метод управления соединением и при каких обстоятельствах соединение будет восстановлено.

    Я решил изучить эту статью, потому что я столкнулся с этой ошибкой и не мог ее найти. Причина моей ошибки - третий пункт выше. В моем проекте для взаимодействия с базой данных используется jpa, и он очень простой. соединение должно быть возвращено в пул в ближайшее время, но после моего теста, после запроса к базе данных, соединение не освобождается, а только после завершения всего моего сеанса соединение возвращается.

    Ядром jpa являетсяhibernate-core, искал в сетиhibernateСтратегия освобождения соединения, знайте причину. Стратегия освобождения соединения в спящем режиме hibernate.connection.release_mode имеет следующие четыре свойства:

    1. on_close, соединение JDBC будет разорвано только в том случае, если сеанс явно закрыт или отключен.
    2. after_transaction, ссылка освобождается каждый раз, когда транзакция завершается
    3. after_statement, после каждого вызова JDBC он будет активно разрывать соединение
    4. auto, выберите after_statement для стратегий транзакций JTA и CMT и after_transaction для стратегий транзакций JDBC.

Моя версия проекта Springboot - 1.x, даже если я поставлюhibernate-coreОбновите до более высокой версии и включите вещи, также на основеon_closeспособ разблокировать соединение, я тестировал версию 2.x, и это также, когда я не открываю вещиon_close, после открытия вещей становитсяafter_transaction, непонятно, почему конкретная версия 1.х не действует при открытии вещей.

использованная литература
  1. Способ объяснения блок-схемы очень ясен и прост для понимания, а версия относительно высока.

    Как HikariCP управляет подключениями к базе данных?