Introduction to the

What is HikariCP?

HikariCP is essentially a database connection pool.

What problems does HikariCP solve?

Creating and closing database connections is expensive, and HikariCP uses “pooling” to reuse connections to reduce the overhead.

Why use HikariCP?

  1. HikariCP is currently the fastest connection pool. Even the popular boneCP stopped maintenance and voluntarily gave way to it. SpringBoot also sets it as the default connection pool.

  1. HikariCP is very lightweight. The 4.0.3 JAR package used in this article is only 156 KB, and its source code is really very concise.

What is the text about?

This article will cover the following (read as required due to its length) :

  1. How to use HikariCP (Getting started, JMX, etc.)
  2. Description of configuration parameters
  3. Source code analysis

How do I use HikariCP

demand

HikariCP is used to obtain the connection object, and simple add, delete, change and check the user data.

Project environment

The JDK: 1.8.0 comes with _231

Maven: 3.6.3

IDE: Spring Tool Suite 4.6.1.release

Mysql connector – Java: 8.0.15

Mysql: 5.7.28

Hikari: 4.0.3

Introduction of depend on

The Project type is Maven Project and the package method is JAR.

 		<! -- test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <! -- hikari -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>4.0.3</version>
        </dependency>
        <! -- Mysql driver -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
        <! -- log -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
Copy the code

Write hikari. The properties

This article uses a configuration file to configure HikariCP. Of course, we can configure HikariCP explicitly in code, but it is not recommended. Since this is an introductory example, I have only given the required parameters. The rest will be explained later.

jdbcUrl=jdbc:mysql://localhost:3306/github_demo? characterEncoding=utf8&serverTimezone=GMT%2B8
username=root
password=root
Copy the code

Initialize the connection pool

When initializing the connection pool, we can either specify the configuration file explicitly in our code or through startup parameters.

/ / loading configuration files, and can not to construct and use launch parameters hikaricp. ConfigurationFile specify the configuration file (not recommended, behind will say why)
HikariConfig config = new HikariConfig("/hikari2.properties");
HikariDataSource dataSource = new HikariDataSource(config);
Copy the code

Initializes the connection pool after, we can through HikariDataSource. GetConnection () method to get connection object, and then to add and delete operation, this part will not show here.

How do I manage connection pools using JMX

demand

Enable JMX functionality and use JConsole to manage connection pools.

Open the JMX

Add configurations based on the introductory example. RegisterMbeans will be set to true and JMX will be enabled.

#-------------JMX--------------------------------
# Enable JMX
# the default false
registerMbeans=true
Whether to suspend and resume connection pooling via JMX
The default is false
allowPoolSuspension=true
The connection pool name.
# automatic by default
poolName=zzsCP
Copy the code

Starting a connection pool

To see what happens, let the main thread go to sleep for 20 minutes.

    public static void main(String[] args) throws InterruptedException {
        HikariConfig config = new HikariConfig("/hikaricp_base.properties");
        HikariDataSource dataSource = new HikariDataSource(config);
        Thread.sleep(20 * 60 * 1000);
        dataSource.close();
    }
Copy the code

Use JConsole to view

Run the main method, use the JDK tool JConsole to connect to our project, and see our connection pool in the MBean TAB. Next, we can do something like this:

  1. Use PoolConfig to dynamically modify the configuration (only some parameters can be modified).
  2. Get the number of connections (active, idle, and all) in the connection Pool, get the number of threads waiting for connections, suspend and resume the connection Pool, discard unused connections, and so on.

To learn more about JMX functionality, see my blog post: How to Manage Applications using JMX?

Description of configuration parameters

The configuration parameters for HikariCP are very simple compared to other connection pools, with a few features to note:

  1. HikariCP enforces a check on connection activity when lending a connection, unlike other connection pools where you can opt out of checking;
  2. IdleTimeout and maxLifetime are checked by default. You can disable them but are not recommended.
  3. KeepaliveTime and leakDetectionThreshold are not checked by default. You can enable keepaliveTime and leakDetectionThreshold.

Required parameters

Note that jdbcUrl and dataSourceClassName are optional.

