Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >十一、HikariCP源码分析之HouseKeeper

十一、HikariCP源码分析之HouseKeeper

原创
作者头像
用户1422411
发布于 2022-06-25 09:56:19
发布于 2022-06-25 09:56:19
1.6K0
举报

欢迎访问我的博客,同步更新: 枫山别院

源代码版本2.4.5-SNAPSHOT

HouseKeeper是一个HikariPool的内部类,它实现了Runnable接口,也就是一个线程任务。这个任务是由ScheduledThreadPoolExecutor类型的线程池执行的,也就是说它是一个定时任务。我们在《HikariCP源码分析之初始化分析二》中分析 HikariCP 初始化的时候,遇到了houseKeepingExecutorService的初始化,简单分析了它的初始化过程,但是这个任务是非常重要的,我们要仔细分析一下。

它的主要作用就是:检测时间回拨,并关闭空闲时间超期的连接。下面的代码依然是有详细的注释,同时也记录了我当时自己分析代码时遇到的一些疑问和解答。

我们看下代码:

代码语言:java
AI代码解释
复制
/**
 * HouseKeeper用于空闲连接过期
 */
private class HouseKeeper implements Runnable {
   private volatile long previous = clockSource.plusMillis(clockSource.currentTime(), -HOUSEKEEPING_PERIOD_MS);

   @Override
   public void run() {
      //①
      //刷新通过MBean修改的设置
      connectionTimeout = config.getConnectionTimeout();
      validationTimeout = config.getValidationTimeout();
      leakTask.updateLeakDetectionThreshold(config.getLeakDetectionThreshold());
      //②
      final long idleTimeout = config.getIdleTimeout();
      final long now = clockSource.currentTime();

      //检测时间回拨, 即网络对时服务对时钟的调整, 允许 128 毫秒的时间差
      if (clockSource.plusMillis(now, 128) < clockSource.plusMillis(previous, HOUSEKEEPING_PERIOD_MS)) {
         LOGGER.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
            clockSource.elapsedDisplayString(previous, now), poolName);
         previous = now;
         //连接池中所以的连接都标记删除
         softEvictConnections();
         //重新创建连
         fillPool();
         return;
      } else if (now > clockSource.plusMillis(previous, (3 * HOUSEKEEPING_PERIOD_MS) / 2)) {
         // No point evicting for forward clock motion, this merely accelerates connection retirement anyway
         //时钟快了, 没必要调整连接池, 反正是加速了连接的过期, 不影响
         LOGGER.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", clockSource.elapsedDisplayString(previous, now), poolName);
      }
      //原来的实现代码如下文件的633-650 行: https://github.com/brettwooldridge/HikariCP/blob/bc010fba486b27ae3d034cc9701e0c4217457ddb/src/main/java/com/zaxxer/hikari/pool/HikariPool.java
      //         logPoolState("Before cleanup ");
      //         for (PoolBagEntry bagEntry : connectionBag.values(STATE_NOT_IN_USE)) {
      //            if (connectionBag.reserve(bagEntry)) {
      //               if (bagEntry.evicted) {
      //                  closeConnection(bagEntry, "(connection evicted)");
      //               }
      //               else if (idleTimeout > 0L && clockSource.elapsedMillis(bagEntry.lastAccess, now) > idleTimeout) {
      //                  closeConnection(bagEntry, "(connection passed idleTimeout)");
      //               }
      //               else {
      //                  connectionBag.unreserve(bagEntry);
      //               }
      //            }
      //         }
      //
      //         logPoolState("After cleanup ");
      //
      //         fillPool(); // Try to maintain minimum connections
      //      }
      // 代码中, 先将所有超过空闲时间的连接都关闭, 然后将连接池中的连接再填充到minIdle最小空闲连接数
      // 后来有个名为 yaojuncn 的人跟brett提了个 issue, 如下: https://github.com/brettwooldridge/HikariCP/issues/379
      // issue的内容就是yaojuncn发现, 清理空闲连接的时候, 连接数会小于minIdle, 极端情况下会是 0, 他认为这样有问题, 服务请求多的时候, 会大量的创建连接, 给数据库造成压力
      // 但是brett认为, 创建连接非常快, 极端情况的几率极小, 这不是个问题, 提议yaojuncn使用固定大小的连接池
      // 讨论来讨论去, brett终于烦了, 接受了yaojuncn的建议, 并且合并了yaojuncn的 merge request.
      // yaojuncn提的实现就是目前的请清理方式, 这个: https://github.com/yaojuncn/HikariCP/commit/cbb1e1cc93d050457ffe9939b67eacd6c6bd97a0

      //③
      //开始清理超过idleTimeout的空闲连接
      previous = now;

