开源实现

按采用的线程模型分类:

单线程多线程
c3p0DBCP 2.x
ProxoolTomact JDBC Pool
XAPoolBoneCP
DBCP 1.xDruid
Hakari

c3p0

c3p0 具有超过 230 个 synchronize 同步块和方法,在不同的类中充斥着大量 wait()notifyAll() 方法,这些导致死锁倾向的代码造成了在网络上搜索“c3p0死锁”可以查到大量的资料。由于代码量复杂等原因,c3p0 在基准测试中也始终排在最后。c3p0 在默认情况下不会在 getConnection 的时候测试连接可用性,这点也是不安全的默认配置。

但 c3p0 也提供了一些有用的功能:

  • 一个将传统的基于 DriverManager 的 JDBC 驱动程序调整为较新的 javax.sql. DataSource 方案的类,以获取数据库连接。
  • 基于 DataSourcesConnectionPreparedStatements 的透明池,可以“包装”传统驱动程序或任意非池化数据源。

c3p0 在以下细节上进行了打磨以确保正确性:

  • DataSources 都是可引用和可序列化的,因此其适合绑定到各种基于 JNDI 的命名服务。
  • 当引入 ConnectionStatement 时,都会仔细清理 StatementResultSets,这是为了防止客户端使用Lazy模式。但常见的资源管理策略仅仅清理 Connection 而造成资源耗尽。
  • 该库采用 JDBC 2 和 JDBC 3 规范定义的方法(即使这些方法与库作者的首选项冲突)。 DataSources 以 JavaBean 样式编写,提供所有必需和大多数可选属性(以及一些非标准属性)和无参构造函数。
    • 实现了所有 JDBC 定义的内部接口(ConnectionPoolDataSource, PooledConnection,ConnectionEvent-generating Connections等)。
    • 用户可以将 c3p0 类与兼容的第三方实现混合使用(尽管并非所有 c3p0 功能都可以与 ConnectionPoolDataSource 的外部实现一起使用)。

Proxool

Proxool 以JDBC 驱动的身份为用户提供透明的连接池服务,所以 Proxool 移植到现有代码中特别容易,用户可以轻松地使用 JDBC API、XML 或 Java 属性文件进行配置。

Proxool 在那个年代另辟蹊径,开创性地提供了连接池监控功能,便于发现连接泄漏的等性能情况及连接事件。

它的很多设计理念都被 HikariCP 认可并吸收,HikariCP 在继承过程中进行了独具匠心的打磨。例如,关于 Cglib 等字节码的代理,这也是 HikariCP 仔细打磨的地方。

XAPool

XA 是 X/Open CAE Specification(Distributed TransactionProcessing) 模型中定义的 TM(Transaction Manager) 与 RM(Resource Manager) 之间进行通信的接口。

Java 中的 javax.transaction.xa.XAResource 定义了XA接口,它依赖数据库厂商对 jdbc-driver 的具体实现。在 XA 规范中,数据库充当 RM 角色,应用需要充当 TM 的角色,即生成全局的 txId,调用 XAResource 接口,把多个本地事务协调为全局统一的分布式事务。

XAPool 是一个 XA 数据库连接池,它实现了 javax.sql.XADataSource,并提供了连接池工具。这是一款主打分布式事务的数据库连接池,它允许池对象,JDBC 连接和 XA 连接。

DBCP

Apache Commons DBCP 并不是独立实现连接池功能的,它内部依赖于 Commons 中的另一个子项目 Apache Commons Pool。数据库连接池中最核心的“池”,就是由 Pool 组件提供的,Apache Commons Pool 决定着数据库连接池的整体性能。

NAME

在2014年3月,DBCP终于更新到了2.x版本,基于新的线程模型的数据库连接池让DBCP焕然一新重获新生,稳定性得到提升,性能也有了质的提升。Apache Commons Pool 2类库是对象池技术的一种具体实现,它的出现是为了解决频繁的创建和销毁对象带来的性能损耗问题。

