HikariCP实战 | 通过查看源码分析如何解决maxLifeTime配置问题

Posted Python&Basketball

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HikariCP实战 | 通过查看源码分析如何解决maxLifeTime配置问题相关的知识,希望对你有一定的参考价值。

目录


1、追本溯源

很多年前在stackoverflow上写过一篇文章:
https://stackoverflow.com/questions/28180562/hikaricp-and-maxlifetime#


hikariCP是非常优秀的JDBC connection pool. 官方配置:https://github.com/brettwooldridge/HikariCP

2、解决hikariCP的maxLifetime配置出现以下warn问题

WARN com.zaxxer.hikari.HikariConfig - maxLifetime is less than 120000ms, using default 1800000ms.

3、具体解决步骤(查看源码)

i don’t know your HikariCP Version, but in the version 2.2.4 you will find the reason why it will throw the above warning.

HikariConfig.class (in the com.zaxxer.hikari.HikariConfig):

 private void More ...validateNumerics()
  
     Logger logger = LoggerFactory.getLogger(getClass());

     if (connectionTimeout == Integer.MAX_VALUE) 
        logger.warn("No connection wait timeout is set, this might cause an infinite wait.");
     

     if (minIdle < 0 || minIdle > maxPoolSize) 
        minIdle = maxPoolSize;
     

     if (maxLifetime < 0) 
        logger.error("maxLifetime cannot be negative.");
        throw new IllegalArgumentException("maxLifetime cannot be negative.");
     
     else if (maxLifetime > 0 && maxLifetime < TimeUnit.SECONDS.toMillis(120)) 
        logger.warn("maxLifetime is less than 120000ms, using default ms.", MAX_LIFETIME);
        maxLifetime = MAX_LIFETIME;
     

     if (idleTimeout != 0 && idleTimeout < TimeUnit.SECONDS.toMillis(30)) 
        logger.warn("idleTimeout is less than 30000ms, using default ms.", IDLE_TIMEOUT);
        idleTimeout = IDLE_TIMEOUT;
     
     else if (idleTimeout > maxLifetime && maxLifetime > 0) 
        logger.warn("idleTimeout is greater than maxLifetime, setting to maxLifetime.");
        idleTimeout = maxLifetime;
     

from this code, the maxLifeTime is at least 120000ms, using default 1800000ms. so you can’t set the maxLifeTime to 30000ms(30*1000). I guess your HikariCP version is at least older than 2.2.4.

But when you find the latest HikariCP version 2.7.4. it said “We strongly recommend setting this value, and it should be at least 30 seconds less than any database or infrastructure imposed connection time limit.”

the same class HikariConfig.class:

private void validateNumerics() 
    if(this.maxLifetime != 0L && this.maxLifetime < TimeUnit.SECONDS.toMillis(30L)) 
        LOGGER.warn(" - maxLifetime is less than 30000ms, setting to default ms.", this.poolName, Long.valueOf(MAX_LIFETIME));
        this.maxLifetime = MAX_LIFETIME;
    

    if(this.idleTimeout + TimeUnit.SECONDS.toMillis(1L) > this.maxLifetime && this.maxLifetime > 0L) 
        LOGGER.warn(" - idleTimeout is close to or more than maxLifetime, disabling it.", this.poolName);
        this.idleTimeout = 0L;
    

    if(this.idleTimeout != 0L && this.idleTimeout < TimeUnit.SECONDS.toMillis(10L)) 
        LOGGER.warn(" - idleTimeout is less than 10000ms, setting to default ms.", this.poolName, Long.valueOf(IDLE_TIMEOUT));
        this.idleTimeout = IDLE_TIMEOUT;
    

    if(this.leakDetectionThreshold > 0L && !unitTest && (this.leakDetectionThreshold < TimeUnit.SECONDS.toMillis(2L) || this.leakDetectionThreshold > this.maxLifetime && this.maxLifetime > 0L)) 
        LOGGER.warn(" - leakDetectionThreshold is less than 2000ms or more than maxLifetime, disabling it.", this.poolName);
        this.leakDetectionThreshold = 0L;
    

    if(this.connectionTimeout < 250L) 
        LOGGER.warn(" - connectionTimeout is less than 250ms, setting to ms.", this.poolName, Long.valueOf(CONNECTION_TIMEOUT));
        this.connectionTimeout = CONNECTION_TIMEOUT;
    

    if(this.validationTimeout < 250L) 
        LOGGER.warn(" - validationTimeout is less than 250ms, setting to ms.", this.poolName, Long.valueOf(VALIDATION_TIMEOUT));
        this.validationTimeout = VALIDATION_TIMEOUT;
    

    if(this.maxPoolSize < 1) 
        this.maxPoolSize = this.minIdle <= 0?10:this.minIdle;
    

    if(this.minIdle < 0 || this.minIdle > this.maxPoolSize) 
        this.minIdle = this.maxPoolSize;
    


