五、HikariCP源码分析之初始化分析二

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

源代码版本2.4.5-SNAPSHOT

HikariPool的初始化

在上一节,我们说到了pool = fastPathPool = new HikariPool(this);中的new HikariPool(this)。我们来看下代码:

public HikariPool(final HikariConfig config) {
  //①
  //PoolBase
  super(config);
  //②
  // 构建一个connectionBag用于保存连接, connectionBag是连接池的核心
  this.connectionBag = new ConcurrentBag<>(this);
  //初始化连接计数器, 用于统计连接池中的连接数量
  this.totalConnections = new AtomicInteger();
  //根据是否允许挂起连接池, 初始化锁
  this.suspendResumeLock = config.isAllowPoolSuspension() ? new SuspendResumeLock() : SuspendResumeLock.FAUX_LOCK;
  //③
  //连接池统计
  if (config.getMetricsTrackerFactory() != null) {
     setMetricsTrackerFactory(config.getMetricsTrackerFactory());
  } else {
     setMetricRegistry(config.getMetricRegistry());
  }

  setHealthCheckRegistry(config.getHealthCheckRegistry());
  //注册 JMX 相关的 bean
  registerMBeans(this);
  //④
  checkFailFast();
  //⑤
  ThreadFactory threadFactory = config.getThreadFactory();
  this.addConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection adder", threadFactory, new ThreadPoolExecutor.DiscardPolicy());
  this.closeConnectionExecutor = createThreadPoolExecutor(config.getMaximumPoolSize(), poolName + " connection closer", threadFactory, new ThreadPoolExecutor.CallerRunsPolicy());

  if (config.getScheduledExecutorService() == null) {
     threadFactory = threadFactory != null ? threadFactory : new DefaultThreadFactory(poolName + " housekeeper", true);
     this.houseKeepingExecutorService = new ScheduledThreadPoolExecutor(1, threadFactory, new ThreadPoolExecutor.DiscardPolicy());
     this.houseKeepingExecutorService.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
     this.houseKeepingExecutorService.setRemoveOnCancelPolicy(true);
  } else {
     this.houseKeepingExecutorService = config.getScheduledExecutorService();
  }
  //⑥
  //默认 30s 运行一次
  this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);
  //⑦
  this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);
}

可以看到代码非常的长,也比较复杂,不要紧,我们慢慢分析。

①初始化父类

super(config);中的 super代表的是com.zaxxer.hikari.pool.PoolBasePoolBase是一个更接近底层的一个连接池抽象。它里面定义了一些数据库连接相关的配置,比如:是否自动提交事务,是否连接只读,是否使用 JDBC4,网络请求超时时间等。一些比较重要的方法:初始化 JDBC 的dataSource,验证连接是否存活,重置连接默认配置等等。调用super(config);的目的,就是初始化PoolBase中的这些数据库配置。

通过这个super我们可以发现,HikariCP的初始化是逐层传递的,假如某个子类继承了父类,父类又继承了它的父类,那么初始化的时候,是用同一个配置类,先传递到子类,再到父类,再到祖父类,每一层都使用HikariConfig来初始化跟自己相关的配置,我们可以学习这种初始化方式,非常优雅。

具体的PoolBase初始化过程,我们不深入了,不是很复杂,大家可以结合我的代码注释来看一下,注释的非常明白。

②初始化ConcurrentBag

ConcurrentBag是一个通用的池模型的容器,是整个 HikariCP 的核心,我们要单独章节分析,此处大家只是明白这里初始化了用于保存数据库连接的容器,它的内部是一个CopyOnWriteArrayList,用于保存连接。

totalConnections呢,从字面就可以理解,是一个连接的计数器,用于记录连接池中的连接数量。它的类型是AtomicInteger,关于Atomic开头的原子类,我们在《HikariCP源码分析之获取连接流程一》中详细分析过AtomicBoolean的原理,这个是差不多的,大家可以看前面的文章。totalConnections这个计数器,会在向连接池中添加新连接的时候加1,连接池中的连接被关闭之后会减 1。

suspendResumeLock是我们在《HikariCP源码分析之获取连接流程二》中分析的重点,此处不赘述了。这里是创建一个连接池挂起的锁,或者说令牌桶,用于连接池挂起的时候,控制用户不能从连接池获取连接的。如果用户没有开启连接池挂起功能,就创建一个空的锁实现FAUX_LOCK,方便 JIT 将它优化掉。

③监控初始化

我们在之前的获取连接的分析文章中提到过,获取连接的时候,会向监控平台上报自己的状态,这里就是初始化监控平台的相关配置。用户可以自定义监控平台的实现,将它注册到 HikariCP 中,就可以被 HikariCP 调用。

