开源实现
按采用的线程模型分类:
单线程 | 多线程 |
---|---|
c3p0 | DBCP 2.x |
Proxool | Tomact JDBC Pool |
XAPool | BoneCP |
DBCP 1.x | Druid |
Hakari |
c3p0
c3p0 具有超过 230 个 synchronize
同步块和方法,在不同的类中充斥着大量 wait()
及 notifyAll()
方法,这些导致死锁倾向的代码造成了在网络上搜索“c3p0死锁”可以查到大量的资料。由于代码量复杂等原因,c3p0 在基准测试中也始终排在最后。c3p0 在默认情况下不会在 getConnection
的时候测试连接可用性,这点也是不安全的默认配置。
但 c3p0 也提供了一些有用的功能:
- 一个将传统的基于
DriverManager
的 JDBC 驱动程序调整为较新的javax.sql. DataSource
方案的类,以获取数据库连接。 - 基于
DataSources
的Connection
和PreparedStatements
的透明池,可以“包装”传统驱动程序或任意非池化数据源。
c3p0 在以下细节上进行了打磨以确保正确性:
DataSources
都是可引用和可序列化的,因此其适合绑定到各种基于 JNDI 的命名服务。- 当引入
Connection
和Statement
时,都会仔细清理Statement
和ResultSets
,这是为了防止客户端使用Lazy模式。但常见的资源管理策略仅仅清理Connection
而造成资源耗尽。 - 该库采用 JDBC 2 和 JDBC 3 规范定义的方法(即使这些方法与库作者的首选项冲突)。
DataSources
以 JavaBean 样式编写,提供所有必需和大多数可选属性(以及一些非标准属性)和无参构造函数。- 实现了所有 JDBC 定义的内部接口(
ConnectionPoolDataSource
,PooledConnection
,ConnectionEvent-generating Connections
等)。 - 用户可以将 c3p0 类与兼容的第三方实现混合使用(尽管并非所有 c3p0 功能都可以与
ConnectionPoolDataSource
的外部实现一起使用)。
- 实现了所有 JDBC 定义的内部接口(
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 决定着数据库连接池的整体性能。
在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 万行。
功能对比
- 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秒的调用后超时,应用程序应在指定时间内获得连接,或获得异常。
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.