完整代码看这里:
https://github.com/brettwooldridge/HikariCP/blob/dev/src/main/java/com/zaxxer/hikari/HikariConfig.java

/*
 * Copyright (C) 2013, 2014 Brett Wooldridge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.zaxxer.hikari;

import com.codahale.metrics.health.HealthCheckRegistry;
import com.zaxxer.hikari.metrics.MetricsTrackerFactory;
import com.zaxxer.hikari.util.PropertyElf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.security.AccessControlException;
import java.sql.Connection;
import java.util.Properties;
import java.util.TreeSet;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadLocalRandom;

import static com.zaxxer.hikari.util.UtilityElf.getNullIfEmpty;
import static com.zaxxer.hikari.util.UtilityElf.safeIsAssignableFrom;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

@SuppressWarnings("SameParameterValue", "unused")
public class HikariConfig implements HikariConfigMXBean

   private static final Logger LOGGER = LoggerFactory.getLogger(HikariConfig.class);

   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 SOFT_TIMEOUT_FLOOR = Long.getLong("com.zaxxer.hikari.timeoutMs.floor", 250L);
   private static final long IDLE_TIMEOUT = MINUTES.toMillis(10);
   private static final long MAX_LIFETIME = MINUTES.toMillis(30);
   private static final long DEFAULT_KEEPALIVE_TIME = 0L;
   private static final int DEFAULT_POOL_SIZE = 10;

   private static boolean unitTest = false;

   // Properties changeable at runtime through the HikariConfigMXBean
   //
   private volatile String catalog;
   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 connectionInitSql;
   private String connectionTestQuery;
   private String dataSourceClassName;
   private String dataSourceJndiName;
   private String driverClassName;
   private String exceptionOverrideClassName;
   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;

   private long keepaliveTime;

   private volatile boolean sealed;

   /**
    * Default constructor
    * <p>
    * If the System property @code hikari.configurationFile is set,
    * then the default constructor will attempt to load the specified configuration file
    * <p>
    * @link #HikariConfig(String propertyFileName) can be similarly used
    * instead of using the system property
    */
   public HikariConfig()
   
      dataSourceProperties = new Properties();
      healthCheckProperties = new Properties();

      minIdle = -1;
      maxPoolSize = -1;
      maxLifetime = MAX_LIFETIME;
      connectionTimeout = CONNECTION_TIMEOUT;
      validationTimeout = VALIDATION_TIMEOUT;
      idleTimeout = IDLE_TIMEOUT;
      initializationFailTimeout = 1;
      isAutoCommit = true;
      keepaliveTime = DEFAULT_KEEPALIVE_TIME;

      var systemProp = System.getProperty("hikaricp.configurationFile");
      if (systemProp != null) 
         loadProperties(systemProp);
      
   

   /**
    * Construct a HikariConfig from the specified properties object.
    *
    * @param properties the name of the property file
    */
   public HikariConfig(Properties properties)
   
      this();
      PropertyElf.setTargetFromProperties(this, properties);
   

   /**
    * Construct a HikariConfig from the specified property file name.  <code>propertyFileName</code>
    * will first be treated as a path in the file-system, and if that fails the
    * Class.getResourceAsStream(propertyFileName) will be tried.
    *
    * @param propertyFileName the name of the property file
    */
   public HikariConfig(String propertyFileName)
   
      this();

      loadProperties(propertyFileName);
   

   // ***********************************************************************
   //                       HikariConfigMXBean methods
   // ***********************************************************************

   /** @inheritDoc */
   @Override
   public String getCatalog()
   
      return catalog;
   

   /** @inheritDoc */
   @Override
   public void setCatalog(String catalog)
   
      this.catalog = catalog;
   


   /** @inheritDoc */
   @Override
   public long getConnectionTimeout()
   
      return connectionTimeout;
   

   /** @inheritDoc */
   @Override
   public void setConnectionTimeout(long connectionTimeoutMs)
   
      if (connectionTimeoutMs == 0) 
         this.connectionTimeout = Integer.MAX_VALUE;
      
      else if (connectionTimeoutMs < SOFT_TIMEOUT_FLOOR) 
         throw new IllegalArgumentException("connectionTimeout cannot be less than " + SOFT_TIMEOUT_FLOOR + "ms");
      
      else 
         this.connectionTimeout = connectionTimeoutMs;
      
   

   /** @inheritDoc */
   @Override
   public long getIdleTimeout()
   
      return idleTimeout;
   

   /** @inheritDoc */
   @Override
   public void setIdleTimeout(long idleTimeoutMs)
   
      if (idleTimeoutMs < 0) 
         throw new IllegalArgumentException("idleTimeout cannot be negative");
      
      this.idleTimeout = idleTimeoutMs;
   

   /** @inheritDoc */
   @Override
   public long getLeakDetectionThreshold()
   
      return leakDetectionThreshold;
   

   /** @inheritDoc */
   @Override
   public void setLeakDetectionThreshold(long leakDetectionThresholdMs)
   
      this.leakDetectionThreshold = leakDetectionThresholdMs;
   

   /** @inheritDoc */
   @Override
   public long getMaxLifetime()
   
      return maxLifetime;
   

   /** @inheritDoc */
   @Override
   public void setMaxLifetime(long maxLifetimeMs)
   
      this.maxLifetime = maxLifetimeMs;
   

   /** @inheritDoc */
   @Override
   public int getMaximumPoolSize()
   
      return maxPoolSize;
   

   /** @inheritDoc */
   @Override
   public void setMaximumPoolSize(int maxPoolSize)
   
      if (maxPoolSize < 1) 
         throw new IllegalArgumentException("maxPoolSize cannot be less than 1");
      
      this.maxPoolSize = maxPoolSize;
   

   /** @inheritDoc */
   @Override
   public int getMinimumIdle()
   
      return minIdle;
   

   /** @inheritDoc */
   @Override
   public void setMinimumIdle(int minIdle)
   
      if (minIdle < 0) 
         throw new IllegalArgumentException("minimumIdle cannot be negative");
      
      this.minIdle = minIdle;
   

   /**
    * Get the default password to use for DataSource.getConnection(username, password) calls.
    * @return the password
    */
   public String getPassword()
   
      return password;
   

   /**
    * Set the default password to use for DataSource.getConnection(username, password) calls.
    * @param password the password
    */
   @Override
   public void setPassword(String password)
   
      this.password = password;
   

   /**
    * Get the default username used for DataSource.getConnection(username, password) calls.
    *
    * @return the username
    */
   public String getUsername()
   
      return username;
   

   /**
    * Set the default username used for DataSource.getConnection(username, password) calls.
    *
    * @param username the username
    */
   @Override
   public void setUsername(String username)
   
      this.username = username;
   

   /** @inheritDoc */
   @Override
   public long getValidationTimeout()
   
      return validationTimeout;
   

   /** @inheritDoc */
   @Override
   public void setValidationTimeout(long validationTimeoutMs)
   
      if (validationTimeoutMs < SOFT_TIMEOUT_FLOOR) 
         throw new IllegalArgumentException("validationTimeout cannot be less than " + SOFT_TIMEOUT_FLOOR + "ms");
      

      this.validationTimeout = validationTimeoutMs;
   

   // ***********************************************************************
   //                     All other configuration methods
   // ***********************************************************************

   /**
    * Get the SQL query to be executed to test the validity of connections.
    *
    * @return the SQL query string, or null
    */
   public String getConnectionTestQuery()
   
      return connectionTestQuery;
   

   /**
    * Set the SQL query to be executed to test the validity of connections. Using
    * the JDBC4 <code>Connection.isValid()</code> method to test connection validity can
    * be more efficient on some databases and is recommended.
    *
    * @param connectionTestQuery a SQL query string
    */
   public void setConnectionTestQuery(String connectionTestQuery)
   
      checkIfSealed();
      this.connectionTestQuery = connectionTestQuery;
   

   /**
    * Get the SQL string that will be executed on all new connections when they are
    * created, before they are added to the pool.
    *
    * @return the SQL to execute on new connections, or null
    */
   public String getConnectionInitSql()
   
      return connectionInitSql;
   

   /**
    * Set the SQL string that will be executed on all new connections when they are
    * created, before they are added to the pool.  If this query fails, it will be
    * treated as a failed connection attempt.
    *
    * @param connectionInitSql the SQL to execute on new connections
    */
   public void setConnectionInitSql(String connectionInitSql)
   
      checkIfSealed();
      this.connectionInitSql = connectionInitSql;
   

   /**
    * Get the @link DataSource that has been explicitly specified to be wrapped by the
    * pool.
    *
    * @return the @link DataSource instance, or null
    */
   public DataSource getDataSource()
   
      return dataSource;
   

   /**
    * Set a @link DataSource for the pool to explicitly wrap.  This setter is not
    * available through property file based initialization.
    *
    * @param dataSource a specific @link DataSource to be wrapped by the pool
    */
   public void setDataSource(DataSource dataSource)
   
      checkIfSealed();
      this.dataSource = dataSource;
   

   /**
    * Get the name of the JDBC @link DataSource class used to create Connections.
    *
    * @return the fully qualified name of the JDBC @link DataSource class
    */
   public String getDataSourceClassName()
   
      return dataSourceClassName;
   

   /**
    * Set the fully qualified class name of the JDBC @link DataSource that will be used create Connections.
    *
    * @param className the fully qualified name of the JDBC @link DataSource class
    */
   public void setDataSourceClassName(String className)
   
      checkIfSealed();
      this.dataSourceClassName = className;
   

   /**
    * Add a property (name/value pair) that will be used to configure the @link DataSource/@link java.sql.Driver.
    *
    * In the case of a @link DataSource, the property names will be translated to Java setters following the Java Bean
    * naming convention.  For example, the property @code cachePrepStmts will translate into @code setCachePrepStmts()
    * with the @code value passed as a parameter.
    *
    * In the case of a @link java.sql.Driver, the property will be added to a @link Properties instance that will
    * be passed to the driver during @link java.sql.Driver#connect(String, Properties) calls.
    *
    * @param propertyName the name of the property
    * @param value the value to be used by the DataSource/Driver
    */
   public void addDataSourceProperty(String propertyName, Object value)
   
      checkIfSealed();
      dataSourceProperties.put(propertyName, value);
   

   public String getDataSourceJNDI()
   
      return this.dataSourceJndiName;
   

   public void setDataSourceJNDI(String jndiDataSource)
   
      checkIfSealed();
      this.dataSourceJndiName = jndiDataSource;
   

   public Properties getDataSourceProperties()
   
      return dataSourceProperties;
   

   public void setDataSourceProperties(Properties dsProperties)
   
      checkIfSealed();
      dataSourceProperties.putAll(dsProperties);
   

   public String getDriverClassName()
   
      return driverClassName;
   

   public void setDriverClassName(String driverClassName)
   
      checkIfSealed();

      var driverClass = attemptFromContextLoader(driverClassName);
      try 
         if (driverClass == null) 
            driverClass = this.getClass().getClassLoader().loadClass(driverClassName);
            LOGGER.debug("Driver class  found in the HikariConfig class classloader ", driverClassName, this.getClass().getClassLoader());
         
       catch (ClassNotFoundException e) 
         LOGGER.error("Failed to load driver class  from HikariConfig class classloader ", driverClassName, this.getClass().getClassLoader());
      

      if 以上是关于HikariCP实战 | 通过查看源码分析如何解决maxLifeTime配置问题的主要内容,如果未能解决你的问题,请参考以下文章

如何把连接池这事说细?HikariCP故障排查实战

HikariCP

Android 源码分析实战 - 动态加载修复 so 库

Android实战----从Retrofit源码分析到Java网络编程以及HTTP权威指南想到的

《Elasticsearch 源码解析与优化实战》第14章:Cluster模块分析

《Elasticsearch 源码解析与优化实战》第14章:Cluster模块分析