Java开发常用的几个数据库连接池

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java开发常用的几个数据库连接池相关的知识,希望对你有一定的参考价值。

数据库连接池的好处是不言而喻的,现在大部分的application
server都提供自己的数据库连接池方案,此时,只要按照application server的文档说明,正确配置,即可在应用中享受到数据库连接池的好处。

但是,有些时候,我们的应用是个独立的java
application,并不是普通的WEB/J2EE应用,而且是单独运行的,不要什么application
server的配合,这种情况下,我们就需要建立自己的数据库连接池方案了。

1、 DBCP

DBCP是Apache的一个开源项目:
commons.dbcp

DBCP依赖Apache的另外2个开源项目
commons.collections和commons.pool

dbcp包,目前版本是1.2.1:http://jakarta.apache.org/commons/dbcp/

pool包,目前版本是1.3:http://jakarta.apache.org/commons/pool/,

common-collections包:http://jakarta.apache.org/commons/collections/

下载这些包并将这些包的路径添加到classpath中就可以使用dbcp做为项目中的数据库连接池使用了。

在建立我们自己的数据库连接池时,可以使用xml文件来传入需要的参数,这里只使用hard
code的方式来简单介绍,所有需要我们自己写的代码很少,只要建立一个文件如下:
import
org.apache.commons.dbcp.BasicDataSource;
import
org.apache.commons.dbcp.BasicDataSourceFactory;
import
java.sql.SQLException;
import java.sql.Connection;
import
java.util.Properties;

public class ConnectionSource

private static BasicDataSource dataSource =
null;

public ConnectionSource()


public static void init()

if (dataSource != null)
try

dataSource.close();
catch (Exception e)



dataSource = null;


try
Properties p = new
Properties();

p.setProperty("driverClassName",
"oracle.jdbc.driver.OracleDriver");
p.setProperty("url",
"jdbc:oracle:thin:@192.168.0.1:1521:testDB");

p.setProperty("password", "scott");
p.setProperty("username",
"tiger");
p.setProperty("maxActive", "30");

p.setProperty("maxIdle", "10");
p.setProperty("maxWait",
"1000");
p.setProperty("removeAbandoned",
"false");
p.setProperty("removeAbandonedTimeout",
"120");
p.setProperty("testOnBorrow", "true");

p.setProperty("logAbandoned", "true");

dataSource = (BasicDataSource)
BasicDataSourceFactory.createDataSource(p);

catch (Exception e)




public static synchronized Connection
getConnection() throws SQLException

if (dataSource == null)

init();


Connection conn = null;

if (dataSource != null)

conn = dataSource.getConnection();


return conn;



接下来,在我们的应用中,只要简单地使用ConnectionSource.getConnection()就可以取得连接池中的数据库连接,享受数据库连接带给我们的好处了。当我们使用完取得的数据库连接后,只要简单地使用connection.close()就可把此连接返回到连接池中,至于为什么不是直接关闭此连接,而是返回给连接池,这是因为dbcp使用委派模型来实现Connection接口了。

在使用Properties来创建BasicDataSource时,有很多参数可以设置,比较重要的还有:

testOnBorrow、testOnReturn、testWhileIdle,他们的意思是当是取得连接、返回连接或连接空闲时是否进行有效性验证(即是否还和数据库连通的),默认都为false。所以当数据库连接因为某种原因断掉后,再从连接池中取得的连接,实际上可能是无效的连接了,所以,为了确保取得的连接是有效的,
可以把把这些属性设为true。当进行校验时,需要另一个参数:validationQuery,对oracle来说,可以是:SELECT COUNT(*) FROM
DUAL,实际上就是个简单的SQL语句,验证时,就是把这个SQL语句在数据库上跑一下而已,如果连接正常的,当然就有结果返回了。

