HikariCP 获取连接

获取连接的过程

HikariDataSource 提供获取数据库连接的方法:getConnection(),但真正是从连接池中获取连接的,即:HikariPool#getConnection(). 获取连接的时序图如下:
 
HikariCP 中获取数据库连接时序图 
对应的源码内容如下:

   public Connection getConnection() 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();
   }

此段代码是获取数据库连接的入口,如果 HikariDataSource 没有采用 no-args 的构造方式,那么 HikariCP 将会延迟初始化连接池,即在第一次获取数据库连接时初始化连接池。关于连接池的初始化问题,我们将在其他博文中另作讨论。

在此我们应该注意到:

  1. 获取数据库连接时,如果连接池没有初始化(fastPathPool 为 null)则要要进行连接池的初始化(初始化 pool);
  2. fastPathPool 与 pool 同为 HikariDataSource 的成员变量,分别对应于数据源的两种初始化方式;
   private final HikariPool fastPathPool;
   // See http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
   private volatile HikariPool pool;
  1. HikariCP 没有让初始化数据源没有使用同一个连接池,fastPathPool 相比于 pool ,没有 volatile 关键字修饰,可以看出在性能优化方面所做的细节。虽然 volatile 变量的读取性能已经和非 volatile 变量的读取非常接近。

可以看到,获取连接的主要过程就是通过 ConcurrentBag 的 borrow 方法获取 PoolEntry,然后通过获取到的 PoolEntry 创建物理连接,并返回代理连接 ProxyConnection。

为什么是 ProxyConnection

可以看出获取到的数据库连接最终是通过 ProxyFactory#getProxyConnection 方法创建了一个代理连接 ProxyConnection . 代码内容如下:

   Connection createProxyConnection(final ProxyLeakTask leakTask, final long now)
   {
      return ProxyFactory.getProxyConnection(this, connection, openStatements, leakTask, now, isReadOnly, isAutoCommit);
   }

其中 connection 为 PoolEntry 的成员变量。也就是说获取的数据库连接是基于 PoolEntry.connection 由 ProxyFactory#getProxyConnection 方法创建的代理连接。

我们看下 ProxyFactory#getProxyConnection 方法的内容,其源码如下:

   /**
    * Create a proxy for the specified {@link Connection} instance.
    * @param poolEntry the PoolEntry holding pool state
    * @param connection the raw database Connection
    * @param openStatements a reusable list to track open Statement instances
    * @param leakTask the ProxyLeakTask for this connection
    * @param now the current timestamp
    * @param isReadOnly the default readOnly state of the connection
    * @param isAutoCommit the default autoCommit state of the connection
    * @return a proxy that wraps the specified {@link Connection}
    */
   static ProxyConnection getProxyConnection(final PoolEntry poolEntry, final Connection connection, final FastList<Statement> openStatements, final ProxyLeakTask leakTask, final long now, final boolean isReadOnly, final boolean isAutoCommit)
   {
      // Body is replaced (injected) by JavassistProxyFactory
      throw new IllegalStateException("You need to run the CLI build and you need target/classes in your classpath to run.");
   }

可以看到方法中并没有具体的创建数据库的代理连接的代码内容,但从抛出异常之前的注释可以看出在会通过 JavassistProxyFactory 生成具体的代码。编译生成的代码会替换掉 getProxyConnection 的方法体。

getProxyConnection 的方法体是 JavassistProxyFactory#generateProxyClass 生成的。其字节码技术为 javassist,之所以采用 javassist,是因为其生成的字节码更加精简。这一点在官方 wiki 有提到,也是 HikariCP 表现得性能很快的原因之一。

我们可以注意到实际的内容源码中的内容有明显的差异:

实际的 ProxyFactory#getProxyConnection :

    static ProxyConnection getProxyConnection(PoolEntry var0, Connection var1, FastList<Statement> var2, ProxyLeakTask var3, long var4, boolean var6, boolean var7) {
        return new HikariProxyConnection(var0, var1, var2, var3, var4, var6, var7);
    }

com.zaxxer.hikari.pool.HikariProxyConnection#HikariProxyConnection:

    protected HikariProxyConnection(PoolEntry var1, Connection var2, FastList var3, ProxyLeakTask var4, long var5, boolean var7, boolean var8) {
        super(var1, var2, var3, var4, var5, var7, var8);
    }

