当前位置:网站首页 > 技术博客 > 正文

hikaricp原理



HikariCP 本质上就是一个数据库连接池。

创建和关闭数据库连接的开销很大,HikariCP 通过“池”来复用连接,减小开销。

  1. HikariCP 是目前最快的连接池。就连风靡一时的 boneCP 也停止维护,主动让位给它。SpringBoot 也把它设置为默认连接池。
  1. HikariCP 非常轻量。本文用到的 4.0.3 版本的 jar 包仅仅只有 156 KB,它的源码真的非常精炼。

本文将包含以下内容(因为篇幅较长,可根据需要选择阅读):

  1. 如何使用 HikariCP(入门、JMX 等)
  2. 配置参数详解
  3. 源码分析

使用 HikariCP 获取连接对象,对用户数据进行简单的增删改查。

:1.8.0_231

:3.6.3

:Spring Tool Suite 4.6.1.RELEASE

:8.0.15

:5.7.28

:4.0.3

项目类型 Maven Project,打包方式 jar。

 

本文使用配置文件的方式来配置 HikariCP,当然,我们也可以在代码中显式配置,但不提倡。因为是入门例子,这里我只给出了必需的参数,其他的参数后面会详细介绍。

 

初始化连接池时,我们可以在代码中显式指定配置文件,也可以通过启动参数配置。

 

初始化连接池之后,我们可以通过方法获取连接对象,然后进行增删改查操作,这部分内容这里就不展示了。

开启 JMX 功能,并使用 jconsole 管理连接池。

在入门例子的基础上增加配置。这要设置 registerMbeans 为 true,JMX 功能就会开启。

 

为了查看具体效果,这里让主线程进入睡眠 20 分钟。

 

运行 main 方法,使用 JDK 的工具 jconsole 连接我们的项目,在 MBean 选项卡可以看到我们的连接池。接下里,我们可以进行这样的操作:

  1. 通过 PoolConfig 动态修改配置(只有部分参数允许修改);
  2. 通过 Pool 获取连接池的连接数(活跃、空闲和所有)、获取等待连接的线程数、挂起和恢复连接池、丢弃未使用连接等。

hikaricp_jmx

想了解更多 JMX 功能可以参考我的博客文章: 如何使用JMX来管理程序?

相比其他连接池,HikariCP 的配置参数非常简单,其中有几个功能需要注意:

  1. HikariCP 借出连接时强制检查连接的活性,不像其他连接池一样可以选择不检查;
  2. 默认会检查 idleTimeout、maxLifetime,可以选择禁用,但不推荐;
  3. 默认不检查 keepaliveTime、leakDetectionThreshold,可以选择开启,推荐开启 leakDetectionThreshold 即可。

注意,这里 jdbcUrl 和 dataSourceClassName 二选一。

 
 
 

HikariCP 的源码少且精,可读性非常高。如果你没见过像诗一样的代码,可以来看看 HikariCP。

提醒一下,在阅读 HiakriCP 源码之前,需要掌握、、、、、等 JDK 自带类的使用。

注意,考虑到篇幅和可读性,以下代码经过删减。

数据库连接池已经发展了很久了,也算是比较成熟的技术,使用比较广泛的类库有 boneCP、DBCP、C3P0、Druid 等等。眼看着数据库连接池已经发展到了瓶颈,所谓的性能提升也仅仅是一些代码细节的优化,这个时候,HikariCP 出现并快速地火了起来,与其他连接池相比,它的快不是普通的快,而是跨越性的快。下面是 JMH 测试的结果([测试项目地址](brettwooldridge/HikariCP-benchmark: JHM benchmarks for JDBC Connection Pools (github.com)))。

DataSource_performance_test

HikariCP 为什么快?我看网上有很多的解释,例如,大量使用 JDK 并发包的工具来避免粗颗粒度的锁、FastList 等自定义类的使用、动态代理类等等。我觉得,这些都不是主要原因。