# -- -- -- -- -- -- -- -- -- -- -- -- -- the required parameters -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
JDBC driver DataSource implementation class fully qualified class name. XA DataSource is not supported
#, if specified, will use DataSouce HikariCP. GetConnection getting connection instead of using the DriverManager. GetConnection, official recommendations specified (except the mysql)
# dataSourceClassName=
#, if specified, HikariCP will use the DriverManager. GetConnection getting connection instead of using the DataSouce. GetConnection
jdbcUrl=jdbc:mysql://localhost:3306/github_demo? characterEncoding=utf8&serverTimezone=GMT%2B8
User name and password
username=root
password=root
Copy the code

Common parameters

Whether connections lent from the pool automatically commit transactions by default
# the default true
autoCommit=true
How long am I willing to wait when I lend a connection from the pool? If a timeout occurs, an SQLException is thrown
The default value is 30000 ms, and the minimum value is 250 ms. JMX dynamic change is supported
connectionTimeout=30000
How long does a connection remain idle in the pool before it is discarded
# minimumIdle < maximumPoolSize takes effect
The default value is 600000 ms. The minimum value is 10000 ms. 0 indicates that this function is disabled. JMX dynamic change is supported
idleTimeout=600000
# How often to check connection activity
Check that the connection is removed from the pool (if idle), then isValid() or connectionTestQuery is called to check for activity, and if it passes the check, it is put back into the pool.
The minimum value is 30000 ms, which must be smaller than maxLifetime. JMX dynamic change is supported
keepaliveTime=0
# When a connection survives long enough, HikariCP will discard it when it is idle
The default value is 1800000 ms and the minimum value is 30000 ms. 0 indicates that this function is disabled. JMX dynamic change is supported
maxLifetime=1800000
Select * from 'x' where 'x' = 'x';
If the driver supports JDBC4.0, it is recommended not to set the Connection. IsValid () will be called by default, which is more efficient
# Empty by default
# connectionTestQuery=
The minimum number of free connections in the pool.
# Add connections when idle connection < minimumIdle, total connection < maximumPoolSize
The default value is maximumPoolSize. JMX dynamic change is supported
minimumIdle=5
The maximum number of connections in the pool (both idle and active)
The default is 10. JMX dynamic change is supported
maximumPoolSize=10
MetricRegistry implementation class for logging connection pool metrics
The default is null and can only be set by code
# metricRegistry=
The HealthCheckRegistry implementation class for reporting the health status of the connection pool
The default is null and can only be set by code
# healthCheckRegistry=
The connection pool name.
# automatic by default
poolName=zzsCP
Copy the code

Rarely used parameter

Whether TODO fails quickly if the connection cannot be successfully initialized while starting the connection pool
When # >0, an attempt is made to obtain the connection. If the fetch time exceeds the specified time, the connection pool will not be enabled and an exception will be thrown
When # =0, an attempt is made to obtain and validate the connection. The pool will not be enabled if the authentication fails but the pool will still be enabled if the authentication fails
When # <0, the pool is enabled regardless of whether the fetch or validation is successful.
The default value is 1
initializationFailTimeout=1
Whether to isolate HikariCP's own queries in the transaction.
# autoCommit takes effect only if the value is false
# the default false
isolateInternalQueries=false
Whether to suspend and resume connection pooling via JMX
The default is false
allowPoolSuspension=false
Is the connection set to read-only when it is taken out of the pool
The default value is false
readOnly=false
# Enable JMX
# the default false
registerMbeans=true
Database catalog
The default is determined by the driver
# catalog=
Initialization statements that need to be executed after each connection is created and before it is pooled
If the execution fails, the connection will be discarded
# Empty by default
# connectionInitSql=
# Driver implementation class used by JDBC drivers
$curl = $curl = $curl = $curl = $curl
# Empty by default
# driverClassName=
The default transaction isolation level for the connection
The default value is null, determined by the driver
# transactionIsolation=
Verify the timeout allowed for connection activity
The default value is 5000 ms. The minimum value is 250 ms. The value must be less than connectionTimeout. JMX dynamic change is supported
validationTimeout=5000
How long can the connected object be loaned out
The default value is 0 (not enabled). The minimum allowed value is 2000 ms. JMX dynamic change is supported
leakDetectionThreshold=0
DataSourceClassName = dataSourceClassName
The default is null and can only be set by code
# dataSource=
# database schema
The default is determined by the driver
# schema=
ThreadFactory specifies the ThreadFactory instance of the pool thread
The default is null and can only be set by code
# threadFactory=
# setRemoveOnCancelPolicy(true) # setRemoveOnCancelPolicy(true)
The default is null and can only be set by code
# scheduledExecutor=
The datasource name of the JNDI configuration
# Empty by default
# dataSourceJndiName=
Copy the code

