⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 https://www.jianshu.com/p/53de4b76f98c 「许da广」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

前言

数据库连接池在Java数据库相关中间件产品群中,应该算是底层最基础的一类产品,作为企业应用开发必不可少的组件,无数天才们为我们贡献了一个又一个的优秀产品,它们有的随时代发展,功成身退,有的则还在不断迭代,老而弥坚,更有新生代产品,或性能无敌,或功能全面。接下来,就让我们好好聊聊,“那些年,我们用过的数据库连接池”。

第一代连接池

区分一个数据库连接池是属于第一代产品还是第二代产品的一个最重要特征就是看它在架构和设计时采用的线程模型,因为这直接影响的并发环境下存取数据库连接的性能。一般来讲采用单线程同步的架构设计的都属于第一代连接池,而采用多线程异步架构的则属于第二代。比较有代表性的就是Apache Commons DBCP,在1.x版本中,一直延续这单线程设计模型,到2.x版本才采用多线程模型。

用版本发布时间来辨别区分两代产品,则一个偷懒的好方法。以下是这些常见数据库连接池最新版本的发布时间:

数据库连接池 最新版本 发布时间
c3p0 c3p0-0.9.5.2 on 9 Dec 2015
proxool 0.9.x May 24, 2011
dbcp 2.1.1 2015-08-06
BoneCP 0.8.0 on 23 Oct 2013
TJP tomcat9 Jan 10 2017
druid 1.0.28 2017-02-05
hikariCP 2.4.11 2017-01-28

从表中我们可以看到,c3p0、DBCP、Proxool和BoneCP都已经很久没更新了,TJP(Tomcat JDBC Pool),druid,hikariCPze则仍处于活跃的更新中,后者明显就是我们所说的二代产品了。

已经彻底死掉的c3p0和proxool

我对c3p0还是很有感情的,因为在它是我使用的第一款数据库连接池,在很长一段时间内,它一直是Java领域内数据库连接池的代名词,当年盛极一时的Hibernate都将其作为内置的数据库连接池,可见业内对它的稳定行还是认可的。关于c3p0如何使用,可以借助于搜索引擎,这里就不再赘述了。c3p0功能简单易用,稳定性好这是它的优点,但性能上的缺点却让它彻底被打入冷宫。c3p0的性能很差,差到即便是和同时代的产品相比,它也是垫底的(见图一)。同时代的BoneCP更是直接以干掉它为自己的口号(官网号称比c3p0快25倍),更不要说和后来的druid和HikariCP相比了。

正常来讲,有问题很正常,改就是了,但c3p0最致命的问题就是架构设计过于复杂,让重构变成了一项不可能完成的任务。随着国内互联网大潮的涌起,性能有硬伤的c3p0彻底的退出了历史舞台。

如果说c3p0被人嫌弃,是因为它自身架构设计的“原罪”,那proxool的冷门,则是与作者兴趣的缺失有关。proxool最初在设计上另辟蹊径,以JDBC驱动的身份为用户提供连接池服务,这使得将proxool移植到现有代码中别的十分容易,而且proxool还开创性的提供了连接池监控功能,让它迅速的获得了不少用户的青睐。

但产品作者兴趣的缺失,让这款本来很有潜力的产品早早夭折。在github的项目首页,作者写到:“我从2006年之后就再没碰过这个项目了,我甚至练Java都不用了...”,也许,proxool本来就是这位天才coder的练手之作,java本身也不是他的主力语言,但不论哪种原因,proxool都已经和c3p0一样,鲜有人问津了。

This project is no longer actively maintained. I haven't used Proxool myself since 2006 and no longer even use Java. If the project has any chance of survival at all it would need to find a new maintainer. If anyone can recommend a good alternative I would be happy to mention it here. Alternatively, if anyone wants to contribute to this project then please create a pull request.

图一:第一代连接池性能测试

咸鱼翻身的dbcp

dbcp(DataBase Connection Pool)属于Apache顶级项目Commons中的核心子项目(最早在Jakarta Commons里就有),在Apache的生态圈中的影响里十分广泛,比如最为大家所熟知的tomcat就在内部集成了dbcp,实现JPA规范的OpenJPA,也是默认集成dbcp的。但dbcp并不是独立实现连接池功能的,它内部依赖于Commons中的另一个子项目pool,连接池最核心的“池”,就是由pool组件提供的,因此,dbcp的性能实际上就是pool的性能,dbcp和pool的依赖关系如下表:

