01Hikari 源码解析之connection相关

一、初始化

类 HikariDataSource 的 getConnection() 为 取connection 的方法,主要流程如下:

  1. 判断 当前的 dataSource 是否关闭,如果关闭 ,抛出异常
  2. 判断fastPathPool 是否为null ,默认为null
  3. 判断当前的pool 是否为null ,如果为空,那就就行初始化,所以项目启动完了之后,第一次请求时间比较长
  4. 不为空 ,返回 result.getConnection()
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;
            ...  ... ...... 省略,详细过程在下面分析
            xxxx .... xxxxx 
            }
      }
 }         

第一次获取connection的时候,需要初始化,大致流程如下:
在这里插入图片描述

  1. 设置poolName ,前缀 HikariPool- ,后面跟的是 pool_number
  2. validateNumerics,这里是 对一些参数校验,具体 查看下面的表格
  3. 对 pool 进行初始,里面还有一系列的 参数初始化 ,这里暂不展开,主要涉及houseKeeperTask ,这个是 在delay 100ms 之后 每隔 30秒 定时运行,详细在下面概述
    在这里插入图片描述

1.1 houseKeeperTask 定时清理 和 填充连接池

在 初始化的时候, houseKeeperTask 设置为 initialDelay = 100L, 每隔30S(默认) 运行一次

this.houseKeeperTask = houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 100L, housekeepingPeriodMs, MILLISECONDS);

主要逻辑在 内部类 HouseKeeper的run() 方法里面,看一下主要的逻辑,就是不断的维护线程池里面的连接数

public void run() {
         try {
    ... 省略 ...
             previous = now;
            String afterPrefix = "Pool ";
            // 如果idleTimeout >0 ,默认10分钟,600000ms 并且 最小连接数 小于最大连接数
            // 如果不配置 最小连接数 ,最小连接数 默认和 max_num_size 一样大,都是默认10
            if (idleTimeout > 0L && config.getMinimumIdle() < config.getMaximumPoolSize()) {
               logPoolState("Before cleanup ");
               afterPrefix = "After cleanup  ";

               final List<PoolEntry> notInUse = connectionBag.values(STATE_NOT_IN_USE);
               int toRemove = notInUse.size() - config.getMinimumIdle();
               // 删除超时的连接
               for (PoolEntry entry : notInUse) {
                  if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
                     closeConnection(entry, "(connection has passed idleTimeout)");
                     toRemove--;
                  }
               }
            }

            logPoolState(afterPrefix);
            //进行连接池补充
            fillPool(); // Try to maintain minimum connections
         }
         catch (Exception e) {
            LOGGER.error("Unexpected exception in housekeeping task", e);
         }
      }
      
private synchronized void fillPool()
   {
      final int connectionsToAdd = Math.min(config.getMaximumPoolSize() - getTotalConnections(), config.getMinimumIdle() - getIdleConnections())
                                   - addConnectionQueue.size();
      for (int i = 0; i < connectionsToAdd; i++) {
         addConnectionExecutor.submit((i < connectionsToAdd - 1) ? POOL_ENTRY_CREATOR : POST_FILL_POOL_ENTRY_CREATOR);
      }
   }

1.2 创建连接 createPoolEntry

这里主要介绍一下 createPoolEntry 方法,重点就是 创建了一个 poolEntry之后,在到 maxlifetime 最大还差2.5% 时,进行标记,如果 当前空闲直接回收,如果回收成功,在判断一下是否需要新建一个poolEntry

private PoolEntry createPoolEntry()
   {
      try {
         final PoolEntry poolEntry = newPoolEntry();

         final long maxLifetime = config.getMaxLifetime();
         if (maxLifetime > 0) {
            // variance up to 2.5% of the maxlifetime
            final long variance = maxLifetime > 10_000 ? ThreadLocalRandom.current().nextLong( maxLifetime / 40 ) : 0;
            final long lifetime = maxLifetime - variance;
            // 快接近最大存活时间时,对连接进行标记,如果当前连接空闲,直接回收
            如果回收成功,再判断是否有等待线程,如果有说明当前资源紧张,再创建一个poolEntry
            poolEntry.setFutureEol(houseKeepingExecutorService.schedule(
               () -> {
                  if (softEvictConnection(poolEntry, "(connection has passed maxLifetime)", false /* not owner */)) {
                     addBagItem(connectionBag.getWaitingThreadCount());
                  }
               },
               lifetime, MILLISECONDS));
         }

         return poolEntry;
      }
      catch (Exception e) {
         if (poolState == POOL_NORMAL) { // we check POOL_NORMAL to avoid a flood of messages if shutdown() is running concurrently
            LOGGER.debug("{} - Cannot acquire connection from data source", poolName, (e instanceof ConnectionSetupException ? e.getCause() : e));
         }
         return null;
      }
   }
   
     public void addBagItem(final int waiting)
   {
      final boolean shouldAdd = waiting - addConnectionQueue.size() >= 0; // Yes, >= is intentional.
      if (shouldAdd) {
         addConnectionExecutor.submit(POOL_ENTRY_CREATOR);
      }
   }

二、获取Connection

上面已经大致涉及第一次初始化的 过程,接下来概述一些 getConnection的流程,主要是在类HikariPool 的
getConnection() 里面,代码如下,