Source code analysis

HikariCP source less and fine, very high readability. If you’ve never seen code that looks like poetry, take a look at HikariCP.

As a reminder, before reading the HiakriCP source code, Need to master CopyOnWriteArrayList, AtomicInteger, SynchronousQueue will, Semaphore, AtomicIntegerFieldUpdater, LockSupport JDK take classes use.

Note that the following code has been reduced for length and readability.

Why is HikariCP fast?

Database connection pooling has been around for a long time and is a fairly mature technology. The most widely used libraries are boneCP, DBCP, C3P0, Druid, and so on. At a time when database connection pooling had reached a bottleneck and the so-called performance improvement was just a few code details optimization, HikariCP came along and took off very quickly, not just by average, but by leaps and bounds compared to other connection pooling. Here are the results of the JMH test ([test item address](brettwooldridge/ hikaricp-benchmark: JHM benchmarks for JDBC Connection Pools (github.com))

Why is HikariCP fast? I see a lot of explanations online, for example, the extensive use of JDK and package tools to avoid coarse-grained locking, the use of custom classes such as FastList, dynamic proxy classes, etc. I don’t think that’s the main reason.

HikariCP is faster because of optimization at the level of abstraction.

Traditional model — the model of rules and regulations

A connection pool, as its name suggests, is a pool of connected objects. Almost all connection pools abstract a pond from the code level. The number of connections in the pool is not constant. For example, connections fail and need to be removed, new connections are created, users borrow or return connections, and so on. To sum up, there are only four operations on the pool: Borrow, return, Add, and remove.

Connection pools are typically designed like this: The BORROW and remove actions take connections out of the pond, and the Add and return actions add connections to the pond. I call this the “traditional model.”

The “traditional model” is a more formal model that, at an abstract level, is very much in line with our real life. For example, if someone borrows my money, it is no longer in my wallet. The familiar DBCP, C3P0, Druid, and so on are all based on the “traditional model”.

Mark model – Fewer locks

HikariCP, however, doesn’t go back to the old ways. Instead, it optimizes the “traditional model” to make connection pooling really fast.

In the “traditional model”, borrow, return, Add and remove all need to add the same lock, that is, only one thread is allowed to operate the pool at a time, and thread switching will be very frequent when the concurrency is high. Because multiple threads operate on the same pond, a lock is required to connect to the pool for thread safety. Is there anything we can do about it?

HikariCP does this. Instead of taking borrow’s connection out of the pond, it is marked “borrowed”, and when it returns, the “borrowed” mark of the connection is removed. I call this the “tagging model.” The “tag model” enables borrow and return actions to be unlocked. How did you do that?

So first of all, when I borrow, I need to see which connection in the pond I can borrow from. This involves reading the pool of connections, since the number of connections in the pool is not constant, we must lock it for consistency. However, HikariCP does not add, why? Because HikariCP tolerates reading inconsistencies. When Borrow, we’re not actually reading the actual pond, but a snapshot of the current pond. If we look at the HikariCP connection, it’s a CopyOnWriteArrayList object, and we know that CopyOnWriteArrayList is a set that’s safe to write and not safe to read.

public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable {
   // Store a set of connections
   private final CopyOnWriteArrayList<T> sharedList;
}
Copy the code

Then, when we find a loanable connection, we need to mark it as loanable. Notice that it is possible for multiple threads to want to mark it. What do you do? Do we have to lock it? Don’t forget that we can use the CAS mechanism to update the token of the connection so that locks are not required. See how HikariCP is implemented.

   public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException {
      final int waiting = waiters.incrementAndGet();
      try {
         for (T bagEntry : sharedList) {
            if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               returnbagEntry; }}return null;
      }
      finally{ waiters.decrementAndGet(); }}Copy the code

