HikariCP探活机制如何保证链接有效_hikaricp 保活_m0_46485771的博客-CSDN博客

最近遇到了数据库链接池的链接被负载均衡器关闭之后,应用端没有准确捕获到无效链接,导致最终报错。所有很想知道HikariCP探活机制如何保证链接有效的。关键的参数如下:

      connection-timeout: 30000 #可接受的最低连接超时为250 ms。 默认值:30000(30秒)
      idle-timeout: 60000
      max-lifetime: 600000

本来以为idle-timeout空闲失效时间,如果链接超过了这个数值,连接池会主动去续链接时间或者给清理掉。续链接时间是因为这个链接还需要使用,不能被关闭,所以需要续。清理掉是因为不需要了,可以清理掉了。这里和数据库连接池最小数有关系了。常规的理解,数据库连接池维护最小链接,那肯定得保证这些最小链接是有效的。
按照一些连接池的建议规范,将最小和最大设置成一样大,做成一个固定大小的连接池。然后发现,idle-timeout在固定大小连接池情况下,直接失效了,失效了就是没有用处。
一下就懵了,这怎么搞,链接无效了怎么弄?
还有就是,就算是个可变的链接池大小,如果链接数达到了最小链接,idle-timeout也会失效,不会去做空闲超时处理。
然后我发现还有一个参数max-lifetime,直接字面意思理解是链接的最大生命周期。有了这个参数,那是不是idle-time就只是用来做连接池中大于核心连接池数的链接剔除操作?

这都是官网文档和字面意思的理解。其实从这些参数以及对一个数据库连接池的假想认知(从功能使用角度),这么流行的一个数据库连接池,肯定要做空闲剔除、链接续时间或者重新获取链接的操作的。
最后走上了源码路。
怎么撸源码呢,有maven能链接仓库就行。直接拉取源码文件,sts工具点击就能看源码,带注释的。

如何进去代码呢,直接从配置文件进去。yml文件里配置了hikari的配置,直接点解就能链接到配置,然后看看配置具体是什么个意思,可能发现宝藏。

HikariDataSource

看不懂单词的我,查了下Evict,代表剔除。是不是说剔除无效链接的??然后看代码注释吧。

   /**
    * Evict a connection from the pool.  If the connection has already been closed (returned to the pool)
    * this may result in a "soft" eviction; the connection will be evicted sometime in the future if it is
    * currently in use.  If the connection has not been closed, the eviction is immediate.
    * 链接被归还到连接池可能做软剔除,然后未来一个时间真正剔除。
    * 如果链接被关闭了,立刻剔除。
    *
    * @param connection the connection to evict from the pool
    */
   public void evictConnection(Connection connection)
   {
      HikariPool p;
      if (!isClosed() && (p = pool) != null && connection.getClass().getName().startsWith("com.zaxxer.hikari")) {
         p.evictConnection(connection);
      }
   }

这个方法是在DataSource里,这是提供的对外口,让调用者去主动剔除?谁是调用者,DataSource一般是数据库链接配置。p这里是HikariPool,那就是链接池做剔除操作,那剔除的条件是什么?

HikariPool

剔除链接

/**
    * "Soft" evict a Connection (/PoolEntry) from the pool.  If this method is being called by the user directly
    * through {@link com.zaxxer.hikari.HikariDataSource#evictConnection(Connection)} then {@code owner} is {@code true}.
    * 如果链接是空闲的,直接关闭,否则只能做标记。这里说链接是空闲的,最小链接里的链接能被标记为空闲么?
    * If the caller is the owner, or if the Connection is idle (i.e. can be "reserved" in the {@link ConcurrentBag}),
    * then we can close the connection immediately.  Otherwise, we leave it "marked" for eviction so that it is evicted
    * the next time someone tries to acquire it from the pool.
    *
    * @param poolEntry the PoolEntry (/Connection) to "soft" evict from the pool
    * @param reason the reason that the connection is being evicted
    * @param owner true if the caller is the owner of the connection, false otherwise
    * @return true if the connection was evicted (closed), false if it was merely marked for eviction
    */
   private boolean softEvictConnection(final PoolEntry poolEntry, final String reason, final boolean owner)
   {
      poolEntry.markEvicted();
      if (owner || connectionBag.reserve(poolEntry)) {
         closeConnection(poolEntry, reason);
         return true;
      }

      return false;
   }