HikariCP 之所以快,更多的还是由于抽象层面的优化

连接池,顾名思义,就是一个存放连接对象的池塘。几乎所有的连接池都会从代码层面抽象出一个池塘。池里的连接数量不是一成不变的,例如,连接失效了需要移除、新连接创建、用户借出或归还连接,等等,总结起来,对连接池的操作不外乎四个:borrow、return、add、remove

连接池一般是这样设计的:borrow、remove 动作会将连接从池塘里拿出来,add、return 动作则会往池塘里添加连接。我把这种模型称为“传统模型”。

hikaricp_pool01

“传统模型”是比较中规中矩的模型,从抽象层面讲,它非常符合我们的现实生活,例如,某人借走我的钱,钱就不在我的钱包里了。我们熟知的 DBCP、C3P0、Druid 等等都是基于“传统模型”开发的。

但是 HikariCP 就不一样了,它没有走老路,而是优化了“传统模型”,让连接池真正意义地实现了提速。

在“传统模型”中,borrow、return、add、remove 四个动作都需要加同一把锁,即同一时刻只允许一个线程操作池,并发高时线程切换将非常频繁。因为多个线程操作同一个池塘,连接出入池需要加锁来保证线程安全。针对这一点,我们是不是能做些什么呢?

HikariCP 是这样做的,borrow 的连接不会从池塘里取出,而是打上“已借出”的标记,return 的时候,再把这个连接的“已借出”标记去掉。我把这种做法称为“标记模型”。“标记模型”可以实现 borrow 和 return 动作不加锁。具体怎么做到的呢?

hikaricp_pool02

首先,我要 borrow 时,我需要看看池塘里哪一个连接可以借出。这里就涉及到读连接池的操作,因为池塘里的连接数量不是一成不变的,为了一致性,我们就必须加锁。但是,HikariCP 没有加,为什么呢?因为 HikariCP 容忍了读的不一致。borrow 的时候,我们实际上读的不是真正的池塘,而是当前池塘的一份快照。我们看看 HikariCP 存放连接的地方,是一个对象,我们知道,是一个写安全、读不安全的集合。

 

接着,当我们找到了一个可借出的连接时,需要给它打上借出的标记。注意,这时有可能出现多个线程都想给它打标记的情况,该怎么办呢?难道要加锁了吗?别忘了我们可以用 CAS 机制来更新连接的标记,这个时候就不需要加锁了。看看 HikariCP 就是这么实现的。

 

于是,在“标记模型”里,只有 add 和 remove 才需要加锁,borrow 和 return 不需要加锁。通过这种颠覆式的设计,连接池的性能得到极大的提高。

这就是我认为的 HikariCP 快的最主要原因。

那么,我们来看看 HikariCP 的源码吧。

HikariCP 的类不多,最主要的就这几个,如图。不难发现,这种类结构和 DBCP2 很像。

这几个类可以分成四个部分:

  1. 用户接口。用户一般会使用来获取连接对象。
  2. JMX 支持。
  3. 配置信息。使用加载配置文件,或手动配置的参数,它一般会作为入参来构造对象;
  4. 连接池。获取连接的过程为->->。需要注意的是,才是真正的连接池,而是用来管理连接池的

可以算是 HikariCP 最核心的一个类,它是 HikariCP 底层真正的连接池,上面说的“标记模型”只要就是靠它来实现的。如果大家不想看太多代码的话,只看它就足够了。

在设计上,是一个比较通用的资源池,它可以是数据库连接的池,也可以是其他对象的池,只要存放的资源对象实现了接口即可。所以,如果我们的项目中需要自己构建池的话,可以直接拿这个现成的组件来用。

 

下面简单介绍下的几个字段:

ConCurrentBagUml

属性 描述 CopyOnWriteArrayList sharedList 存放着状态为使用中、未使用和保留三种状态的资源对象 ThreadLocal threadList 存放着当前线程归还的资源对象 SynchronousQueue handoffQueue 这是一个无容量的阻塞队列,出队和入队都可以选择是否阻塞 AtomicInteger waiters 当前等待获取元素的线程数