Apache Commons DBCP Apache Commons Pool
v1.2.2 v1.3
v1.3 v1.5.4
v1.4 v1.5.4
v2.0.x v2.2
v2.1.x v2.4.2

可以看到,因为核心功能依赖于pool,所以dbcp本身只能做小版本的更新,真正大版本的更迭则完全依托于pool。有很长一段时间,pool都还是停留在1.x版本,这直接导致dbcp也更新乏力。很多依赖dbcp的应用在遇到性能瓶颈之后,别无选择,只能将其替换掉,dbcp忠实的拥趸tomcat就在其tomcat 7.0版本中,自己重新设计开发出了一套连接池(Tomcat JDBC Pool)。好在,在2013年事情终于迎来转机,13年9月Commons-Pool 2.0版本发布,14年2月份,dbcp也终于迎来了自己的2.0版本,基于新的线程模型全新设计的“池”让dbcp重焕青春,虽然和新一代的连接池相比仍有一定差距,但差距并不大,dbcp 2.x版本已经稳稳达到了和新一代产品同级别的性能指标(见下图)。

图二:DBCP 2.x与新一代产品的性能对比

dbcp终于靠pool咸鱼翻身,打了一个漂亮的翻身仗,但长时间的等待已经完全消磨了用户的耐心,与新一代的产品项目相比,dbcp没有任何优势,试问,谁会在有选择的前提下,去选择那个并不优秀的呢?也许,现在还选择dbcp2的唯一理由,就是情怀吧。

甘心赴死的BoneCP

在讨论BoneCP这块的内容之前,我们还是先来看看BoneCP作者自己是这么评价这款产品的:

BoneCP is a Java JDBC connection pool implementation that is tuned for high performance by minimizing lock contention to give greater throughput for your applications. It beats older connection pools such as C3P0 and DBCP but should now be considered deprecated in favour of HikariCP.

用我自己的话翻译一下就是:俺是一个高性能的数据库连接池,俺之所以这么牛逼是因为俺在实现的时候减少了锁的使用,想当年,什么c3p0啊DBCP啊都被老子干趴了,但是现在为了支持HikariCP,俺选择退出!也就是说,BoneCP的退出是它自己的选择,但它又不像proxool是被抛弃的,它是作者经过深思熟虑后,做出的选择,可以说BoneCP是“甘心赴死,杀身成仁”。那么问题来了,BoneCP究竟是不是像它自己形容的那样牛逼?BoneCP和HikariCP之间究竟有啥联系,能引得它主动“金盆洗手”?

先说性能,BoneCP自称性能是c3p0的25倍,并提供了依照自己定义的测试案例,提供了一组图片

  1. 单线程(1,000,000获得及释放数据库连接请求,连接池大小20-50)

单线程

  1. 多线程(500线程分别获取释放100个链接,连接池大小50-200)

多线程1

  1. 多线程(500个线程每个100次获得/释放,连接池大小20-500)

多线程2

当然,以上图片仅供参考,因为不同的参数配置,不同的应用环境,不同的测试案例,得到的结果肯定也不会相同,官网提供的数据,肯定是在最有利于自己表现的环境下得到的。但结合另外一份测试数据(第一幅图),可以看到BoneCP的性能在第一代产品中,确实是属于领先地位的。高性能的表现的秘诀也并不高深,一是极简的设计,整个产品只有几百k大小,二是重构内部pool的设计,减少锁的使用,而这两点优化原则,几乎适用于所以的连接池产品。

值得一提的是,BoneCP本身并不“健全”,它的很多特征都依赖于Guava,因此也就和dbcp一样,面临更新乏力的问题。但现在,这些问题都不重要了,因为它赖以为傲的性能被HikariCP全面超越。HikariCP可以说是BoneCP的二代产品(HikariCP自己在官网上声称在BoneCP的基础上,做了很多优化),它在设计思路上和BoneCP完全一致,主打的特征也是超强的性能表现,关于HikariCP的详细内容,我将在下一章节介绍。

站在巨人肩膀上的第二代连接池

