HikariCP Analysis (1)

SpringBoot 2.x officially announced the use of HikariCP as the default database connection pool, as a new generation of connection pool, HikariCP performance is very good, and the code is very simple, concurrent design is also worth learning from us

Initialization process

Core components

Today we will first analyze the HikariCP initialization process, starting with the previous image

The diagram above shows the general logic of obtaining connections, involving the following classes, which contain most of the HikariCP logic

  • HikariDataSourceAs the default data source implementation class loaded when Spring Boot2.x is started, it is provided for direct use by the upper layer service and holdsHikariPoolobject
  • HikariPoolReal connection pool management provides the ability to obtain, discard, close, and reclaim connections for upper-layer use and internal storageConcurrentBagobject
  • ConcurrentBag The real existence of the connection is where the inside holds oneCopyWriteArrayListobjectsharedListAnd also throughthreadListProvides thread-level connection caching
  • ProxyFactoryGenerate wrapper classesHikariProxyConnectionSince it is a wrapper class, there must be some additional operation package in it. Javassist technology is also involved here. See the logic for detailsJavassistProxyFactory

Start the process

First, service startup is the logic for loading the connection pool

@Bean(name = "agreementDataSource")
    @ConfigurationProperties(prefix = "mybatis")
    public DataSource agreementDataSource(a) {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "readSource")
    @ConfigurationProperties(prefix = "mybatis.read")
    public DataSource readSource(a) {
        return DataSourceBuilder.create().build();
    }
Copy the code

The Datasource bean is simply initialized. The HikariPool inside the Datasource is not initialized. The logic to initialize the HikariPool is set to the first connection

Obtaining connections for the first time
@Override
   public Connection getConnection(a) throws SQLException
   {
      if (isClosed()) {
         throw new SQLException("HikariDataSource " + this + " has been closed.");
      }

      if(fastPathPool ! =null) {
         return fastPathPool.getConnection();
      }

      // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
      HikariPool result = pool;
      if (result == null) {
         synchronized (this) {
            result = pool;
            if (result == null) {
               validate();
               LOGGER.info("{} - Starting...", getPoolName());
               try {
                  pool = result = new HikariPool(this);
                  this.seal();
               }
               catch (PoolInitializationException pie) {
                  if (pie.getCause() instanceof SQLException) {
                     throw (SQLException) pie.getCause();
                  }
                  else {
                     throw pie;
                  }
               }
               LOGGER.info("{} - Start completed.", getPoolName()); }}}return result.getConnection();
   }
Copy the code

On the first request, the connection pool is initialized to determine whether the current connection pool is empty. If it is empty, the connection pool is initialized with two HikariPool objects. The performance is not as good as fastPathPool without volatile modifiers. We use the no-parameter construct for initialization, so pool is used here

  • fastPathPoolfinal Embellish, determined at construction time. If null is used with a no-parameter construct, use the parameterized construct andpoolThe same
  • pool:volatile Modifier, the no-argument construct is not setpoolIn thegetConnectionWhen constructingpool, with parameters to construct andfastPathPoolThe same

Initialize the connection pool

The initialization code is as follows:

public HikariPool(final HikariConfig config){
      super(config);

      this.connectionBag = new ConcurrentBag<>(this);
      this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;

      this.houseKeepingExecutorService = initializeHouseKeepingExecutorService();

      checkFailFast();

      if(config.getMetricsTrackerFactory() ! =null) {
         setMetricsTrackerFactory(config.getMetricsTrackerFactory());
      }
      else {
         setMetricRegistry(config.getMetricRegistry());
      }

      setHealthCheckRegistry(config.getHealthCheckRegistry());

      registerMBeans(this);

      ThreadFactory threadFactory = config.getThreadFactory();

      LinkedBlockingQueue<Runnable> addConnectionQueue = new LinkedBlockingQueue<>(config.getMaximumPoolSize());
      this.addConnectionQueue = unmodifiableCollection(addConnectionQueue);
      this.addConnectionExecutor = createThreadPoolExecutor(addConnectionQueue, poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
      this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());

      this.leakTaskFactory = new ProxyLeakTaskFactory(config.getLeakDetectionThreshold(), houseKeepingExecutorService);

      this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
   }
Copy the code

