开发者社区> 华章出版社> 正文

带你读《HikariCP数据库连接池实战》之三:初识HikariCP

简介: 本书不仅对市面上常见的连接池组件进行了全方位比较和分析,还以实战的角度深入介绍了高性能HikariCP连接池的使用、原理与维护。

点击查看第一章
点击查看第二章
第3章

初识HikariCP

第2章介绍了数据库连接池的相关核心概念,以及主流的数据库连接池。从本章开始,我们一起来聊聊HikariCP相关技术的概念、历史起源、适用场合、发展现状和趋势、大致原理、架构思路与操作环境搭建的那些事。
本章是笔者大量翻阅了HikariCP作者Brett Wooldridge先生的推特、GitHub、Stackoverflow,以及一些论坛留言、采访及演讲资料后做的梳理,内容丰富。同时包括几款数据库连接池之间的对比,涵盖了这几款数据库连接池的历史变迁、数据库连接池维护者之间的论战。当然也有笔者的一些实战感悟,希望读者可以读透本章。
这些概念之间的联系不是非常紧密,读者可以有选择性地阅读自己感兴趣的内容。

3.1 Hikari背景、特色及前景

在HikariCP的GitHub官网上写着图灵奖获奖者Dijkstra的一句话:“Simplicity is prerequisite for reliability”,大意是“简单是可靠性的先决条件”。HikariCP在致敬前辈的同时,也真正做到了简单、极致。Hikari官网首页的定义是:Fast, simple, reliable. HikariCP is a “zero-overhead” production ready JDBC connection pool. At roughly 130Kb, the library is very light.(快速,简单,可靠。HikariCP是一个“零开销”的JDBC连接池产品。它的库非常轻量级,大约只有130KB。)
Hikari日语发音是Hi-ka-li(lee),其作者在GitHub首页及受到的采访中都详细提到了其正确的发音:
I’ve lived and worked in Tokyo since 2008, though I think my Japanese is far behind where it should be given my time here. I chalk that up to preferring time at the keyboard to language study.
(自2008年以来,我一直在东京生活和工作,但我认为我的日语水平远远落后于我在这里待的时间。我认为比起语言学习,我更喜欢在键盘前学习。)
As you mentioned, Hikari (pronounced Hi-ka-lee) translates to “Light” (as in sunlight). In English, it is a double entendre in the context of HikariCP; though in Japanese it would not be. “Light” in the sense of “the speed of…”, and “light” in the sense of being light in terms of code weight.
(正如你(采访记者)提到的,Hikari翻译成“光”(在阳光下)。在英语中,HikariCP在语境中是双关语;但是在日语中并不是这样的。“光”的意思是“…的速度”,“光”的意思是代码量很小。)
Hikari虽然是日语,但是其作者却是一个从2008年一直生活在日本东京的地道的美国人。Hikari的意思是光,HikariCP的意思是Hikari Connection Pool,就是Hikari连接池。从以上作者的描述中可以看出,作者之所以取这个名字是按照英文的语境给这款连接池赋予了两个意义:速度快、代码量小。
小窍门 使用IDEA开发工具的Translation插件,可以听到单词的语音和某些技术词汇的原产地,比如Redis是意大利语,Lombok是印尼语,Hikari是日语。

HikariCP来源于BoneCP。在BoneCP官网上的README中只有简单的一段墓志铭,如图3-1所示。翻译成中文的意思就是:“BoneCP是一种Java JDBC连接池实现,通过最小化锁争来为应用程序提供更高的吞吐量,从而实现高性能。它击败了较旧的连接池,如c3p0和DBCP,但现在应该被视为弃用,以支持HikariCP。”
image.png
HikariCP到底是何方神圣,竟能让曾经叱咤风云的数据库连接池开源产品放弃更新后“白帝托孤”?我们看一下HikariCP作者在2017年2月21日的专栏中的访谈文章。这篇文章中提供了一些HikariCP不为人知的背景故事,其中就包括HikariCP的创建初衷。
小知识 国外有一个专栏叫作jOOQ Tuesdays series,该专栏在每个月的第3个星期二会发布一篇在IT行业中翘楚的访谈。被采访者包括使用SQL、Java、开源软件和各种其他相关主题的人员。