com.zaxxer.hikari.pool.ProxyConnection#ProxyConnection

    protected ProxyConnection(PoolEntry poolEntry, Connection connection, FastList<Statement> openStatements, ProxyLeakTask leakTask, long now, boolean isReadOnly, boolean isAutoCommit) {
        this.poolEntry = poolEntry;
        this.delegate = connection;
        this.openStatements = openStatements;
        this.leakTask = leakTask;
        this.lastAccess = now;
        this.isReadOnly = isReadOnly;
        this.isAutoCommit = isAutoCommit;
    }

可以看出:

  1. ProxyFactory 中创建代理连接的内容与实际编译的结果完全不一样:源码中创建代理连接只是抛了个 IllegalStateException 异常,而编译后却是创建了 HikariProxyConnection 并返回。这是由于 HikariCP 通过 javassist 生成了具体的代理类字节码。生成的代理字节码的主要工具是 JavassistProxyFactory。
  2. 代理连接包含了 poolEntry、connection、leakTask 等引用。因此代理连接相较于 poolEntry 主要是提供了泄露检测的功能。

在 com.zaxxer.hikari.pool.HikariPool#getConnection(long) 中创建代理连接的过程如下:

return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);

其中,leakTaskFactory.schedule(poolEntry) 即是创建连接泄露检测任务。详情如下:

   ProxyLeakTask schedule(final PoolEntry poolEntry)
   {
      return (leakDetectionThreshold == 0) ? ProxyLeakTask.NO_LEAK : scheduleNewTask(poolEntry);
   }

即:

  • leakDetectionThreshold 为 0 时,不进行连接泄露检测;
  • leakDetectionThreshold 不为 0 时,进行连接泄露检测;

关于连接泄露检测的详细内容,可以移步本人的关于 HikariCP leakDetectionThreshold 的博客。

总结

获取连接的过程大致为:

  1. 连接从连接池 fastPathPool 或者 pool(两者对应的初始化方式不同);
  2. 获取连接时,HikariPool 从其资源集合 ConcurrentBag 借用(borrow)PoolEntry,PoolEntry 是数据库物理连接的一对一封装;
  3. 根据借用到的 PoolEntry,创建代理连接并返回;

参考

Down-the-Rabbit-Hole

HikariCP是一个高性能的Java连接池库,用于管理数据库连接。它提供了一种轻量级、快速且可靠的连接池解决方案,适用于各种数据库系统,包括ClickHouse。 要使用HikariCP连接ClickHouse数据库,你需要进行以下步骤: 1. 首先,你需要在你的项目中添加HikariCP的依赖。你可以在Maven或Gradle配置文件中添加以下依赖: Maven: ```xml <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>3.4.5</version> </dependency> ``` Gradle: ```groovy implementation 'com.zaxxer:HikariCP:3.4.5' ``` 2. 在你的代码中,你需要配置HikariCP连接池的参数。以下是一个示例配置: ```java import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; // 创建HikariCP配置对象 HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:clickhouse://localhost:8123/mydatabase"); config.setUsername("username"); config.setPassword("password"); // 创建HikariCP数据源 HikariDataSource dataSource = new HikariDataSource(config); ``` 在上面的示例中,你需要将`jdbcUrl`替换为你的ClickHouse数据库的连接URL,并提供正确的用户名和密码。 3. 现在你可以使用HikariCP连接池来获取数据库连接并执行查询或更新操作。以下是一个简单的示例: ```java import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; // 从HikariCP连接池中获取连接 try (Connection connection = dataSource.getConnection()) { // 创建PreparedStatement对象并执行查询 String sql = "SELECT * FROM mytable"; try (PreparedStatement statement = connection.prepareStatement(sql); ResultSet resultSet = statement.executeQuery()) { // 处理查询结果 while (resultSet.next()) { // 读取数据 int id = resultSet.getInt("id"); String name = resultSet.getString("name"); // ... } } } catch (SQLException e) { // 处理异常 e.printStackTrace(); } ``` 以上就是使用HikariCP连接ClickHouse数据库的基本步骤。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值