public Connection getConnection(final long hardTimeout) throws SQLException
   {
   // 获取信号量,最大值1000
   suspendResumeLock.acquire();
   final long startTime = currentTime();
   try {
      long timeout = hardTimeout;
      do {
         // 从connectionBag 里面 获取borrow
         PoolEntry poolEntry = connectionBag.borrow(timeout, MILLISECONDS);
         if (poolEntry == null) {
            break; // We timed out... break and throw exception
         }
         final long now = currentTime();
         // 判断当前的poolEntry 是否有效,无效就关闭
         if (poolEntry.isMarkedEvicted() || (elapsedMillis(poolEntry.lastAccessed, now) > aliveBypassWindowMs && !isConnectionAlive(poolEntry.connection))) {
            closeConnection(poolEntry, poolEntry.isMarkedEvicted() ? EVICTED_CONNECTION_MESSAGE : DEAD_CONNECTION_MESSAGE);
            timeout = hardTimeout - elapsedMillis(startTime);
         }
         else {
            metricsTracker.recordBorrowStats(poolEntry, startTime);
            return poolEntry.createProxyConnection(leakTaskFactory.schedule(poolEntry), now);
         }
      } while (timeout > 0L);  // 在connectionTimeout 时间内 一直循环直到找到可用的

      metricsTracker.recordBorrowTimeoutStats(startTime);
      throw createTimeoutException(startTime);
   }
   catch (InterruptedException e) {
      Thread.currentThread().interrupt();
      throw new SQLException(poolName + " - Interrupted during connection acquisition", e);
   }
   finally {
      suspendResumeLock.release();
   }
}

其流程图如下:
在这里插入图片描述
大致流程如下:

  1. 获取信号量suspendResumeLock ,这里 默认是 1000个

  2. 从connectionBag 里面获取 poolEntry ,这里 做了一定的优化 ,ConcurrentBag(borrow)详细的见02Hikari源码解析之ConcurrentBag、FastList分析 ,这里概述一下:
    2.1. 先从 threadList 里面获取,同一个Request 即同一个线程时 从里面获取
    2.2. 如果threadList 里面可用的 ,再从sharedList 里面获取 ,sharedList 存放ConcurrentBag中全部用于出借的资源 ,这里有一个注意点,这里也是 hkari 快的原因,就是判断如果 还有其他的等待者,赶紧的再去创建连接,而没有判断 当前 sharedList 包里面 是否够用了,所以 高QPS的情况下,很容易连接池达到最大的量
    2.3. 当现有全部资源全部在使用中,等待一个被释放的资源或者一个新资源,handoffQueue里面获取

  3. 判断 poolEntity 是否被标记驱逐 或者 是否connectionAlive 以及上一次访问时间到当前是否 大于 aliveBypassWindowMs(默认 500ms)

  4. 如果是有效连接,便开始 创建createProxyConnection

  5. 否则 继续循环 2-3 的 步骤,直到 connectionTimeout 用完

三、归还Connection

逻辑代码主要在 HikariPool 类下面的 recycle(final PoolEntry poolEntry) ,流程图如下:
主要逻辑:
1.先将里面的所有Statement 关闭,并将保存的list 对象openStatements 清空
2.将 代理delegate 状态设置为 CLOSE_CONNECTION
3.将 poolEntity 的状态设置为 STATE_NOT_IN_USE
4.判断是否有 等待线程,如果有 直接推到 handoffQueue 队列 进行第一时间的交换
5.否则放入 本地 threadLocalList ,默认是弱引用,这样不影响回收
在这里插入图片描述

四、总结

Hikari 在获取连接的时候,做了一定的优化:

首先 查看 是否有可用的连接,从 ThreadLocal<List> 里面获取,这里 的 list ,是自定义了一个 FastList,而 FastList 和普通的ArrayList 不同之处就是 Fast 在get() 和remove () 方面去掉了rangeCheck(index); 一定程度上又快了一些.
全部资源用了 CopyOnWriteArrayList 定义的 sharedList ,这样读的是没有锁,写的时候进行了 加锁操作
SynchronousQueue 是使用了 公平锁 机制, SynchronousQueue 就是一定程度上无缓存的队列.
Hikari 作者 不推荐使用minimumIdle,该属性控制HikariCP尝试在池中维护的最小空闲连接数。如果空闲连接低于此值并且池中的总连接数少于maximumPoolSize,HikariCP将尽最大努力快速高效地添加其他连接。但是,为了获得最佳性能和响应尖峰需求,我们建议不要设置此值,而是允许HikariCP充当固定大小的连接池。

Hikari 作者 认为如果minimumIdle小于maximumPoolSize的话,在流量激增的时候需要额外的连接,此时在请求方法里头再去处理新建连接会造成性能损失,即会导致数据库一方面降低连接建立的速度,另一方面也会影响既有的连接事务的完成,间接影响了这些既有连接归还到连接池的速度。 Hikari 作者 认为minimumIdle与maximumPoolSize设置成一样,多余的空闲连接不会对整体的性能有什么严重影响。

支付宝微信
支付宝微信
如果有帮助记得打赏哦特别需要您的打赏哦
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值