This method is used to initialize the entire connection pool and all the properties in the pool

  1. usingconfigInitialize the various connection pool properties and generate a data source for producing physical connectionsDriverDataSource
  2. Initialize the core class that holds connection objectsconnectionBag
  3. Initialize an object of type thread pool for delayed taskshouseKeepingExecutorService, used for subsequent execution of some delay/timing tasks (such as connection leak check delay task, in additionmaxLifeTime This object is also responsible for retrieving and closing the connection.
  4. Preheat the connection pool,HikariCPWill be in the flowcheckFailFastInitialization of a connection object into the pool, of course, trigger the process is guaranteedinitializationTimeout > 0This configuration property represents the time left for the warmup operation (the default value of 1 does not occur when the warmup fails). withDruidthroughinitialSizeControl the number of preheating connected objects is different,HikariCPOnly one connection object is preheated into the pool.
  5. Initialize a thread pool objectaddConnectionExecutorIs used to extend connection objects
  6. Initialize a thread pool objectcloseConnectionExecutorTo close the connection object

Pool.getconnection (), HikariCP gets the connection, and the internal management of the connection is analyzed later

Monitoring the related

Finally, the content of monitoring in HikariCP should be added. In the process of our use, we should know the status and pressure of the whole database connection pool in real time, and inform the developers in time when there is a problem

HikariCP built-in implementation of a variety of monitoring implementation, if you need to customize the implementation, you can also expand the corresponding interface, and set in the initial monitoring configuration

public interface MetricsTrackerFactory {
   /**
    * Create an instance of an IMetricsTracker.
    *
    * @param poolName the name of the pool
    * @param poolStats a PoolStats instance to use
    * @return a IMetricsTracker implementation instance
    */
   IMetricsTracker create(String poolName, PoolStats poolStats);
}
Copy the code

Here’s a look at some of the metrics implemented by default

public static final String HIKARI_METRIC_NAME_PREFIX = "hikaricp";

   private static final String METRIC_CATEGORY = "pool";
   private static final String METRIC_NAME_WAIT = HIKARI_METRIC_NAME_PREFIX + ".connections.acquire";
   private static final String METRIC_NAME_USAGE = HIKARI_METRIC_NAME_PREFIX + ".connections.usage";
   private static final String METRIC_NAME_CONNECT = HIKARI_METRIC_NAME_PREFIX + ".connections.creation";

   private static final String METRIC_NAME_TIMEOUT_RATE = HIKARI_METRIC_NAME_PREFIX + ".connections.timeout";
   private static final String METRIC_NAME_TOTAL_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections";
   private static final String METRIC_NAME_IDLE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.idle";
   private static final String METRIC_NAME_ACTIVE_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.active";
   private static final String METRIC_NAME_PENDING_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.pending";
   private static final String METRIC_NAME_MAX_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.max";
   private static final String METRIC_NAME_MIN_CONNECTIONS = HIKARI_METRIC_NAME_PREFIX + ".connections.min";
Copy the code
  • hikaricp_connections_pending Monitoring is very necessary
    • Number of threads waiting for connections. When troubleshooting performance problems, this indicator is an important reference indicator. If the number of threads waiting for connections is large for a certain period of time, you can consider increasing the size of the database connection pool
  • hikaricp_connections_acquire Monitoring is very necessary
    • Connect to get the time. You can view the time it takes to obtain a connection within a time range
  • hikaricp_connections_timeout Monitoring is very necessary
    • Gets the number of threads that timed out the connection. An increase in the number of timeouts is either a problem with the database (a database operation takes too long to return a connection) or there are not enough connections in the connection pool
  • hikaricp_connections_active Need to monitor
    • Number of current active connections. You can adjust the configuration based on the increasing trend of the number of active connections

Configuration interpretation

HikariDataSource validates the pool attributes while initializing them. If the pool attributes are invalid, they are reset to the default value after validate

Important configuration

  • connectionTimeout :
    • Description: Maximum number of milliseconds to wait for connections from the pool
    • Default value: 30000ms
    • validateReset: Reset to 30 seconds if less than 250 ms
    • Recommended configuration: The default value is used when the pool connection is not tight. In scenarios with frequent interactions, you can adjust the pool size to facilitate monitoring and adjustment
  • idleTimeout :
    • Description: Maximum time a connection is allowed to remain idle in the pool
    • Default value: 600000ms
    • validateReset: IfidleTimeout+ 1 seconds >maxLifetimemaxLifetime> 0 is reset to 0 (never exits); ifidleTimeout! If = 0 and less than 10 seconds, it is reset to 10 seconds
    • Recommended configuration: This value is available only whenminimumIdleDefined as less thanmaximumPoolSize, and the number of connections in the current pool is greater thanminimumIdle In this case, use the default Settings
  • maxLifetime :
    • Description: Maximum connection lifetime in the pool
    • Default value: 1800000ms
    • validateReset: If it is not 0 and less than 30 seconds, it will be reset to 30 minutes
    • Recommended configuration: It is best to set it to more than database timeoutwait_timeoutSmall enough to avoid database level timeouts, but connections in the pool are still alive
  • minimumIdle :
    • Description: Minimum number of free connections maintained in the pool
    • Default value: 10
    • validateReset:minIdle< 0 orminIdle > maxPoolSizeIs reset tomaxPoolSize
    • Recommended configuration: see online information are recommended Settings andmaximumPoolSizeThis maximally reduces the likelihood of blocking access connections due to capacity expansion and reduction, but has been found to be less necessary in practice. This can be combined with the performance of online connection pool monitoring. It is best to keep this configuration greater than the activie_Connection value that can guarantee the peak traffic. For example, the number of active connections in our service at peak times is about 10, so 10 ~ 20 can be set
  • maximumPoolSize :
    • Description: Maximum number of connections in the pool, including idle and active connections
    • Default value: 10
    • validateReset: IfmaxPoolSizeIf the value is less than 1, it will be reset. whenminIdle<= 0 is reset toDEFAULT_POOL_SIZEIt is 10; ifminIdle> 0, reset tominIdleThe value of the
    • Recommended configuration: To ensure that the bottom connection pool can withstand extreme traffic, you can set this value to several times that ofminimumIdle In the case of single-database multi-instance deployment, the number of connections to all instances is less than the upper limit of the database

conclusion

  • HikariCPInitialization is done by default when the first connection is acquired
  • HikariCPSome important monitoring indicators, so as to know the connection pool online usage
  • HikariCPSome important configurations, default configurations combined with the actual situation of their own application services, slowly tune to ensure that the connection pool does not become a bottleneck in the whole service