还有2个参数:timeBetweenEvictionRunsMillis 和
minEvictableIdleTimeMillis,
他们两个配合,可以持续更新连接池中的连接对象,当timeBetweenEvictionRunsMillis
大于0时,每过timeBetweenEvictionRunsMillis
时间,就会启动一个线程,校验连接池中闲置时间超过minEvictableIdleTimeMillis的连接对象。

还有其他的一些参数,可以参考源代码。

2、
C3P0:

C3P0是一个开放源代码的JDBC连接池,C3PO
连接池是一个优秀的连接池,推荐使用。C3PO实现了JDBC3.0规范的部分功能,因而性能更加突出,包括了实现jdbc3和jdbc2扩展规范说明的Connection 和Statement 池的DataSources 对象。
下载地址:http://sourceforge.net/projects/c3p0

package
com.systex.utils.web;

import java.beans.PropertyVetoException;
import
java.sql.Connection;
import java.sql.SQLException;
import
javax.sql.DataSource;
import
com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3PODataSource
private static
ComboPooledDataSource dataSource = null;
private static final String driver
= "com.mysql.jdbc.Driver";
private static final String url =
"jdbc:mysql://localhost:3306/wyd";
private static final String userName =
"root";
private static final String password = "root";

public static DataSource getDataSource()
if
(dataSource == null)
dataSource = new ComboPooledDataSource();
try