Thus, in the “tag model,” only Add and remove need to be locked; Borrow and return do not. Through this radical design, the performance of connection pooling is greatly improved.

This is the main reason I think HikariCP is fast.

HikariCP main class

So, let’s look at the source code of HikariCP.

There are not many classes of HikariCP, and these are the most important ones, as shown in the figure. As you can see, this class structure is very similar to DBCP2.

These categories can be divided into four parts:

  1. User interface. Users generally useDataSource.getConnection()To get the connection object.
  2. The JMX support.
  3. Configuration information. useHikariConfigLoad the configuration file, or manually configure itHikariConfigIs normally constructed as an input parameterHikariDataSourceObject;
  4. Connection pool. The process of obtaining the connection isHikariDataSource.getConnection()->HikariPool.getConnection()->ConcurrentBag.borrow(long, TimeUnit).It’s important to note that,ConcurrentBagIs the real connection pool, andHikariPoolIs used to manage connection pools.

ConcurrentBag- The most core class

ConcurrentBag is the core class of HikariCP. It is the real connection pool of HikariCP. If you don’t want to see too much code, that’s all you need to see.

By design, ConcurrentBag is a generic resource pool. It can be a database connection pool or a pool of other objects that implement the IConcurrentBagEntry interface. So, if we need to build our own pool in our project, we can just use this off-the-shelf component.

public class ConcurrentBag<T extends IConcurrentBagEntry> implements AutoCloseable {
	private final CopyOnWriteArrayList<T> sharedList;
}
Copy the code

ConcurrentBag:

attribute describe
CopyOnWriteArrayList sharedList Holds resource objects in three states: In use, unused, and reserved
ThreadLocal threadList Holds the resource object returned by the current thread
SynchronousQueue handoffQueue This is a block queue with no capacity, and both outgoing and incoming queues can choose to block or not
AtomicInteger waiters Number of threads currently waiting to fetch elements