这几个字段在中如何使用呢,这里拿方法来说明下:

 

在上面的方法中,唯一会造成线程阻塞的就是,除此之外,我们没有看到任何的 synchronized 和 lock。

除了,也是一个比较重要的类,它用来管理连接池

HikariPoolUML

HikariPool 的几个字段说明如下:

属性类型和属性名 说明 DataSource dataSource 用于获取原生连接对象的数据源。一般我们不指定的话,使用的是DriverDataSource ThreadPoolExecutor addConnectionExecutor 执行创建连接任务的线程池。 只开启一个线程执行任务。 ThreadPoolExecutor closeConnectionExecutor 执行关闭原生连接任务的线程池。 只开启一个线程执行任务。 ScheduledExecutorService houseKeepingExecutorService 用于执行检查 idleTimeout、leakDetectionThreshold、keepaliveTime、maxLifetime 等任务的线程池。

为了更清晰地理解上面几个字段的含义,我简单画了个图,不是很严谨,将就看下吧。在这个图中,客户端线程可以调用进行 borrow、requite 和 remove 操作,houseKeepingExecutorService 线程可以调用进行 remove 操作,只有 addConnectionExecutor 可以进行 add 操作。

HikariPoolSimpleProcess

掌握了上面的两个类,HikariCP 的整个源码视图应该就比较完整了。下面再说一些有趣的地方。

在下面的代码中,里竟然有两个。

 

为什么要这样做呢?

首先,从性能方面考虑,使用 fastPathPool 来创建连接会比 pool 更好一些,因为 pool 被 volatile 修饰了,为了保证可见性不能使用缓存。那为什么还要用到 pool 呢?

我们打开,可以看到,pool 的存在可以用来支持双重检查锁。这里我比较好奇的是,为什么不把 的引用给 fastPathPool??这个问题大家感兴趣可以研究一下。

 

其实,这两个对象有两种取值情况:

取值一:。当通过有参构造来创建就会出现这样取值;

取值二:。当通过无参构造来创建就会出现这样取值。

所以,我更推荐使用的方式,因为这样做的话,我们将使用 fastPathPool 来获取连接。

HikariCP 加载配置的代码非常简洁。我们直接从方法开始看,如下。

 

相比其他类库(尤其是 druid),HikariCP 加载配置的过程非常简洁,不需要按照参数名一个个地加载,这样后期会更好维护。当然,这种方式我们也可以运用到实际项目中。

另外,配置 HikariCP 的时候不允许写错参数或者添加一些无关的参数,否则会因为找不到对应的 setter 方法而报错。

以上基本讲完 HikariCP 的源码。后续发现其他有趣的地方再做补充,也欢迎大家指正不足的地方。

最后,感谢阅读。

HikariCP github

2021-05-20 修改

相关源码请移步:https://github.com/ZhangZiSheng001/hikari-demo

本文为原创文章,转载请附上原文出处链接: https://www.cnblogs.com/ZhangZiSheng001/p/12329937.html

  • 上一篇: js数组菜鸟教程
  • 下一篇: java商城软件下载
  • 版权声明


    相关文章:

  • js数组菜鸟教程2025-08-11 21:01:00
  • treeset类2025-08-11 21:01:00
  • rman-06054 异机恢复2025-08-11 21:01:00
  • fastjsonutil2025-08-11 21:01:00
  • 溢出内容隐藏2025-08-11 21:01:00
  • java商城软件下载2025-08-11 21:01:00
  • 网络攻防演练中常用的攻击手法2025-08-11 21:01:00
  • win10藏文输入法怎么添加2025-08-11 21:01:00
  • 算法设计与分析视频教程2025-08-11 21:01:00
  • 多线程编程实战指南2025-08-11 21:01:00