多年前,Brett Wooldridge为其工作的公司创建了一个产品原型,当时,Brett Wooldridge需要一个数据库连接池。和大多数开发者一样,他单纯地只想使用数据库连接池,然后继续工作,因此他在网上找到了最受欢迎的库。不幸的是,在对原型进行负载测试时,他所在的技术团队开始遇到死锁,并且异常表明线程之间的连接状态丢失了。
因为数据库连接池是开源的,所以他认为只需要下载源代码,找到并且修复问题,然后贡献给社区即可(其实这也是大多数人使用开源项目的方式,先用起来,遇到困难后研究并修复,然后把解决方案贡献给社区)。不过当Brett Wooldridge阅读这款数据库连接池源码的时候,他却发现这个小小的数据库连接池居然有上千行代码,远远超过他的预料。另外,代码中充斥了大量的锁和嵌套,一些资源在一个方法中被请求却在另一个遥远的地方被释放。这样混乱的代码,即使发现并修复了遇到的问题,也根本无法推断潜在的问题所在何处了。
Brett Wooldridge又换了一个开源的连接池并且检查了它的代码。这次锁语义虽然更清晰,但代码量仍然是他预料的2倍,特别不能容忍的是,这款开源代码将核心连接池逻辑委托给一个单独的库。
此外,Brett Wooldridge研究的所有池都以多种方式违反了JDBC规约。在通常情况下,连接池应该返回一个连接connection,该连接connection和连接池已经释放的连接connection应该是无法区分的。但是,当连接关闭、返回、清除警告或者回滚未提交的事务时,这些连接池并不会自动关闭语句Statements。并且它们不会重置用户更改的属性,如自动提交或事务隔离级别,以及更多的一些参数,从而导致下一个消费者将获得“脏”连接。
于是Brett Wooldridge就想:“真的吗?难道这就是Java 20年后生态系统中的连接池的状态?”出于挫败感和必要性,Brett Wooldridge创建了HikariCP。
很多连接池都是可以直接使用的,那么是什么理由使HikariCP脱颖而出呢?正确性和可靠性是很难出亮点的方向,所以Brett Wooldridge就专注于性能,并从一个简单的推文开始逐渐分享他的数据库连接池之旅。这样的影响就像滚雪球一样越来越大,一些用户在推特上发布了关于HikariCP性能提升和可靠性提升的信息,并且在2015年的某个时候,Wix工程团队写了一篇关于切换到连接池HikariCP的博客。
从本质上来说,性能极致的口碑使得HikariCP越来越受欢迎,这也是大多数用户推崇HikariCP的出发点。但是Brett Wooldridge还是非常希望随着时间的推移,更多的用户会同样重视正确性和可靠性,否则性能就毫无意义,而HikariCP确实也做了很多与正确性和可靠性相关的工作。
在追求极致方面,Brett Wooldridge尝试了一系列的优化,这些优化并不是低级的代码优化,而是包含算法优化以外的一些更为极致的事情。Brett Wooldridge以基准测试benchmarks作为参照工具,研究了JMH微基准框架,他发现JVM执行了一系列惊人的优化,如果不小心应对,那么在代码中看起来很聪明的优化只会混淆JIT基于模式的优化器,结果反而会变慢而不是更快。为了有效地优化JVM,除了JIT外,还必须了解Java内存模型(JMM),以及它是如何映射到x86等CPU架构中的。因此他阅读了JIT源代码,并且熟悉诸如死代码消除、循环不变提升、常量传播、虚拟调用内联等概念。共享状态的竞争是大多数瓶颈的根源(参见前面提到的JMM),所以近来HikariCP最大的性能提升(例如,在v2.6.0中)来自于简单地避免它的技巧,这些信息在本书后续章节都会介绍,希望对读者进行中间件的调优有所帮助。
开发一款开源产品,Brett Wooldridge自然会得到很多用户关于产品各种各样的功能性需求,虽然每个要求都可能很简单,但如果全部采纳会显著增加复杂性和代码大小,而他的初衷还是希望数据库连接池HikariCP足够简单。这点是大家需要注意的,就是Brett Wooldridge很可能会拒绝用户的一些扩展性需求,因为他追求的是一条大道至简的路。比如连接读写分离这样的逻辑路由需求就不在HikariCP的未来规划路线图中,Brett Wooldridge倾向于认为这些更多的是Spring层应该做的行为,而不属于数据库连接池技术的范畴。
当然,这并不是说Brett Wooldridge不会添加新功能,不去更新HikariCP,相反,HikariCP至少在近几年内的更新升级是比较频繁的。比如,HikariCP的配置,作者认为配置的选项和维度越多,用户就越难以优化配置池。但是,很多用户需要HikariCP提供动态调整连接池大小的功能,否则会影响他们的实际开发工作,因此他增加了这方面的支持。原则上,他不希望因缺乏动态的大小支持而剥夺用户对HikariCP的可靠性和正确性的认可。
我们翻看GitHub上的文章就能看到作者拒绝了很多功能请求。作为HikariCP的监护人,保持简单和真实的核心理念符合社区的最佳利益。在代码和配置方面,Brett Wooldridge总是尽量减少“表面积”(通常,对于HikariCP的用户而言,“表面积”表示配置参数的数量)。API的表面积越大,理解起来就越困难。
HikariCP的应用前景和未来发展也是非常可观的。SpringBoot 2.x官方已经宣布使用HikariCP作为SpringBoot默认的数据库连接池,随着SpringBoot和微服务的蓬勃发展,HikariCP在全球范围内的使用一定也会更加普及。

