专业的编程技术博客社区

网站首页 > 博客文章 正文

P3-1 数据库连接池HikariCP的(二)

baijin 2024-10-17 07:52:10 博客文章 3 ℃ 0 评论

内容简介:HikariCP源码解析

相关链接:P3-1 数据库连接池HikariCP(一)

背景:springboot2.4+HikariCP 3.4.5

二、源码分析

咱们书接上回,继续探寻HikariCP的奥秘,首先看下HikariCP的获取连接时序图

上回我们分析了HikariDataSource的getConnection()方法,而这个方法的实现细节都是在HikariPool的getConnection()方法中,接下来就要看看HikariPool的getConnection()方法

1-HikariPool的getConnection方法

我们一点点看

(1)申请令牌

suspendResumeLock.acquire();其作用是当连接池挂起的时候, 控制用户不能获取连接。

比如我们通过JMX修改在运行中的HikariPool的参数时,就需要挂起连接池,将原来所有的连接都从连接池中驱逐出去,再恢复连接池,这时候的连接池才会使用新的配置创建新的连接。

suspendResumeLock类是用Semaphore实现的。Semaphore是java.util.concurrent.包下的 并发工具类,它用给线程发放令牌的方式,控制线程的并发数量。HikariCP 在这里初始化了 1万个令牌,挂起连接池,其实就是调用了Semaphore一次获取 1 万个令牌,这样其他线程就没有令牌可以拿了,只能等待,直到用户恢复线程池,释放这 1 万个令牌到桶里。需要注意的是,要使用挂起连接池的功能,必须配置isAllowPoolSuspension=true,否则使用挂起功能会报错。

(2)获取连接

调用ConcurrentBag的borrow方法借用一个PoolEntry对象,对于连接池而言实际就是从连接池中获取一个连接,connectionBag是 HikariCP中实际保存数据库连接的容器,里面是一个CopyOnWriteArrayList

核心步骤只有两步,

borrow() 的主要逻辑是:

查看线程本地存储 threadList 中是否有空闲连接,如果有,则返回一个空闲的连接;

如果线程本地存储中无空闲连接,则从共享队列 sharedList 中获取;

如果共享队列中也没有空闲的连接,则请求线程需要等待。

(3)获取连接后判断条件


如果connectionBag给我们返回了一个连接,那么需要判断两个条件:

1-该连接是否被软驱逐(标记状态)了,poolEntry.isMarkedEvicted()

2-计算连接到现在多长时间没有被使用过了(界限默认是500ms)且该连接是否已经不可用了!isConnectionAlive(poolEntry.connection)

这么做的原因是HikariCP的连接不是实时从连接池里剔除的,只是给连接上打个标记而已,都是在获取连接的时候检查是否可用,如果不可用的时候才直接从连接池里删除。

这里涉及到一个知识点,连接跟数据库通信有两种方式:

JDBC4 以下版本的驱动,使用用户配置的connectionTestQuery中的 sql 来检查。 connectionTestQuery是获取连接的时候,用于检查连接是否可用的一个 sql,大家可能用过,常见的是配置一个select 1。

JDBC4 以上,如果不配置connectionTestQuery, 默认使用 ping 命令检查。

如果使用的是 JDBC4 以上的驱动,建议大家不用配置connectionTestQuery,因为 ping 命令的方式比执行一个 sql 要高效很多。

Case1:若连接不用,则关闭连接

首先会把这个连接对象从ConnectionBag里移除,然后把实际的物理连接交给一个线程池去异步执行,这个线程池就是closeConnectionExecutor,然后异步任务内开始实际的关连接操作,因为主动关闭了一个连接相当于少了一个连接,所以还会触发一次扩充连接池操作。

Case2:若连接可用,

1-上报监控平台,metricsTracker.recordBorrowStats,记录连接的借用

2-创建代理连接,poolEntry.createProxyConnection,动态生成代理connection对象。