值得一提的是registerMBeans(this);这一句代码。这里是注册 JMX 相关的 MBean,只有配置了数据库的isRegisterMbeans配置项,HikariCP 才会注册MBean,我们才能使用 JMX 在运行期间修改连接池的配置。如果不配置isRegisterMbeans,那么使用 JMX 修改配置会报错。对 JMX 感兴趣的同学,可以自行学习下相关内容。

④快速失败

这里只有一行代码checkFailFast();,但是我们单独拿出来了,这说明这里有点意思。

直接看看代码:

private void checkFailFast() {
  if (config.isInitializationFailFast()) {
     try {
        newConnection().close();
     } catch (Throwable e) {
        try {
           shutdown();
        } catch (Throwable ex) {
           e.addSuppressed(ex);
        }
        throw new PoolInitializationException(e);
     }
  }
}

代码看着不少,其实关键的没有多少。isInitializationFailFast是一个 HikariCP的配置项,它的默认值是 true。老规矩,先从字面意思猜测一下,好像是:初始化的时候快速失败的意思。再看一下下面的代码newConnection().close();,这是创建了一个连接,然后立即关闭了呀!综合以上线索,这是什么意思?其实非常好理解。就是在初始化 HikariCP 的时候,建立一个连接,然后立即关闭,如果有报错建立不了,就关闭整个连接池,抛错。

目的就是在启动期间,创建连接来验证关键参数是否有错误,如果不能建立连接,立即抛出错误,方便用户及时发现问题。比如:我们的数据库密码写错了。如果没有这个立即失败的验证,等你上线部署成功之后,第一次获取连接才能发现问题,这不就悲催了嘛,搞不好要挨骂的。

⑤初始化线程池

HikariCP 中有几个线程池:

  • closeConnectionExecutor :用于执行关闭底层连接的线程池,只有一个线程,线程任务队列最大是连接池最大连接数,超出队列的任务,会不断重试添加。

  • addConnectionExecutor:用于执行添加新连接的线程池,只有一个线程,线程任务队列最大是连接池最大连接数,超出队列的任务,直接抛弃。

  • houseKeepingExecutorService:这是一个定时线程池,默认只有一个线程,它的作用比较多:用于执行检测连接泄露、关闭生存时间到期的连接、回收空闲连接、检测时间回拨。

closeConnectionExecutor的队列任务抛弃策略有点不一样,它会不断重试,是基于连接必须关闭的考虑,其他的任务直接抛弃是影响不大。

这里有两项配置可以影响线程池,一个是scheduledExecutor:用于提供给houseKeepingExecutorService用的线程池,如果用户不自定义,就使用默认的 1 个线程的线程池。另一个是threadFactory:用于生成线程池中的线程,HikariCP 会在生成线程池的时候,调用该线程工厂获取线程。

⑥启动连接管理任务

看代码:

this.houseKeepingExecutorService.scheduleWithFixedDelay(new HouseKeeper(), 0L, HOUSEKEEPING_PERIOD_MS, MILLISECONDS);

这里向houseKeepingExecutorService线程池里提交了一个任务:每隔 30 秒,就执行一次HouseKeeper任务。这个任务的功能主要是:检测时间回拨,调整连接池里的连接。什么是时间回拨?比如服务器的系统时间不准,后来用户修改了服务器的系统时间,因为 HikariCP 是对时间敏感的框架,它靠定时任务来管理连接,如果系统时间变了,那么定时任务就不准确了。

有两种情况:

  • 一是用户调快了时间,这个时候,HikariCP 什么都不做,因为时间快了,只是加快了定时任务的执行,使连接更早过期,这个对连接池影响不大,因为连接池会自动添加新连接。

  • 二是用户调慢了时间,也就是回退了时间。回退时间对 HikariCP 是有极大影响的,比如原来还差 1 秒执行的任务,现在可能要过 15秒之后才能执行了,这可能引发本来该存活时间到期的连接,不会过期了。所以,这个时候,HikariCP 会把连接池中所有的连接都软驱逐掉,使所有的连接都不可用,然后重新创建新连接。

由于HouseKeeper任务比较复杂,我们单独的章节分析。

⑦创建连接泄露检测任务的父任务

看代码:

this.leakTask = new ProxyLeakTask(config.getLeakDetectionThreshold(), houseKeepingExecutorService);

我们在《HikariCP源码分析之获取连接流程三》中分析连接泄露检测时候,提到过,用户获取到每个连接的时候,都会为该连接创建一个连接泄露检测的定时任务,在指定的时间内,抛出连接泄露警告。

在创建连接泄露检测任务的时候,会使用一个父任务的参数,从这个父任务中拿连接泄露的最大时间和用于执行任务的线程池,然后使用这两个参数创建任务。这个父任务,就是在这里创建的,创建的时候就是传了这两个参数:连接泄露的最大时间和用于执行任务的线程池。

至此,HikariDataSource初始化就分析完成了。大家有任何问题,可以提出来,我们一起讨论学习。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342