3.2 SpringBoot数据库连接池加载顺序剖析

HikariCP是 Spring Boot 2.x版本官方宣布默认的数据库连接池,SpringBoot对HikariCP的采纳也从侧面说明HikariCP在使用广泛性、认可度、性能、稳定性等方面是经得起检验的,也表明HikariCP的应用前景和未来发展是一片光明的。
如图3-2所示,在SpringBoot当前最新的2.x版本的文档中也强烈建议使用HikariCP作为默认的数据库连接池。文档中是这么描述的:我们很喜欢HikariCP的性能和并发性,如果HikariCP可用,我们便会选择它;否则,我们将使用Tomcat Pooling DataSource;如果HikariCP和Tomcat数据库连接池都不可用,我们将使用Commons DBCP2。当然,用户也可以通过设置spring.datasource.type属性来绕过上述使用顺序,进而指定想要使用的数据库连接池。
image.png
当然,如果微服务使用spring-boot-starter-jdbc或者spring-boot-starter-data-jpa的starter,会自动获得HikariCP的依赖。
其实Spring Boot 1.5.x是默认使用tomcat-jdbc作为连接池的,如下是其寻找连接池顺序的规则:Tomcat pool → HikariCP → Commons DBCP → Commons DBCP2。然而在其2.x版本中,HikariCP被提升为默认的数据库连接池,数据库连接池的默认配置顺序改为HikariCP → Tomcat pool → Commons DBCP2。接下来,我们就从源码角度给读者详细介绍这个规则的原理。
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration是Spring Boot 2.0.3.Release版本中的加载数据源的核心类。在该类中,PooledDataSource-Configuration是需要重点关注的,它决定了连接池的选择。我们可以看到图3-2中的注解包含了HikariCP、Tomcat、DBCP2等主流连接池,选择连接池的不同,application.properties配置也不同。
image.png
在上述PooledDataSourceCondition.class的代码中,PooledDataSourceAvailableCondition有一段findType的代码用来加载资源文件。
image.png
而这里的常量数组DATA_SOURCE_TYPE_NAMES就是关键,在Spring Boot 2.x中的定义如下:
image.png
在findType()方法中,for循环顺序遍历数组,加载到哪个类就使用哪个类的连接池,所以就采用HikariCP → Tomcat pool → Commons DBCP2这样的顺序。
同理,在1.5.3.Release版本的SpringBoot的数组中也是这么定义的,采用Tomcat Pool → HikariCP → Commons DBCP → Commons DBCP2的顺序。
image.png
所以,若要在SpringBoot 1.5.3.Release版本中使用HikariCP,除了在spring.datasource.type属性中对应设置外,还有一个简单的方法就是Maven依赖排除Tomcat-JDBC。
SpringBoot 2.0发布的时候,官方也发布了“db方面,默认引入了HikariCP,替代了之前的Tomcat-Pool作为底层的数据库连接池。对比Tomcat-Pool,HikariCP拥有更好的性能,总而言之就是提高了db的访问速度”这样的版本变更信息。读到这里读者应该能够清晰地理解SpringBoot在数据库连接池选型这件事上的来龙去脉了。