这里有个疑问就是从连接池获取的连接都是java.sql.Connection类型的对象,但是这里为什么要建立动态代理呢?原因是如果直接返回底层的数据库连接给用户使用,那么,如果用户自己关闭了这个底层数据库连接,那么这个连接在连接池里就不可用了,其他线程也使用不了了,这样就失去了连接池的意义了。所以就设计了一个createProxyConnection方法来创建了一个连接的代理ProxyConnection,将这个代理返回给用户使用。ProxyConnection继承了java.sql.Connection,覆盖了一些方法,比如关闭连接,如果用户调用了关闭连接操作,不是真正的关闭底层连接,而是将连接还回到连接池。

2-泄露检测的定时任务

在createProxyConnection方法中,参数leakTask.schedule(poolEntry),leakTask的类型是ProxyLeakTaskFactory,其用于泄露检测

(4)连接超时

此处的connectionTimeout就是我们在yaml文件中配置的参数,他是在初始化连接池的时候就获取到值了,默认是30s

我们看到(2)和(3)都是在不超时的dowhile循环中执行,一旦超时,会有两个步骤:一是向监控平台上报获取连接超时;二是构造一个异常信息,然后抛出去。

(5)释放锁

我们在最初申请了令牌,最后就需要释放锁,为了防止任何异常打断代码执行,所以释放锁的代码一定要放在 finally 中,保证最后一定会把锁释放掉。

至此,整个获取连接的逻辑就介绍完了。

2-HikariPool的初始化

其中,主要包含两部分,

一是调用HikariPool对象的父类对象PoolBase的构造器(读取HikariConfig配置信息配置PoolBase的属性和调用PoolBase的构造器的initializeDataSource方法)

二是配置一堆线程池信息

我们一点点看

1-初始化父类PoolBase

super(config)作用是初始化父类PoolBase中的数据库配置,通过HikariConfig逐层初始化相关的配置。PoolBase是一个更接近底层的一个连接池抽象类。它里面定义了一些数据库连接相关的配置,比如:是否自动提交事务,是否使用 JDBC4等。以及初始化 JDBC 的dataSource,验证连接是否存活,重置连接默认配置等等。

2-初始化ConcurrentBag

ConcurrentBag就是并发包,本质就是连接池的主体,它的内部是一个CopyOnWriteArrayList,用于保存连接。存储连接的封装对象PoolEntry,另外做了并发控制来解决连接池的并发问题。

3-初始化suspendResumeLock

就是创建连接池挂起的锁,这个地方在上一篇文章中有所介绍,此处不再赘述

4-初始化houseKeepingExecutorService

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

5-快速检测失败

在初始化 HikariCP 的时候,建立一个连接,然后立即关闭,如果有报错建立不了,就关闭整个连接池,抛错

6-监控设置

上篇文章我们已经知道获取连接的时候,会向监控平台上报自己的状态,这里就是初始化监控平台的相关配置

8-注册MBean

这里是注册 JMX 相关的 MBean,只有配置了数据库的isRegisterMbeans配置项,HikariCP 才会注册MBean,我们才能使用 JMX 在运行期间修改连接池的配置。如果不配置isRegisterMbeans,那么使用 JMX 修改配置会报错。

9-初始化线程池

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

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

10-启动连接管理任务

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

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

用户获取到每个连接的时候,都会为该连接创建一个连接泄露检测的定时任务,在指定的时间内,抛出连接泄露警告。

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

以上就是对HikariPool初始化的介绍。

3-HikariCP的指标监控

HikariCP提供了一些监控指标,他的监控指标都是基于MicroMeter提供出来的,然后支持Prometheus和Dropwizard。本次我们将讨论一下HikariCp的监控指标有哪些,对于参数的监控我们以后会专门写篇文章进行介绍,此处就不先介绍了。

至此我们对于HikariCP的原理有了初步的了解,还有很多地方是值得慢慢思考设计者的设计思想的,静下心来慢慢看源码,你就会慢慢发现设计者思想的伟大之处,还是要慢慢学习。

参考文档:

https://github.com/brettwooldridge/HikariCP (官方文档)

https://www.jianshu.com/nb/46657191

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表