其原理就是建立一个对象池,池中预先生成了一些对象,需要对象的时候借用,用完后进行归还,对象不够时灵活地自动创建,对象池满后提供参数控制是阻塞还是非阻塞响应租借用。

在 SpringBoot 1.5.x 版本中,数据库连接池的默认配置是 Tomcat Pool → HikariCP →Commons DBCP → Commons DBCP2;然而在 2.x 版本中,HikariCP 被提升为默认的数据库连接池,数据库连接池的默认配置顺序是 HikariCP → Tomcat pool→ CommonsDBCP2。

Tomcat JDBC Pool

在 DBCP 2.0 之前,为什么需要一个新的连接池:

  • DBCP 1.x 是单线程。为了线程安全,在对象分配或对象返回的短期内,Commons 锁定了全部池。
  • DBCP 1.x 可能会变得很慢。当逻辑 CPU 数目增长,或者试图借出或归还对象的并发线程增加时,性能就会受到影响。高并发系统受到的影响会更为显著。
  • DBCP 拥有 60 多个类,而 tomcat-jdbc-pool 核心只有 8 个类。因此为了未来需求变更着想,肯定需要更少的改动。我们真正需要的只是连接池本身,其余的只是附属。
  • DBCP 使用静态接口,因此对于指定版本的 JRE,只能采用正确版本的 DBCP,否则就会出现 NoSuchMethodException 异常。
  • 当 DBCP 可以用其他更简便的实现来替代时,实在不值得重写那 60 个类。
  • Tomcat JDBC 连接池无需为库本身添加额外线程,就能异步获取连接。
  • Tomcat JDBC 连接池是 Tomcat 的一个模块,依靠 Tomcat JULI 这个简化了的日志架构。
  • 使用 javax.sql.PooledConnection 接口获取底层连接。
  • 防止饥饿。如果池变空,线程将等待一个连接。当连接返回时,池就将唤醒正确的等待线程。大多数连接池只会一直维持饥饿状态。

Tomcat JDBC Pool 还具有其他连接池没有的特点:

  • 支持高并发环境与多 核/CPU 系统。
  • 接口的动态实现。支持 java.sql 与 java.sql 接口(只要JDBC驱动),甚至在利用低版本的 JDK 来编译时也支持。
  • 验证间隔时间。我们不必每次使用单个连接时都进行验证,可以在借出或归还连接时进行验证,只要不低于我们所设定的间隔时间就行。
  • 只执行一次查询。当与数据库建立起连接时,只执行一次可配置查询。这项功能对会话设置非常有用,因为你可能会想在连接建立的整个时段内都保持会话。
  • 能够配置自定义拦截器。通过自定义拦截器来增强功能。可以使用拦截器来采集查询统计,缓存会话状态,重新连接之前失败的连接,重新查询,缓存查询结果,等等。由于可以使用大量的选项,所以这种自定义拦截器也是没有限制的,与 java.sql/javax.sql 接口的 JDK 版本没有任何关系。
  • 高性能。后面将举例展示一些性能差异。
  • 极其简单。它的实现非常简单,代码行数与源文件都非常少,这都有赖于从一开始研发它时,就把简洁当作重中之重。核心只有8个文件。
  • 异步连接获取。可将连接请求队列化,系统返回 Future<Connection>
  • 更好地处理空闲连接。不再简单粗暴地直接关闭空闲连接,而是把连接仍然保留在池中,通过更为巧妙的算法控制空闲连接池的规模。
  • 可以控制连接应被废弃的时间。当池满了即废弃,或者指定一个池使用容差值,发生超时就进行废弃处理。
  • 通过查询或语句来重置废弃连接计时器。允许一个使用了很长时间的连接不会因为超时而被废弃。这一点是通过使用 ResetAbandonedTimer 来实现的。
  • 经过指定时间后,关闭连接。与返回池的时间相类似。
  • 当连接要被释放时,获取 JMX 通知并记录所有日志。它类似于 remove-AbandonedTimeout,但却不需要采取任何行为,只需要报告信息即可。通过 suspectTimeout 属性来实现。
  • 可以通过 java.sql.Driver、javax.sql.DataSource 或 javax.sql.XADataSource 获取连接。通过 dataSource 与 dataSourceJNDI 属性实现这一点。
  • 支持 XA 连接。