      String afterPrefix = "Pool ";
      if (idleTimeout > 0L) {
         //查出连接池中所有的空闲连接
         final List<PoolEntry> idleList = connectionBag.values(STATE_NOT_IN_USE);
         //空闲连接数量 - 用户配置的最小连接数 = 目前可以回收的连接数, 不明白详见Question①
         int removable = idleList.size() - config.getMinimumIdle();
         //如果有可以回收的连接
         if (removable > 0) {
            logPoolState("Before cleanup ");
            afterPrefix = "After cleanup  ";

            // 按照最近访问的实际, 从小到大排序, 排序指标是最后访问时间的时间戳, 时间大的是最近使用的, 从小到大遍历比较合理, 能先清理掉长时间没用的, 不用遍历所有的空闲连接
            //如果要清理的连接数够了,那么就不用继续遍历了,可以减少循环次数          
            Collections.sort(idleList, LAST_ACCESS_COMPARABLE);
            for (PoolEntry poolEntry : idleList) {
               //判断最后访问时间和当前时间的时间差, 是否超过了用户配置的最大空闲时间, 超过了就将连接变为保留状态
               if (clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && connectionBag.reserve(poolEntry)) {
                  //关闭连接
                  closeConnection(poolEntry, "(connection has passed idleTimeout)");
                  //可回收连接数减 1, 如果可回收连接数等于 0, 就是清理完了
                  if (--removable == 0) {
                     break; // keep min idle cons
                  }
               }
            }
         }
      }
      //记录日志
      logPoolState(afterPrefix);
      //可能有些连接过期了, 重新填充连接池到用户配置的最小连接数
      fillPool(); // Try to maintain minimum connections
   }
}

①刷新配置

如果你看过《HikariCP源码分析之获取连接流程二》的话可能还记得,我们是可以通过 JMX 的方式来挂起整个连接池的,此时连接池是不可用的状态,然后我们就可以修改连接池的一些配置,然后将连接池恢复。修改了配置之后,并不是立即生效的,因为配置是在这里刷新的,而这里是一个定时任务,是每 30 秒触发一次。

为什么不能立即修改这些配置呢?

因为是在运行期修改的配置,你修改配置之后,连接池中之前的连接还是原来的配置呢,总得要处理一下这些连接吧?而这个处理过程正好是HouseKeeper的职责范围,因此就在这里刷新配置了,而且使用 JMX 修改配置这个需求,对时效性实在没有什么要求,30 秒完全可以接受,毕竟这不是一个常规的操作,一般是测试用途。

至于刷新的配置内容,略过,大家可以看下配置分析那一节的内容。

②时间回拨

这里比较有意思,通常我们的服务器都是有网络对时服务的,如果本地的系统时间不对的话就会自动调整。但是我们的 HikariCP 中的定时任务是依赖系统时间的啊,如果时间被调整了,那么定时任务就错乱了,后果非常严重,会导致该回收的连接回收不了。

开始正式任务之前,idleTimeoutnow依然是准备工作,idleTimeout是用户的配置项,连接的最大空闲时间,而now就是当前的系统时间。

我们看下这个if判断clockSource.plusMillis(now, 128) < clockSource.plusMillis(previous, HOUSEKEEPING_PERIOD_MS)clockSource.plusMillis(now, 128)的意思是当前时间加128毫秒,clockSource是一个时间工具类。previous是上一次任务的执行时间,HOUSEKEEPING_PERIOD_MS是任务的执行间隔,它们相加也就是本次任务应该执行的时间。如果当前时间+128 毫秒,小于,当前任务应该触发的时间,那么就是系统时间回退了128 毫秒以上对吧?这个是不行的。

有两种情况HikariCP 是可以容忍的:

  • 系统时间回退 128 毫秒以内
  • 系统时间前进了,具体多长时间不管

上面两种情况下,是不会进入 if 条件里的。但是我们还是要分析一下的,假如我们进入了 if 代码块,previous = now;这个的业务意思保存一下本次任务执行的时间,因为下次执行任务要用。softEvictConnections();一句简简单单的代码,就将连接池中所有的连接都驱逐出去了,连接首先会被标记删除,然后就真的关闭了这个连接,我们后面可以出一个 HikariCP 中连接关闭的单独文章分析下。fillPool();含义一眼就能看出来,是重新填充连接池,重新创建连接加入到连接池中。

如果是 else-if ,那么就是系统时间被调快了,这个只是加速了连接的生命结束,对 HikariCP 没有影响,连接被回收了是会自动创建新的连接,这个没有关系,因此不处理,只是打印一个警告。

③清理过期连接

我们直接看 if 里面,使用connectionBag.values(STATE_NOT_IN_USE)方法查询出来所有的空闲状态的连接,int removable = idleList.size() - config.getMinimumIdle();计算了当前空闲连接数超出用户配置数几个,也就是要清理的连接个数。