dataSource.setDriverClass(driver);
catch (PropertyVetoException
e)
System.out.println("DataSource Load Driver
Exception!!");
e.printStackTrace();

dataSource.setJdbcUrl(url);
dataSource.setUser(userName);
dataSource.setPassword(password);
//
设置连接池最大连接容量
dataSource.setMaxPoolSize(20);
//
设置连接池最小连接容量
dataSource.setMinPoolSize(2);
//
设置连接池最大statements对象容量
dataSource.setMaxStatements(100);

return
dataSource;


public static Connection getConnection() throws
SQLException
return
C3PODataSource.getDataSource().getConnection();



3、 Proxool

这是一个Java SQL
Driver驱动程序,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。

官方网站: http://proxool.sourceforge.net/

下载地址:http://proxool.sourceforge.net/download.html
参考技术A DateSource

数据库连接池的几个参数-以tomcat-jdbc举例





在某次大范围网络变更中,有些应用出现了数据库连接在网络恢复后不能自动恢复的情况,只有重启解决。这一般是数据库连接池配置不合理引起的,那么我们就拿tomcat-jdbc为例看看数据库连接池配置的一些注意事项。


对于数据库连接这种比较重的资源,我们在使用中一般都会进行池化,也就是我们会将创建好的数据库连接放到池子里,当我们需要使用的时候从池子里取,而不是创建新的(当然,是不是创建新的也要看连接池的配置),我们用完连接后归还池子留着以后使用。

那么这样就有一个问题:我们放在池子里的连接还是有效的么?因为数据库连接其实归根结底也就是一个TCP连接,而TCP连接如果你不真的去操作它(读取或写入),你是不知道这条连接的状态的。比如我们现在要进行一次数据库操作,然后我们从池子里拿出一个连接,然后组织好SQL和参数,发起操作。发起之后出异常了,因为这个连接已经无效了。那么这就涉及数据库连接的有效性测试了,不过这个测试也是有讲究的。

就拿tomcat-jdbc(其他连接池基本有类似参数)这个连接池来说就有testOnBorrow,testOnReturn,testWhileIdle和testOnConnect这么多姿势,可见这个数据库连接的有效性检测也是有点猫腻,我们先从代码层面上来看看这几种测试具体的区别,然后再谈谈我的看法,最后我们可以进行性能测试,看看这些参数对性能的具体影响。


testOnBorrow

从字面意思上理解就是,从连接池拿连接的时候会进行有效性测试。那么看起来,一个数据库密集操作的应用来讲,如果每次从池里拿连接都要进行一下测试,成本好像太高了点。那真实的情况是这样么?

其实在有效性测试时我们还有另外两个参数需要关注:a. validationInterval, b. validationQuery。这两个参数分别是测试间隔和测试所使用的SQL(一般大家都会使用SELECT 1)。其实对于testOnBorrow来讲,也并不是每次拿连接的时候都会进行有效性测试,而也是要看validationInterval,如果上一次的测试时间到现在超过了这个时间,则会进行有效性测试。所以testOnBorrow正确的含义应该是这个有效性测试发生在拿连接的时候,但是不是要测试还要看测试间隔(validationInterval)。


了解了testOnBorrow后,另外几个也就没有什么了。


testOnReturn: 连接归还连接池的时候检测,但也会检查validationInterval

testWhileIdle: 后台有个线程会扫描连接池里没有正在使用的连接,然后如果上次检测时间到现在超过了validationInterval则对其进行检测。

testOnConnect: 连接创建的时候检测,连接创建时就不受validationInterval限制了。另外,即使testOnConnect没有设置,如果设置了initSQL也会进行测试,如果设置了initSQL则测试的SQL就是initSQL而不是validationQuery。不过一般来讲连接创立时感觉不需要测试(如果谁有更好的理由可以告诉我),连接能建立成功表示1: 网络是通的,并且mysql监听端口也是通的 2: TCP连接建立后,mysql client还需要和mysql server进行握手并获取server端一些环境变量,所以基本上可以认为连接是有效的,不过我们倒可以利用initSQL来做一些检查:比如关键数据库表是否存在等。


ok,我们现在基本上了解了这几个参数表达的含义,那么设置和不设置这几个参数的影响是什么呢?


如果这几个参数我们都不设置,那意思是我们不进行连接池里连接有效性检查,那么一旦因为网络等原因导致连接失效,则这个连接永远都无法恢复了,只有重启解决(不过也有一些其他参数可以让连接恢复,后文会提到),我想这不是我们想要的。那么,如果我们要设置该如何设置呢?testOnBorrow和testOnReturn都是在使用连接的路径上进行测试,所以如果进行测试的时候,会稍稍增加一些延迟,不过我觉得这个延迟并不太大,后面我会进行实际的测试。而testWhileIdle是后台线程对空闲连接测试,不在连接的使用链路上,不会增加延迟,看起来更合理。但是,对于一个繁忙的应用来讲,估计很少有连接是空闲的,我觉得对于繁忙的关键应用,仅设置testWhileIdle并不合适,而对于一个不太繁忙的应用可能连接大部分都是空闲的,进行testWhileIdle测试又有点浪费的感觉。而如果设置testOnReturn,当高峰期过后,经过有效性测试的连接变成空闲连接,这个期间很有可能连接失效了,当高峰期再次来临的时候拿到的是失效连接。所以,我觉得应该默认推荐testOnBorrow(千万不要被名字迷惑,并不是每次都测试)。


除了上面连接测试相关的几个参数外,还有几个参数在使用连接池的时候也要注意:


maxActive,maxIdle,minIdle,initialSize

这几个从字面上就能很好理解了。maxActive是最大活跃连接数,这个数字不宜设置过大,太多的并发连接对数据库的压力很大,甚至会导致雪崩,这是一定要注意的。但是如果设置过小,而应用的服务线程数有很高,可能会导致有的服务线程拿不到连接,所以服务的线程数和数据库连接数是需要经过配合调整的。minIdle是允许的最小空闲连接数,比如当高峰期过后,连接使用的少了,但是连接池还是会为你留着minIdle的连接,以备高峰期再次来临的时候不需要创建连接。而initialSize就是连接池初始化的时候就为你先创建这么多个连接出来,这个参数其实是很有用的。比如我们一个应用刚启动的时候,有的同学会发现为啥处理速度这么慢呢?有一个原因可能就是要新创建连接,那么我们可以设置一个初始值,确保应用启动后,流量进入的时候已经有一些可以服务的连接了。


maxAge

这个参数是一个时间值,当连接归还到连接池的时候会判断,如果开始连接的时间到现在超过maxAge,则会将该连接关闭。初看起来这个有点莫名其妙,连接池不就是为了连接复用么,为啥又要关闭连接呢?其实这个参数我也认为是有点打补丁的感觉。这个参数主要是为了防止连接上附加了太多的资源,比如我们一个应用,因为某些原因我在连接上附加一个attaments,这个attaments可能占用很大的内存,如果我们的连接从来不释放,即使是归还到连接池成为空闲连接,它还是会占用这么多资源。所以连接池就提供了一种方式,你可以设置一个时间,当连接归还到池里的时候检查一下,如果超时就关闭了,避免资源长时间泄露(这就像我们有一个系统,因为代码没写好会导致资源泄露,然后为了紧急修复我们就写了个脚本,每天高峰期来临之前先重启一下系统)。

这个参数看起来也能防止因为网络等原因导致连接失效而不能自动恢复的问题,但是这个参数是会关闭连接的,如果设置过短则会导致连接不断地重连,连接池失去作用,设置过长则不能解决连接失效的问题,所以连接失效还是要靠那几个test的参数。


removeAbandoned, logAbandoned, abandonWhenPercentageFull和removeAbandonedTimeout

这几个参数是用来控制,如果你从连接池里拿了一个连接,但是很久很久(>=removeAbandonedTimeout)都没归还(比如连接泄露),则这个时候连接池会强制的关闭连接(还受abandonWhenPercentageFull参数控制,这个参数是一个百分比,也就是busy/maxActive如果超过这个百分比并且超时则关闭,默认值是0,所以仅仅检测超时)。

logAbandoned顾名思义,就是会把上面的情况日志记录下来,并且日志里还有这个拿这个连接时候的调用栈,这对于定位连接在哪里泄露的挺有帮助的,但是记录调用栈是有成本的,每次拿连接的时候都会new一个Exception,然后拿到stackTrace,所以建议不要打开这个,即使要打开也不要在生产上打开。而removeAbandoned,现在大家都会用ORM来数据访问,一般不会有连接泄露,但是如果碰到那种长时间执行的SQL也是可以检测出来的,比如有的生成报表的SQL传说有跑个把小时的。


好了,我们已经了解了连接池连接有效性检测,然后还有其他几个参数的介绍。那么我们现在来实际测试一下,连接池有效性测试对我们的数据库和应用会有多少影响?我们会进行一下测试:

配置:

  1. mysql 5.6

  2. server: 4C4G虚拟机

  3. client: 4C4G虚拟机3台

三台机器,每台创建2个线程不断地执行SELECT 1,总QPS在2.2万左右,mysql的响应时间99%为0.000100秒,mysql机器CPU占用为20%左右。每台开启5个线程QPS能达到5.5万以上,响应时间分变化不大,但是出现了少数几个0.100000的查询,msyql机器CPU占用为50%,load达到5。

感觉还行,这么一台配置不咋地的机器能够到5.5万qps,生产上的数据库服务器配置应该在这10倍左右。假设我们一台数据库服务器为10个应用提供服务,每个应用开启100个线程,每个线程拿到1个连接,每30秒执行一次SELECT 1,那么QPS即30左右,这个对数据库几乎是没有任何影响的,而且这个100个线程也是高估的数字。

而且按照响应时间分布,在testOnBorrow时执行健康检查,可能会对进行检查的请求(不是所有请求,并且比例非常非常低)增加一点点延迟(0.000100),但是同时我们提高了可靠性,在出现网络抖动的时候连接可以自动恢复,我觉得这是非常值得的,所以强烈推荐给连接池配置上testOnBorrow。


以上是关于Java开发常用的几个数据库连接池的主要内容,如果未能解决你的问题,请参考以下文章

关于数据库连接池的几个参数的几个补充

数据库连接池的几个参数-以tomcat-jdbc举例

java的几个概念AOPIOCDIDIP工厂模式IOC容器

java中建立数据库连接池,有哪几个步骤

JAVA基础-JDBC二(常用的开源工具)

大话数据库连接池简史,你都用过几个?