网站首页 > 博客文章 正文
内容简介: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
猜你喜欢
- 2024-10-17 SpringBoot+Vue3+MySQL集群 开发健康体检双系统(完结)
- 2024-10-17 再有人问你数据库连接池的原理,这篇文章甩给他!
- 2024-10-17 详解Spring Boot并发处理能力:理论与参数设置实践
- 2024-10-17 数据库连接池有什么用?springboot中如何使用?
- 2024-10-17 谈谈高并发系统的一些解决方案(高并发系统设计的三大目标)
- 2024-10-17 微服务事务管理艺术:Spring Boot 集成 Seata 深度指南
- 2024-10-17 阿里巴巴开源数据库jdbc连接池 Druid 1.1.18 发布
- 2024-10-17 【架构之路】提升后端接口性能的实战技巧
- 2024-10-17 「解密」有人要将“高并发”拉下“神坛”!
- 2024-10-17 MySQL连接优化是数据库性能调优的重要一环
你 发表评论:
欢迎- 373℃手把手教程「JavaWeb」优雅的SpringMvc+Mybatis整合之路
- 369℃用AI Agent治理微服务的复杂性问题|QCon
- 360℃初次使用IntelliJ IDEA新建Maven项目
- 352℃Maven技术方案最全手册(mavena)
- 349℃安利Touch Bar 专属应用,让闲置的Touch Bar活跃起来!
- 348℃InfoQ 2024 年趋势报告:架构篇(infoq+2024+年趋势报告:架构篇分析)
- 346℃IntelliJ IDEA 2018版本和2022版本创建 Maven 项目对比
- 344℃从头搭建 IntelliJ IDEA 环境(intellij idea建包)
- 最近发表
- 标签列表
-
- powershellfor (55)
- messagesource (56)
- aspose.pdf破解版 (56)
- promise.race (63)
- 2019cad序列号和密钥激活码 (62)
- window.performance (66)
- qt删除文件夹 (72)
- mysqlcaching_sha2_password (64)
- ubuntu升级gcc (58)
- nacos启动失败 (64)
- ssh-add (70)
- jwt漏洞 (58)
- macos14下载 (58)
- yarnnode (62)
- abstractqueuedsynchronizer (64)
- source~/.bashrc没有那个文件或目录 (65)
- springboot整合activiti工作流 (70)
- jmeter插件下载 (61)
- 抓包分析 (60)
- idea创建mavenweb项目 (65)
- vue回到顶部 (57)
- qcombobox样式表 (68)
- vue数组concat (56)
- tomcatundertow (58)
- pastemac (61)
本文暂时没有评论,来添加一个吧(●'◡'●)