如果removable大于0,那么确实有需要清理的连接。

这里Collections.sort(idleList, LAST_ACCESS_COMPARABLE);先对所有的空闲连接按照最后使用时间从小到大进行排序,因为每个连接上都记录了最后使用时间,时间戳越小的,说明它最后使用时间越早,越大的越是最近使用过的。最近使用过的连接很可能被某个线程保存在本地的 ThreadLocal 中了,我们不清理这些连接,方便线程下次使用的时候直接获取。

然后循环遍历排序后的空闲连接,if 的条件是clockSource.elapsedMillis(poolEntry.lastAccessed, now) > idleTimeout && connectionBag.reserve(poolEntry),如果clockSource.elapsedMillis(poolEntry.lastAccessed, now),它是连接上次使用时间距离当前时间的时间差,大于用户配置的连接的最大空闲时间,说明这个连接空闲了太久,需要回收,此时在 if 条件中就执行connectionBag.reserve(poolEntry),修改连接的状态,修改成功之后,closeConnection方法是关闭这个底层连接,是真的被关闭了,然后可回收连接数减 1,继续循环,直到可回收连接数为 0 ,说明已经达到了用户的配置要求,结束。

但是有个问题,我们是回收了多余的空闲连接,假如在这期间,有其他连接生命周期时间到了,被关闭了,或者是连接发生了致命错误,被关闭了。那么,现在剩下的连接数又少于用户配置的最小空闲连接数了,怎么办呢?

这就是fillPool()的作用,它会将连接池中的连接数量重新填充到最小连接数。