3.3 SpringBoot整合HikariCP实战

SpringBoot如何整合HikariCP进行使用呢?
本节我们会一起搭建一个SpringBoot 2.x的应用程序,该程序将指导大家如何一步步将HikariCP集成到SpringBoot中,并支持 SpringBoot JDBC对MySQL进行相关操作。
本节使用到的工具有:SpringBoot 2.0.3.Release、MySQL 5.7.x、HikariCP 3.2.0、Maven和Java 8。

3.3.1 Spring Initializr

Spring Initializr 是Spring 官方提供的一个很优秀的工具,用来初始化一个SpringBoot 的项目。一般来说有两种方式可以使用Spring Initializr来创建一个项目。
第1种方式:登录http://start.spring.io/网站,填写项目信息,选择依赖后,单击 Generate Project,然后下载一个该项目的压缩包。将这个压缩包解压,然后在IDEA中依次选择File → New → Project from existing sources。选择好文件夹后,单击OK,在Import Project中,选择Import project from external model选项,默认使用Maven工程,选择Maven,然后单击Next。下面看情况,一直单击Next,JDK选择JDK8,最后单击Finish。工程就建立好了。
第2种方式:其实 Spring Initializr 已经默认集成到IDEA中了,直接 File → New → Project,在左侧就能看到Spring Initializr,单击Next 就可以选择组件,如图3-3所示。
image.png
本案例我们采用第1种方式,登录start.spring.io的官方网站,填写组织信息和项目名称,如图3-4所示。
image.png
由于我们这个应用需要使用JDBC和MySQL,所以我们在依赖一栏填写jdbc,选择MySQL JDBC driver和JDBC databases即可,如图3-5所示。
接下来单击Generate Project就可以生成我们的项目——基于SpringBoot 2.0.3的Maven工程,如图3-6所示。
最终生成的项目结构如图3-7所示。
image.png
image.png

3.3.2 添加HikariCP依赖

在已经生成的pom.xml里,我们需要做两件事,一件是增加HikariCP的最新版本依赖(在写作本章时官方最新版本是3.2.0,2019年3月初我发现又更新到了3.3.1,主要修复了一系列的Bug)。
image.png
image.png
另外一件事情是,如果你的SpringBoot是1.5.x版本,可能需要排除掉spring-boot-starter-jdbc的Tomcat-JDBC依赖,如果排除的话可以使用Maven的exclusion进行操作。