在数据库连接池的产品群中,二代产品对一代产品的超越是颠覆性的,除了一些“历史原因”,你很难再找到第二条理由说服自己不选择二代产品,但任何成功都不是偶然的,二代产品的成功很大程度上得益于前代产品们打下的基础,站在巨人的肩膀上,新一代的连接池的设计师们将这一项“工具化”的产品,推向了极致。其中,最具代表性的两款产品是:

  • HikariCP
  • druid

性能无敌的HikariCP

刚刚在介绍BoneCP的时候多少已经提到过HikariCP了,作为连接池产品中的“性能杀手”,它的表现究竟如何呢,先来看下官网提供的数据:

图三:HikariCP官网配图1

不光性能强劲,稳定性也不差:

图四:HikariCP官方配图2

那它是怎么做到如此强劲的呢?官网给出的说明如下:

  • 字节码精简:优化代码,直到编译后的字节码最少,这样,CPU缓存可以加载更多的程序代码;
  • 优化代理和拦截器:减少代码,例如HikariCP的Statement proxy只有100行代码,只有BoneCP的十分之一;
  • 自定义数组类型(FastStatementList)代替ArrayList:避免每次get()调用都要进行range check,避免调用remove()时的从头到尾的扫描;
  • 自定义集合类型(ConcurrentBag):提高并发读写的效率;
  • 其他针对BoneCP缺陷的优化,比如对于耗时超过一个CPU时间片的方法调用的研究(但没说具体怎么优化)。

可以看到,上述这几点优化,针对的都是BoneCP现有的缺陷,优化到这份上,也难怪BoneCP的作者不想玩了。综合现在能找到的资料来看,HakariCP在性能上的优势应该是得到共识的,再加上它自身小巧的身形,在当前的“云时代、微服务”的背景下,HakariCP一定会得到更多人的青睐。

功能全面的druid

近几年,阿里在开源项目上动作频频,除了有像fastJson这类工具型项目,更有像AliSQL这类的大型软件,今天说的druid,就是阿里众多优秀开源项目中的一个。它除了提供性能卓越的连接池功能外,还集成了sql监控,黑名单拦截等功能,用它自己的话说,druid是“为监控而生”。借助于阿里这个平台的号召力,产品一经发布就赢得了大批用户的拥趸,从用户使用的反馈来看,druid也确实没让用户失望。

相较于其他产品,druid另一个比较大的优势,就是中文文档比较全面(毕竟是国人的项目么),在github的wiki页面,列举了日常使用中可能遇到的问题,对一个新用户来讲,上面提供的内容已经足够指导它完成产品的配置和使用了。

下图为druid自己提供的性能测试数据:

图五:druid提供的性能测试数据

最后,隐身的连接池

时至今日,虽然每个应用(需要RDBMS的)都离不开连接池,但在实际使用的时候,连接池已经可以做到“隐形”了。也就是说在通常情况下,连接池完成项目初始化配置之后,就再不需要再做任何改动了。不论你是选择druid或是HikariCP,甚至是DBCP,它们都足够稳定且高效!我们之前讨论了很多关于连接池的性能的问题,但这些性能上的差异,是相交于其他连接池而言的,对整个系统应用来说,第二代连接池在使用过程中体会到的差别是微乎其微的,基本上不存在因为连接池的自身的配饰和使用导致系统性能下降的情况,除非是在单点应用的数据库负载足够高的时候(压力测试的时候),但即便是如此,通用的优化的方式也是单点改集群,而不是在单点的连接池上死扣。

与我而言,连接池是我打开自己技术栈(Java存储层相关)的起点,它有着作为起点的一切优点:使用广泛,入门简单,同时核心思想和实践又足够有料。本篇文章仅是一个开端,既是帮助我(和大家)理清楚市面上这些产品的现状,同时也为为接下来马上开始的“深入研究”铺点底子。如果一定要说点什么感悟的话,那我只能再一次感叹开源的力量以及社区对Java技术推广的巨大助力。开源是一种精神,分享是一种态度,而这,正是Java语言几十年依旧屹立不倒的原因。

文章目录
  1. 1. 前言
  2. 2. 第一代连接池
    1. 2.1. 已经彻底死掉的c3p0和proxool
    2. 2.2. 咸鱼翻身的dbcp
    3. 2.3. 甘心赴死的BoneCP
  3. 3. 站在巨人肩膀上的第二代连接池
    1. 3.1. 性能无敌的HikariCP
    2. 3.2. 功能全面的druid
  4. 4. 最后,隐身的连接池