How to use these fields in ConcurrentBag? Here is the borrow method to illustrate:

   public T borrow(long timeout, final TimeUnit timeUnit) throws InterruptedException
   {
      // 1. First fetch resources from threadList
       
      final List<Object> list = threadList.get();
      for (int i = list.size() - 1; i >= 0; i--) {
         final Object entry = list.remove(i);
         final T bagEntry = weakThreadLocals ? ((WeakReference<T>) entry).get() : (T) entry;
         if(bagEntry ! =null && bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
            returnbagEntry; }}// The number of threads waiting for connections +1
      final int waiting = waiters.incrementAndGet();
      try {
         // 2. If not, the object is retrieved from sharedList
         for (T bagEntry : sharedList) {
            if (bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               // I don't understand this step, it seems dispensable
               if (waiting > 1) {
                  listener.addBagItem(waiting - 1);
               }
               returnbagEntry; }}// The listener can't get the resource from sharedList.
         listener.addBagItem(waiting);
        
         // 3. If it has not been obtained, it will block waiting for idle connections
         timeout = timeUnit.toNanos(timeout);
         do {
            final long start = currentTime();
            // Three things can happen here,
            // 1. Time out, return null
            // 2. The resource is in use, and the loop continues
            // 3. The element status is not used, and the element status is changed to used and returned
            final T bagEntry = handoffQueue.poll(timeout, NANOSECONDS);
            if (bagEntry == null || bagEntry.compareAndSet(STATE_NOT_IN_USE, STATE_IN_USE)) {
               return bagEntry;
            }
            timeout -= elapsedNanos(start);
         } while (timeout > 10 _000);
         // 4. Return null after timeout
         return null;
      }
      finally {
         // Number of threads waiting to acquire connections -1waiters.decrementAndGet(); }}Copy the code

In the above method, the only thing that blocks the thread is handoffQueue.poll(timeout, NANOSECONDS), and we don’t see any synchronization or locking beyond that.

HikariPool- Manages connection pools

In addition to ConcurrentBag, HikariPool is an important class for managing connection pools.

The following describes the fields in HikariPool:

Attribute type and attribute name instructions
DataSource dataSource The data source used to get the native connection object. DriverDataSource is normally used when we do not specify it
ThreadPoolExecutor addConnectionExecutor The thread pool that performs the task of creating a connection.Only one thread is started to execute the task.
ThreadPoolExecutor closeConnectionExecutor The thread pool that executes the task of closing native connections.Only one thread is started to execute the task.
ScheduledExecutorService houseKeepingExecutorService The thread pool used to perform tasks such as checking idleTimeout, leakDetectionThreshold, keepaliveTime, and maxLifetime.

In order to understand the meanings of the above fields more clearly, I have drawn a simple graph, which is not very strict, but will take a look. In this diagram, the client thread can call to borrow, requite and remove operation, remove houseKeepingExecutorService thread can call for operation, Only addConnectionExecutor can perform add operations.

Something interesting

With these two classes in hand, the entire source view of HikariCP should be complete. Now let’s talk about some interesting things.

Why does HikariDataSource have two Hikaripools

In the following code, there are two Hikaripools in HikariDataSource.

public class HikariDataSource extends HikariConfig implements DataSource.Closeable
{
   private final HikariPool fastPathPool;
   private volatile HikariPool pool;
}
Copy the code

Why do you do that?

First, from a performance standpoint, it is better to use fastPathPool to create connections than pools because pools are volatile and caching is not allowed for visibility. So why pool?

We open HikariDataSource. GetConnection (), you can see, the pool can be used to support the existence of double check the lock. What I’m curious about here is why not reference the HikariPool to fastPathPool? This is a question that you can study if you are interested.

   public Connection getConnection(a) throws SQLException
   {
      if(fastPathPool ! =null) {
         return fastPathPool.getConnection();
      }

      HikariPool result = pool;
      if (result == null) {
         synchronized (this) {
            result = pool;
            if (result == null) {
               validate();
               pool = result = new HikariPool(this);
               this.seal(); }}}return result.getConnection();
   }
Copy the code

In fact, the two HikariPool objects have two values:

The value can be fastPathPool = Pool = new HikariPool(this). This value occurs when a HikariDataSource is created by constructing a new HikariConfig configuration with parameters.

Value two: fastPathPool = null; Pool = new HikariPool(this). This value occurs when a HikariDataSource is created by constructing new HikariDataSource() with no arguments.

Therefore, I prefer to use a new HikariDataSource(HikariConfig Configuration) because we will use fastPathPool to get the connection.

How to load configuration

The code for HikariCP to load the configuration is very neat. We directly from PropertyElf. SetTargetFromProperties started watching (Object, the Properties) method, as follows.

   // Set the properties parameter to HikariConfig
   public static void setTargetFromProperties(final Object target, final Properties properties)
   {
      if (target == null || properties == null) {
         return;
      }
    
      // Get all the methods of HikariConfig
      List<Method> methods = Arrays.asList(target.getClass().getMethods());
      properties.forEach((key, value) -> {
         // If it is a dataSource.* parameter, add it directly to the dataSourceProperties property
         if (target instanceof HikariConfig && key.toString().startsWith("dataSource.")) {
            ((HikariConfig) target).addDataSourceProperty(key.toString().substring("dataSource.".length()), value);
         }
         else {
            // Find the corresponding setter method for the parameter and assign itsetProperty(target, key.toString(), value, methods); }}); }Copy the code

Compared to other libraries (especially Druid), HikariCP loads configurations cleanly without having to load them individually by parameter name, which is easier to maintain later. Of course, this approach can also be applied to real projects.

In addition, when configuring HikariCP, do not write wrong parameters or add irrelevant parameters. Otherwise, an error will be reported because the corresponding setter method cannot be found.

The above basic HikariCP source code. Find other interesting points to add later, also welcome to point out the shortcomings.

Finally, thanks for reading.

The resources

HikariCP github

The 2021-05-20 revision

Related source code please move: github.com/ZhangZiShen…

This article original articles, reproduced please attach the original source link: www.cnblogs.com/ZhangZiShen…