其缺点有:

  • 默认配置也存在类似 c3p0 的问题,就是在 getConnection 的时候并不会默认测试连接可用性。
  • 不完全遵守 JDBC 规范,默认也不会重置连接状态(如自动提交、事务隔离级别等),用户必须手动配置名为 ConnectionState 的 JDBCInterceptor。
  • 在自动提交中,如果连接池配置了 autocommit=false,就需要在自己的事务中执行连接有效性测试 isValid(),否则使用者获取的连接有可能就在一个事务进行中。
  • 对于创建连接时可以在连接上运行的初始化 initSQL 也是如此,Tomcat 不会在自己的事务中封装连接测试或 initSQL。
  • 连接池应该在 Connection 返回到池时或从池中取出之前,调用 clearWarnings() 方法清除 SQL 警告,然而 TomcatJDBC 也没有这么做。
  • JDBC 规范还规定,连接关闭时,所有没有关闭的、已经打开的 Statements 都应该自动关闭,但是默认情况下 Tomcat JDBC 并不会跟踪 Statements,除非手动配置一个 StatementFinalizer 拦截。
  • 不幸的是,StatementFinalizer 使用一组 WeakReference 对象跟踪 Statements,当 JVM 受到 GC压力时,在 Tomcat 有机会关闭这些语句之前,可能会对废弃的 Statements 进行垃圾收集,这可能导致资源的泄漏,但是只有在 GC 压力下才会发生,因此可能很难追踪。

BoneCP

在 c3p0 和 DBCP 已经存在的时代,BoneCP 的出现就是为了追求极致,它几乎比下一个最快的连接池选项快 25 倍,而且 BoneCP 从不自旋锁定,因此它不会减慢应用程序速度。

BoneCP 可以说是极致数据库连接池的领军开源项目。它和 HikariCP 也是非常有渊源的,除了 HikariCP 捐赠了 BoneCP 几美金的故事以外,BoneCP 在浪潮之巅功成身退,深藏功与名,将一身衣钵传给了 HikariCP。

BoneCP 的特点如下:

  • 具有高可扩展性的快速连接池。
  • 在 connection 状态改变时,可配置回调机制(钩式拦截器)。
  • 通过分区(Partitioning)来提升性能。
  • 允许用户直接访问 connection 或 statement。
  • 自动扩展 pool 容量。
  • 支持 statement caching。
  • 支持异步地获取connection,通过返回一个 Future<Connection> 实现。
  • 以异步的方式施放辅助线程,来关闭 connection 和 statement,以获得高性能。
  • 在每个新获取的 connection 上,通过简单的机制,执行自定义的 statement。即通过简单的 SQL 语句来测试 connection 是否有效,对应的配置属性为 initSQL。
  • 支持运行时切换数据库,而不需要停止应用。
  • 能够自动地回放任何失败的事务(如数据库或网络出现故障等)。
  • 支持JMX。
  • 可以延迟初始化(lazy initialization)。
  • 支持使用 XML 或 property 文件的配置方式。
  • 支持 idle connection timeouts 和 max connection age。
  • 自动检验 connection 是否活跃等等。
  • 允许直接从数据库获取连接,而不通过 Driver。
  • 支持 Datasouce 和 Hibernate。
  • 支持通过 debugging hooks 来定位获取后未关闭的 connection。
  • 支持通过 debugging 来显示被关闭了两次的 connection 的堆栈轨迹(stack locations)。
  • 支持自定义 pool name。
  • 代码整洁有序。
  • 免费,开源,纯 Java 编写,具有完整的文档。