如果链接是空闲的,直接关闭,否则只能做标记。这里说链接是空闲的,最小链接里的链接能被标记为空闲么?如果不能,那么最小链接里的链接永远不会被剔除清理,不被清理是合理的,如果都清理了,和保持最小链接就矛盾了。
反证法,最小链接不被剔除。

那接下来继续找还有没有剔除逻辑或者链接失效判断逻辑?

获取链接的几个方法

   /** {@inheritDoc} */
   @Override
   public int getActiveConnections()
   {
      return connectionBag.getCount(STATE_IN_USE);
   }
看一下都有哪些状态。

  int STATE_NOT_IN_USE = 0;  //字面意思理解
      int STATE_IN_USE = 1;
      int STATE_REMOVED = -1;
      int STATE_RESERVED = -2;

连接池分了4中链接状态,既然区分了状态,那就是说有一个地方专门存链接,每个链接都能被清晰的标注。打标之后才能更好的工作。

   /** {@inheritDoc} */
   @Override
   public int getIdleConnections()
   {
      return connectionBag.getCount(STATE_NOT_IN_USE);
   }

空闲链接是没有使用状态的链接。什么是没有使用呢?多久不用代码没有使用?

  /**
    * The house keeping task to retire and maintain minimum idle connections.
    * 管家任务保持最小链接数。管家任务是一个线程。
    */
   private final class HouseKeeper implements Runnable
   {
      private volatile long previous = plusMillis(currentTime(), -housekeepingPeriodMs);
//housekeepingPeriodMs 固定值30秒钟
//当期服务器时间减去30秒
      @Override
      public void run()
      {
         try {
            // refresh values in case they changed via MBean
            connectionTimeout = config.getConnectionTimeout();
            validationTimeout = config.getValidationTimeout();
//leak detection 泄露检查            leakTaskFactory.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
            catalog = (config.getCatalog() != null && !config.getCatalog().equals(catalog)) ? config.getCatalog() : catalog;
//终于找到关键的ideltimeout了。。。。。。
//看下是怎么用的?
            final long idleTimeout = config.getIdleTimeout();
            final long now = currentTime();
//NTP 知道是什么吗,时钟同步用的。网络时间协议。
            // Detect retrograde time, allowing +128ms as per NTP spec.
            //如果最新的时间+128毫秒,比前面时间还要小,说明服务器时钟中途被修改了,系统会提示。
            if (plusMillis(now, 128) < plusMillis(previous, housekeepingPeriodMs)) {
               logger.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
                           poolName, elapsedDisplayString(previous, now));
               previous = now;
               softEvictConnections();
               return;
            }
            else if (now > plusMillis(previous, (3 * housekeepingPeriodMs) / 2)) {
            //当期时间与前一个时间比较,超过45秒。也是时钟不对
            
               // No point evicting for forward clock motion, this merely accelerates connection retirement anyway
               logger.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", poolName, elapsedDisplayString(previous, now));
            }
//以上对时钟有要求,【左偏慢128ms,右偏快45s】
            previous = now;

            String afterPrefix = "Pool ";
            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) {
               //PoolEntry  有个属性lastAccessed,最后访问?什么东西,最后访问的一个时间戳。最后访问时间比当前时间要大于空闲时间
                  if (toRemove > 0 && elapsedMillis(entry.lastAccessed, now) > idleTimeout && connectionBag.reserve(entry)) {
                     closeConnection(entry, "(connection has passed idleTimeout)");
                     toRemove--;
                  }
               }
            }
//这里面有个思考?
//被标记了不再使用的才会被移除。
//如果正在使用,可是超时了,超过了最大的查询时间,也就是说遇到了一个慢查询,很慢很慢导致超过了5分钟,比如啊。这个5分钟服务器没有响应过来,会被haproxy识别为空闲无效访问直接断掉么?

// connectionBag.reserve(entry) 这段还没看明白,有时间再研究了。



            logPoolState(afterPrefix);

            fillPool(); // Try to maintain minimum connections
         }
         catch (Exception e) {
            logger.error("Unexpected exception in housekeeping task", e);
         }
      }
   }


原网址: 访问
创建于: 2023-07-31 10:29:19
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论