DBCP主要是为jdbc提供连接池服务。
2.实现
2.1 Jakarta Commons Pool
DBCP利用了Jakarta Commons Pool来实现连接池管理。下面回顾一下Commons Pool的基本概念
PoolableObjectFactory: 用于管理被池化的对象的产生、激活、挂起、校验和销毁;
ObjectPool: 用于管理要被池化的对象的借出和归还,并通知PoolableObjectFactory完成相应的工作
使用过程:
1. 生成一个要用的PoolableObjectFactory类的实例
- PoolableObjectFactory factory = new PoolableObjectFactorySample();
2. 利用这个PoolableObjectFactory实例为参数,生成一个实现了ObjectPool接口的类(例如StackObjectPool)的实例,作为对象池
- ObjectPool pool = new StackObjectPool(factory);
3. 需要从对象池中取出对象时,调用该对象池的Object borrowObject()方法。
- obj = pool.borrowObject();
4. 需要将对象放回对象池中时,调用该对象池的void returnObject(Object obj)方法。
- pool.returnObject(obj);
- 5. 当不再需要使用一个对象池时,调用该对象池的void close()方法,释放它所占据的资源。
- pool.close();
给一个PoolableObjectFactory 的例子:
- public class PoolableObjectFactorySample implements PoolableObjectFactory {
- public Object makeObject() throws Exception {
- return new Object();
- }
- public void activateObject(Object obj) throws Exception {
- System.err.println("Activating Object " + obj);
- }
- public void passivateObject(Object obj) throws Exception {
- System.err.println("Passivating Object " + obj);
- }
- public boolean validateObject(Object obj) {
- return true;
- }
- public void destroyObject(Object obj) throws Exception {
- System.err.println("Destroying Object " + obj);
- }
- }
通过以上内容,相信大家都了解了common pool的基本用法。
2.2 DBCP
一个普通的jdbc连接大概是这样的:
- public static Connection getConn() throws Exception {
- Class.forName("oracle.jdbc.driver.OracleDriver");
- Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@10.20.143.12:1521:crmp?args[applicationEncoding=UTF-8,databaseEncoding=UTF-8]","eve", "ca");
- return conn;
- }
然后考虑池化,我们能想到的第一步就是把getConn()放到刚刚的PoolableObjectFactory的makeObject里面去。
dbcp其实做的也就比这件事多一点点,先上个图:
datasource和connection都用decorator包装了一下。这也是一个decorator的典型用法。
最主要的一点是PoolableConnection持有了ObjectPool的一个引用,这样才能在connection被使用完后,任意时刻都可以放回池中,而conn的使用者无需关注ObjectPool。
主要的createDatasource过程如下:
- protected synchronized DataSource createDataSource()
- throws SQLException {
- if (closed) {
- throw new SQLException("Data source is closed");
- }
- // Return the pool if we have already created it
- if (dataSource != null) {
- return (dataSource);
- }
- // create factory which returns raw physical connections
- ConnectionFactory driverConnectionFactory = createConnectionFactory();
- // create a pool for our connections
- createConnectionPool();
- // Set up statement pool, if desired
- GenericKeyedObjectPoolFactory statementPoolFactory = null;
- if (isPoolPreparedStatements()) {
- statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
- -1, // unlimited maxActive (per key)
- GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
- 0, // maxWait
- 1, // maxIdle (per key)
- maxOpenPreparedStatements);
- }
- // Set up the poolable connection factory
- createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);
- // Create and return the pooling data source to manage the connections
- createDataSourceInstance();
- try {
- for (int i = 0 ; i < initialSize ; i++) {
- connectionPool.addObject();
- }
- } catch (Exception e) {
- throw new SQLNestedException("Error preloading the connection pool", e);
- }
- return dataSource;
- }
创建连接池(datasource)的过程,通过以上代码就比较清楚了。
下面再补充一点连接失效时的处理:
- 在数据库端正常关闭tcp连接的情况下(发送了FIN包,如oracle的kill process),connection能够检测到。
- 在 数据库端未正常关闭tcp连接的情况下(未发送FIN包,如session失效,比如DB服务器端断掉了连接,oracle中杀掉了 session,mysql中超过最大时常断掉了连),这种情况下应用端的连接会继续可用,知道使用类似insert,commit这样的操作,才会抛出 异常
在应用catach到连接断开的异常时,会释放conn,即调用conn.close,这时一般再检查下,如果发现连接无效,是不会放回连接池的。从而说明,数据库如果切换,应用这边理论上是不用重启就可以切过去的。
- public synchronized void close() throws SQLException {
- boolean isClosed = false;
- try {
- isClosed = isClosed();
- } catch (SQLException e) {
- try {
- _pool.invalidateObject(this);
- } catch (Exception ie) {
- // DO NOTHING the original exception will be rethrown
- }
- throw new SQLNestedException("Cannot close connection (isClosed check failed)", e);
- }
- if (isClosed) {
- throw new SQLException("Already closed.");
- } else {
- try {
- _pool.returnObject(this);
- } catch(SQLException e) {
- throw e;
- } catch(RuntimeException e) {
- throw e;
- } catch(Exception e) {
- throw new SQLNestedException("Cannot close connection (return to pool failed)", e);
- }
- }
- }
2.3 连接池常用配置
先贴一个常用配置:
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="driverClassName" value="driverClassName" />
- <property name="url" value="${db_url}" />
- <property name="username" value="${db_user}" />
- <property name="password" value="${db_passwd}" />
- <property name="maxWait" value="${db_maxWait}" />
- <property name="initialSize" value="2" />
- <property name="maxActive" value="20" />
- <property name="maxIdle" value="20" />
- <property name="minIdle" value="2" />
- <property name="timeBetweenEvictionRunsMillis" value="300000" />
- <property name="testOnBorrow" value="false" />
- <property name="testWhileIdle" value="true" />
- <property name="validationQuery" value="select 1 from dual" />
- </bean>
其实多数配置是common.pool的配置,一提到池,大家就想到最大活动,最大空闲,最小空闲,空闲时间等概念。
这里主要解释一下:maxActive,maxIdle这两个参数
- maxActive:最多活动连接数,连接池是这样用它的
- if(_maxActive < 0 || _numActive < _maxActive) {
- // allow new object to be created
- } else {
- // the pool is exhausted
- switch(_whenExhaustedAction) {
- case WHEN_EXHAUSTED_GROW:
- // allow new object to be created
- break;
- ....
- default:
- throw new IllegalArgumentException("WhenExhaustedAction property " + _whenExhaustedAction + " not recognized.");
- }
- }
- }
- _numActive++;
这里可以看到,每次创建一个对象_numActive++,当创建的对象>=最大值时,新的对象就需要排队等待了。等待如果超过了默认时间(配置中的maxWait)就回抛出
throw new NoSuchElementException("Timeout waiting for idle object");从而出现我们常常看到的连接拿不到的情况。
- maxIdle:在池内的最多空闲连接数,默认配置为8
- if (decrementNumActive) {
- _numActive--;
- }
- if((_maxIdle >= 0) && (_pool.size() >= _maxIdle)) {
- shouldDestroy = true;
- } else if(success) {
- _pool.addLast(new ObjectTimestampPair(obj));
- }
- notifyAll(); // _numActive has changed
- if(shouldDestroy) {
- try {
- _factory.destroyObject(obj);
- } catch(Exception e) {
- // ignored
- }
- }
在return的过程中,我们可以看到,如果_pool.size() >= _maxIdle,连接就会直接消耗,在dbcp的默认配置中,这里的值为8,而maxactive为28,这样最大的问题照成高并发的情况下,可能有一 批连接在归还的时候被销毁,很快大并发上来的时候又会创建. 创建是一个耗时耗资源的事情,而且public synchronized Object borrowObject() 是同步方法,所以在高并发时出现大量线程block,以下是一个20并发查询的测试结果(查询sql本身耗时不多):
上面是maxactive=28,maxidle=8的情况,响应时间和tps也就不用说了。对于调整后的线程还是有一定阻塞的情况,是因为测试使用了ibatis,ibatis的一些原因照成的,详见附录[1]
- timeBetweenEvictionRunsMillis:表示多少时间执行一次逐出。
逐出的大概原理是后台timer每隔timeBetweenEvictionRunsMillis起一个线程,
取出池中的 return Math.min(_numTestsPerEvictionRun, _pool.size());个连接,(默认是3个,可通过参数控制),检查他的空闲时间:
- if ((_minEvictableIdleTimeMillis > 0) && (idleTimeMilis > _minEvictableIdleTimeMillis)) {
- removeObject = true;
- }
如果大于_minEvictableIdleTimeMillis(默认是30分钟,同样可以设置),就销毁。如果配置了testWhileIdle,还 会测试这个连接是否可用,不可用同样也会销毁。注意这里一次只会处理numTestsPerEvictionRun个连接,不会一次全部处理。
- minIdle:最小空闲数
对于这个值,有的推荐配成0,有的推荐配成2,各有说法:
推荐配成0的原因是:
- public void run() {
- try {
- evict();
- } catch(Exception e) {
- // ignored
- }
- try {
- ensureMinIdle();
- } catch(Exception e) {
- // ignored
- }
- }
evict()会检查对象空闲时间,删除不用对象,然后ensureMinIdle()会检查池中是否有大于minIdle个对象,如果没有,就创建对 象。这就可能造成在应用很闲的时候,先将所有对象删除,然后又重新创建两个对象,过会又删除的抖动情况。目前的配置是30分钟逐出,也就是每30分钟会多 一次消耗。
配成2的好处是,应用比较闲的时候,至少不会每次请求都重新建立连接。
附录:
[1] ibatis导致线程阻塞的问题
1. 对于上文的阻塞,通过dump线程发现,是由于ibatis在处理返回结果的时候出现的。
- at com.ibatis.common.beans.ClassInfo.getInstance(ClassInfo.java:362)
- - locked <0x00002aaaae2c9b60> (a java.lang.Class for java.util.HashMap)
- at com.ibatis.common.beans.ComplexBeanProbe.setProperty(ComplexBeanProbe.java:328)
- ….
ClassInfo.getInstance对应的代码是:
- if (cacheEnabled) {
- synchronized (clazz) {
- ClassInfo cache = (ClassInfo) CLASS_INFO_MAP.get(clazz);
- if (cache == null) {
- cache = new ClassInfo(clazz);
- CLASS_INFO_MAP.put(clazz, cache);
- }
- return cache;
- }
上面这段代码的目的是什么呢?ibatis在取得查询结果后,需要通过反射将driver返回的 object[]设置到resultMap中指定的对象(通过set**方法)。出于性能考虑(反射调用getMethod相对耗时很多),ibatis 使用class作为key,将所用的set方法放到map中缓存方便反射调用。为了避免被并发多次初始化,使用了同步。但是在高并发的情况下(而且我们的 测试是取得同一个对象的数据),该同步导致阻塞,也就是测试报告中新配置线程block的地方。
解释了上面所有的问题后,还有一个疑问,除了第一次调用,每次调用这个函数只会操作
- ClassInfo cache = (ClassInfo) CLASS_INFO_MAP.get(clazz)
这个方法虽然有同步,但执行是非常快的,为什么会造成多个线程阻塞呢?
调研发现是因为这个方法在设置每一个属性的时候会被调用一次,假设一个查询返回20条记录,一条记录有10个属性,那么一个结果拼装至少会调用 200次,由于释放锁只是修改内存结构,并不会切换线程(java中block的线程在linux中是sleep状态,处于等待队列中),因此一般情况 下,当前持有锁的线程会一直反复持有这个锁并连续调用200次,然后其他线程重新竞争的时候,又只有一个线程会竞争到锁,这样运气最坏的线程就会等待 (19*200)次操作,虽然每次时间很短,加起来也有一定的阻塞情况了。
相关推荐
标题中的"commons-pool-1.3.jar"和"commons-dbcp-1.2.2.jar"是两个在Java开发中常用的开源库,主要用于数据库连接池管理。它们都是Apache Commons项目的一部分,旨在提高数据库访问效率,减少数据库资源的消耗。 ...
Apache Commons DBCP(Database Connection Pool)和Apache Commons Pool是两个在Java开发中广泛使用的开源库,主要用于数据库连接管理和对象池服务。这两个JAR文件,`commons-dbcp-1.4.jar` 和 `commons-pool-1.5.6...
标题中的"commons-dbcp-1.4&&commons-pool-1.3.jar"指的是Apache Commons的两个重要组件:DBCP(Database Connection Pool)1.4版本和Pool 1.3版本。这两个组件在Java Web开发中扮演着关键角色,尤其在数据库连接...
在Java开发中,数据库连接管理是一项关键任务,而Apache Commons DBCP(Database Connection Pool)是广泛使用的数据库连接池实现之一。本主题将详细解析标题和描述中提及的三个核心库:`commons-dbcp-1.2.1.jar`、`...
赠送jar包:commons-pool2-2.10.0.jar; 赠送原API文档:commons-pool2-2.10.0-javadoc.jar; 赠送源代码:commons-pool2-2.10.0-sources.jar; 赠送Maven依赖信息文件:commons-pool2-2.10.0.pom; 包含翻译后的API...
在本话题中,我们将探讨两个重要的开源库:`commons-dbcp`和`commons-pool`,以及它们在配置DataSource时的作用,特别是在Tomcat服务器中的应用,以及与Struts框架配置的相关性。 `commons-dbcp`(Apache Commons ...
spring-webmvc-5.2.6.RELEASE.jar、spring-web-5.2.6.RELEASE.jar、spring-jdbc-5.2.6.RELEASE.jar、mysql-connector-java-5.1.37-bin.jar、mybatis-spring-1.3.1.jar、commons-dbcp2-2.4.0.jar 、commons-pool2-...
DBCP(DataBase Connection Pool)是 apache common上的一个 java 连接池项目,也是 tomcat 使用的连接池组件,依赖 于Jakarta commons-pool 对象池机制,DBCP可以直接的在应用程序中使用。 使用DBCP会用到commons-...
总的来说,"commons-pool&commons-dbcp"是Java开发中的重要工具,它们在优化数据库操作和提升系统性能方面发挥着关键作用。无论是在小型项目还是大型企业级应用中,合理使用这两者都可以带来明显的性能提升和资源...
commons-pool-1.5.4 RELEASE NOTES This is a patch release containing a fix for POOL-152, a regression introduced in version 1.5. Changes in this version include: Fixed Bugs: o POOL-152: ...
DBCP(DataBase Connection Pool)是 apache common上的一个 java 连接池项目,也是 tomcat 使用的连接池组件,依赖 于Jakarta commons-pool 对象池机制,DBCP可以直接的在应用程序中使用。 使用DBCP会用到commons-...
标题 "commons-dbcp-2-2.50+commons-pool2-2.6.0+commons-logging-1.2" 涉及到的是Java开发中的几个关键组件,它们是Apache Commons项目的一部分,主要用于数据库连接管理和日志记录。Apache Commons是一个包含多个...
本篇文章将深入解析标题中的三个关键库:`commons-collections.jar`、`commons-pool.jar`和`commons-dbcp-1.2.1.jar`,以及它们在Java应用中的重要性。 首先,`commons-collections.jar`是Apache Commons ...
标题中的三个文件——"commons-collections-3.1.jar","commons-dbcp-1.2.1.jar",和"commons-pool-1.2.jar",是Apache Commons项目的一部分,它们在Java应用程序中扮演着重要角色。Apache Commons是一个为Java开发...
Apache Commons DBCP 1.4 和 Apache Commons Pool 1.6 是两个在Java应用程序中广泛使用的开源库,主要用于数据库连接池的管理。这两个库在处理大量并发数据库访问时能够提高性能并优化资源利用。 Apache Commons ...
Apache Commons DBCP 1.4 和 Commons Pool 1.5.6 是两个在Java开发中广泛使用的开源库,主要用于数据库连接池管理。这两个jar包是处理数据库连接管理和资源优化的关键组件,尤其在大型Web应用和服务中,它们扮演着至...
DBCP(Database Connection Pool)是Apache Jakarta项目中的一个子项目,它利用了commons-pool对象池机制来实现数据库连接的复用,从而减少创建和释放数据库连接时的开销。Tomcat,一个广泛使用的Java应用服务器,...
在你提供的标题和描述中提到了三个关键的jar包:`commons-collections-3.1.jar`、`commons-dbcp-1.2.2.jar`和`commons-pool.jar`,这些正是在使用JDBC时常见的辅助库。 1. **Apache Commons Collections** (`...
这些文件是Apache Commons项目中的三个重要组件:Commons Collections、Commons DBCP(数据库连接池)和Commons Pool。Apache Commons是Java编程语言中一个非常重要的库集合,提供了大量实用工具类和算法,帮助...
Apache Commons DBCP(数据库连接池)和Apache Commons Pool是两个在Java开发中广泛使用的开源库,主要用于管理和优化数据库连接的使用。这两个组件是高效、可靠的数据库操作背后的关键技术。 Apache Commons Pool...