![image.png](https://ucc.alicdn.com/pic/developer-ecology/a025b05ac6df4fd0a79458fc556bfbdd.png)

注意 SpringBoot 1.5.x版本默认使用Tomcat-JDBC作为连接池,如下是其找连接池顺序的规则:
Tomcat pool → HikariCP → Commons DBCP → Commons DBCP2。
然而在2.x版本中,HikariCP被提升为默认的数据库连接池,数据库连接池的默认配置顺序是HikariCP → Tomcat Pool → Commons DBCP2。

3.3.3 JdbcTemplate

JdbcTemplate是Spring针对JDBC提供的持久化技术,Spring同样为Hibernate 5.0、IBatis(MyBatis)、JPA等提供ORM持久化技术。JdbcTemplate主要提供execute方法、update方法、batchupdate方法、query方法、queryForXXX方法、call方法等来执行SQL语句。SpringBoot会自动为我们装配一个JdbcTemplate的Bean,我们只需使用@Autowired来引入它即可。
为了针对学生信息实现一个插入和查询的操作,需要编写StudentRepository,使用JdbcTemplate创建一个支持findAll查找全部学生和addStudent增加用户的方法。
image.png
接下来,我们创建一个学生(Student)的Model,主要包含学生的ID、姓名和电子邮箱信息。这里省略了get、set方法。
image.png

3.3.4 Database Initialization

Spring Boot默认支持数据源自动生成dataSource initializr,它会从根路径classpath下面读取SQL的脚本,如schema.sql和data.sql等。具体可以参考SpringBoot的官方文档《Spring Database Initialization》。
首先,我们编写schema.sql,用来创建学生表。
image.png
接下来我们编写data.sql,用来准备将3条记录插入到学生表中。
image.png
为了记录以上的SQL脚本的日志,我们配置一下logback.xml,用来开启image.png
image.png

3.3.5 启动运行

一般来说,SpringBoot通过Restful Controller对外提供服务。本项目只是一个案例,我们采用SpringBoot的CommandLineRunner,用来接受插入和查询的参数命令,来实现对学生信息的插入和查询功能。

![image.png](https://ucc.alicdn.com/pic/developer-ecology/97e00859619f4b98989bf53c79da8988.png)

将该示例项目执行mvn package脚本打成jar包。如果打包时遇到sunfire问题,可以在代码中新增sunfire maven插件。
image.png
测试数据库和表都创建成功后,数据也已经插入了。执行如下命令:
image.png
返回结果如下:
image.png
我们禁止database initializr的功能,再向数据库中插入一条学生记录simon。
image.png
返回结果如下:
image.png
这时我们再进行一次查询,看看数据是否从之前的3条变成了4条。
image.png
返回结果如下:
image.png

3.4 本章小结

作为一名没有怎么接触过HikariCP的初学者,如何快速了解HikariCP?如何了解它的基础和产生背景?如何快速使用这款开源产品?这些是很多初学者会面临的问题。本章用简短的篇幅,从HikariCP的念法、什么是HikariCP、HikariCP项目的初衷、HikariCP如何运用在SpringBoot 2.0中,为读者进行了详实的介绍,并结合笔者自身的体会来阐述和介绍HikariCP的基础知识。
除了提供一个可以运行的案例以便读者快速上手HikariCP外,本章也给读者带来一个结论:在SpringBoot 1.5.x版本中,数据库连接池的默认配置是Tomcat Pool → HikariCP → Commons DBCP → Commons DBCP2;然而在2.x版本中,HikariCP被提升为默认的数据库连接池,数据库连接池的默认配置顺序变成HikariCP → Tomcat pool → Commons DBCP2。
学习本章,对于了解后续章节的HikariCP的架构、源码、参数、调优等内容打下了良好的基础。

版权声明:本文中所有内容均属于阿里云开发者社区所有,任何媒体、网站或个人未经阿里云开发者社区协议授权不得转载、链接、转贴或以其他方式复制发布/发表。申请授权请邮件developerteam@list.alibaba-inc.com,已获得阿里云开发者社区协议授权的媒体、网站,在转载使用时必须注明"稿件来源:阿里云开发者社区,原文作者姓名",违者本社区将依法追究责任。 如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:developer2020@service.aliyun.com 进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:

华章出版社

官方博客
官网链接