好了,至此,HouseKeeper的功能我们分析完了

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【追光者系列】HikariCP源码分析之evict、时钟回拨、连接创建生命周期
evict定义在com.zaxxer.hikari.pool.PoolEntry中,evict的汉语意思是驱逐、逐出,用来标记连接池中的连接不可用。
用户1655470
2018/07/24
2.9K0
【追光者系列】HikariCP源码分析之evict、时钟回拨、连接创建生命周期
聊聊hikari连接池的idleTimeout及minimumIdle属性
本文主要研究一个hikari连接池的idleTimeout及minimumIdle属性
code4it
2018/09/17
4.8K0
【追光者系列】HikariCP 源码分析之从 validationTimeout 来讲讲 2.7.5 版本的那些故事
摘要: 原文可阅读 http://www.iocoder.cn/HikariCP/zhazhawangzi/validationTimeout 「渣渣王子」欢迎转载,保留摘要,谢谢!
芋道源码
2019/10/29
1.6K0
【追光者系列】HikariCP 源码分析之从 validationTimeout 来讲讲 2.7.5 版本的那些故事
【追光者系列】HikariCP源码分析之故障检测那些思考 fail fast &amp; allowPoolSuspension
由于时间原因,本文主要内容参考了 https://segmentfault.com/a/1190000013136251,并结合一些思考做了增注。
用户1655470
2018/07/24
1.4K0
聊聊hikari连接池的fixed pool design
HikariCP-2.7.6-sources.jar!/com/zaxxer/hikari/pool/HikariPool.java
code4it
2018/09/17
8750
聊聊hikari连接池的validationTimeout
HikariCP-2.7.6-sources.jar!/com/zaxxer/hikari/pool/HikariPool.java
code4it
2018/09/17
4.7K0
SpringBoot官方为什么采用这个数据库连接池?史上最快?
现在已经有很多公司在使用HikariCP了,HikariCP还成为了SpringBoot默认的连接池,伴随着SpringBoot和微服务,HikariCP 必将迎来广泛的普及。
macrozheng
2021/07/27
9610
SpringBoot官方为什么采用这个数据库连接池?史上最快?
【追光者系列】HikariCP 源码分析之 evict、时钟回拨、连接创建生命周期
摘要: 原创出处 https://mp.weixin.qq.com/s/PjJVYkMY67i7T-93tPpK7g 「渣渣王子」欢迎转载,保留摘要,谢谢!
芋道源码
2019/10/29
1.3K0
【追光者系列】HikariCP 源码分析之 evict、时钟回拨、连接创建生命周期
【追光者系列】HikariCP 源码分析之 allowPoolSuspension
摘要: 原创出处 https://mp.weixin.qq.com/s/-WGg22lUQU41c_8lx6kyQA 「渣渣王子」欢迎转载,保留摘要,
芋道源码
2018/07/31
1.2K0
HikariCP 源码分析之 leakDetectionThreshold 及实战解决 Spark/Scala 连接池泄漏
摘要: 原创出处 https://mp.weixin.qq.com/s/_ghOnuwbLHOkqGKgzWdLVw 「渣渣王子」欢迎转载,保留摘要,谢谢!
芋道源码
2019/10/29
3.2K0
HikariCP 源码分析之 leakDetectionThreshold 及实战解决 Spark/Scala 连接池泄漏
二、HikariCP获取连接流程源码分析二
在上一篇《HikariCP获取连接流程源码分析一》中,我们分析了HikariDataSource的getConnection()方法,而这个方法,其实详细的实现细节都是在HikariPool的getConnection()方法中,我们来分析下HikariPool的getConnection()方法。
用户1422411
2022/06/25
8320
三、HikariCP获取连接流程源码分析三
这里涉及到 HikariCP 的一个设计点,HikariCP的连接不是实时从连接池里剔除的,只是给连接上打个标记而已,都是在获取连接的时候检查是否可用,如果不可用的时候才直接从连接池里删除。如果在 HikariCP的任何地方都可能剔除连接,那么剔除连接的地方会比较多,会很乱,也容易引发 bug。反之,把剔除链接的操作收缩到某几个固定的逻辑中,就比较好管理。
用户1422411
2022/06/25
1.1K0
MySQL 连接挂死了!该如何排查?
近期由测试反馈的问题有点多,其中关于系统可靠性测试提出的问题令人感到头疼,一来这类问题有时候属于“偶发”现象,难以在环境上快速复现;二来则是可靠性问题的定位链条有时候变得很长,极端情况下可能要从 A 服务追踪到 Z 服务,或者是从应用代码追溯到硬件层面。
程序员小富
2022/12/10
3.4K0
MySQL 连接挂死了!该如何排查?
干掉Druid,HakariCP 为什么这么快?
Springboot 2.0将 HikariCP 作为默认数据库连接池这一事件之后,HikariCP 作为一个后起之秀出现在大众的视野中。HikariCP 是在日本的程序员开源的,hikari日语意思为“光”,HikariCP 也以速度快的特点受到越来越多人的青睐。
码猿技术专栏
2024/01/29
2770
干掉Druid,HakariCP 为什么这么快?
五、HikariCP源码分析之初始化分析二
在上一节,我们说到了pool = fastPathPool = new HikariPool(this);中的new HikariPool(this)。我们来看下代码:
用户1422411
2022/06/25
7000
【追光者系列】HikariCP连接池监控指标实战
该指标持续飙高,说明DB连接池中基本已无空闲连接。 拿之前业务方应用pisces不可用的例子来说(如下图所示),当时所有线程都在排队等待,该指标已达172,此时调用方已经产生了大量超时及熔断,虽然业务方没有马上找到拿不到连接的根本原因,但是这个告警出来之后及时进行了重启,避免产生更大的影响。
用户1655470
2018/07/24
2.1K0
【追光者系列】HikariCP连接池监控指标实战
八、HikariCP源码分析之ConcurrentBag一
大家好,今天我们一起分析下 HikariCP 的核心ConcurrentBag,它是管理连接池的最重要的核心类。从它的名字大家可以看得出来,它是一个并发管理类,性能非常好,这是它性能甩其他连接池十条街的秘密所在。
用户1422411
2022/06/25
8510
【追光者系列】HikariCP连接池监控指标实战
业务方关注哪些数据库指标? 首先分享一下自己之前的一段笔记(找不到引用出处了) 系统中多少个线程在进行与数据库有关的工作?其中,而多少个线程正在执行 SQL 语句?这可以让我们评估数据库是不是系统瓶颈。 多少个线程在等待获取数据库连接?获取数据库连接需要的平均时长是多少?数据库连接池是否已经不能满足业务模块需求?如果存在获取数据库连接较慢,如大于 100ms,则可能说明配置的数据库连接数不足,或存在连接泄漏问题。 哪些线程正在执行 SQL 语句?执行了的 SQL 语句是什么?数据库中是否存在系统瓶颈或已经
芋道源码
2018/06/13
6.5K0
基于HiKariCP组件,分析连接池原理
HiKariCP作为SpringBoot2框架的默认连接池,号称是跑的最快的连接池,数据库连接池与之前两篇提到的线程池和对象池,从设计的原理上都是基于池化思想,只是在实现方式上有各自的特点;首先还是看HiKariCP用法的基础案例:
知了一笑
2022/04/18
8630
基于HiKariCP组件,分析连接池原理
聊聊hikari连接池的maxLifetime属性及evict操作
用来标记连接池中的连接不可用,这样在borrow连接的时候,如果是标记evict的,则会继续获取连接
code4it
2018/09/17
6.2K0
推荐阅读
相关推荐
【追光者系列】HikariCP源码分析之evict、时钟回拨、连接创建生命周期
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档