BoneCP 最大的一个问题是无法在 getConnection() 的时候配置数据库连接池来测试连接。然而其他每个数据库连接池大多都可以这样配置。它这样做是为了提升速度,但却牺牲了可靠性。

  • 在默认配置方面,BoneCP 也不会在 Connection 返回到池时或从池中取出之前通过 Connection.clearWarnings() 方法清除 SQL 警告;
  • 默认情况下也不会关闭废弃的、已经打开的 statements;
  • 也不会在自己的事务中封装连接测试或 initSQL。

Druid

主要功能:

  • 替换 DBCP 和 c3p0。Druid 提供了一个高效、功能强大、扩展性好的数据库连接池。
  • 可以监控数据库访问性能。Druid 内置了一个功能强大的 StatFilter 插件,能够详细统计 SQL 的执行性能,这有助于对线上数据库访问性能进行分析。
  • 数据库加密。直接把数据库密码写在配置文件中是不好的行为,容易导致安全问题。DruidDruiver 和 DruidDataSource 都支持 PasswordCallback。
  • SQL 执行日志。Druid 提供了不同的 LogFilter,能够支持 Common-Logging、Log4j 和 JdkLog,用户可以按需要选择相应的 LogFilter,监控自己的应用的数据库访问情况。
  • 扩展 JDBC。如果用户对 JDBC 层有编程的需求,可以通过 Druid 提供的 Filter 机制,很方便地编写 JDBC 层的扩展插件。

Druid 在监控、可扩展性、稳定性和性能方面都有明显的优势:

  • 强大的监控特性,通过 Druid 提供的监控功能,可以清楚地知道连接池和 SQL 的工作情况。
    • 监控 SQL 的执行时间、ResultSet 持有时间、返回行数、更新行数、错误次数、错误堆栈信息。
    • SQ L执行的耗时区间分布。什么是耗时区间分布?比如,某个 SQL 执行了 1000 次,其中在 0~1 毫秒区间50次…
    • 监控连接池的物理连接创建和销毁次数、逻辑连接的申请和关闭次数、非空等待次数、PSCache 命中率等。
  • 方便扩展。Druid 提供了 Filter-Chain 模式的扩展 API,可以自己编写 Filter 拦截 JDBC 中的任何方法。
  • Druid 集合了开源和商业数据库连接池的优秀特性,并结合阿里巴巴公司大规模苛刻生产环境的使用经验进行了优化。
  • 性能不是 Druid 的设计目标,但是测试数据表明,Druid 性能比 DBCP、c3p0、Proxool、JBoss 都好。
    • Druid 代码将近 50 万行。

功能对比

NAME
  • LRU。LRU 是一个性能关键指标,特别是 Oracle,其中每个 Connection 对应数据库端的一个进程,如果数据库连接池遵从 LRU,有助于数据库服务器优化,这是重要的指标。
  • PSCache。PSCache 是数据库连接池的关键指标。在 Oracle 中,类似 SELECT NAME FROM USER WHERE ID =?这样的 SQL,启用 PSCache 和不启用 PSCache 的性能可能会相差一个数量级。
  • PSCache-Oracle-Optimized。在 Oracle 10 系列的 Driver 中,如果开启 PSCache,会占用大量的内存,必须做特别的处理,启用内部的 EnterImplicitCache 等方法优化才能够减少内存的占用。
  • ExceptionSorter。ExceptionSorter 是一个很重要的容错特性,如果一个连接产生了一个不可恢复的错误,必须立刻将其从连接池中去掉,否则会连续产生大量错误。

中断测试

将数据库连接池执行 getConnection() 在5秒的调用后超时,应用程序应在指定时间内获得连接,或获得异常。

NAME