数据库最大连接池溢出是在系统运行中比较常见的一个问题,在开发中,可以通过设置最大连接池的各位为1或者2,就能在开发的时候发现数据库连接没有被释放的情况。不过这个小技巧在hibernate和sping等框架大量使用之后就没什么用了。
数据库连接池溢出的源代码:
package test.ffm83.commons.dbcp;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.lang.StringUtils;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.util.Properties;
/* 通过dbcp连接oracle数据库,模拟连接池溢出
* 使用1.4版本实现
* @author 范芳铭
* */
public
classDbcpUsageParamA {
private
static BasicDataSource dataSource =
null;
public DbcpUsageParamA() {
}
public
static void init() {
if (dataSource !=
null) {
try {
dataSource.close();
}catch(Exception e) {
e.printStackTrace();
}
dataSource =
null;
}
try {
Propertiesp = newProperties();
p.setProperty("driverClassName","oracle.jdbc.driver.OracleDriver");
p.setProperty("url",
"jdbc:oracle:thin:@192.168.19.24:1521:fanfangming");
p.setProperty("password","ffm");
p.setProperty("username","ffm");
p.setProperty("maxActive","2");
//最大连接数
p.setProperty("maxIdle","1");
p.setProperty("maxWait","10");
dataSource =(BasicDataSource) BasicDataSourceFactory
.createDataSource(p);
}catch(Exception e) {
e.printStackTrace();
}
}
public
static synchronized ConnectiongetConnection()
throwsSQLException {
if (dataSource ==
null) {
init();
}
Connectionconn = null;
if (dataSource !=
null) {
conn= dataSource.getConnection();
}
return conn;
}
public
static void main(String[] args)
throws Exception {
getConAndNotClose();
getConAndNotClose();
getConAndNotClose();
}
//本方法使用数据库连接但是不释放,用来模拟连接池溢出情况
private
static void getConAndNotClose()
throws Exception{
Connectioncon = null;
try {
con= DbcpUsageParamA.getConnection();
Stringsql = " select sysdate from dual ";
PreparedStatementps = con.prepareStatement(sql);
ResultSetrs = ps.executeQuery();
while (rs.next()) {
Stringvalue = rs.getString("sysdate");
System.out.println(StringUtils.center(value+",数据库连接成功", 50,
"-"));
}
}catch(Exception e) {
e.printStackTrace();
}
//注意,这里没有关闭数据库连接池,实际代码不要这么写。
}
}
运行结果:
----------2014-12-16 12:47:49.0,数据库连接成功-----------
----------2014-12-16 12:47:49.0,数据库连接成功-----------
org.apache.commons.dbcp.SQLNestedException: Cannot get a connection, pool error Timeout waiting for idle object
atorg.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:114)
….
其他几个参数也可以这样去测试。
在配置时,主要难以理解的主要有:removeAbandoned 、logAbandoned、removeAbandonedTimeout、maxWait这四个参数,设置了rmoveAbandoned=true那么在getNumActive()快要到getMaxActive()的时候,系统会进行无效的Connection的回收,回收的Connection为removeAbandonedTimeout(默认300秒)中设置的秒数后没有使用的Connection。
如果开启"removeAbandoned",那么连接在被认为泄露时可能被池回收. 这个机制在(getNumIdle() < 2) and (getNumActive()> getMaxActive() - 3)时被触发.
举例当maxActive=20,活动连接为18,空闲连接为1时可以触发"removeAbandoned".
看了这么多,先看一个例子,关于removeAbandoned。
package test.ffm83.commons.dbcp;
import org.apache.commons.dbcp.BasicDataSource;
importorg.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.lang.StringUtils;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Connection;
import java.util.Properties;
/* 通过dbcp连接oracle数据库,体验连不释放下的removeAbandoned等参数
* 使用1.4版本实现
* @author 范芳铭
* */
public class DbcpAbandone {
privatestatic BasicDataSource dataSource = null;
publicDbcpAbandone() {
}
publicstatic void init() {
if(dataSource != null) {
try{
dataSource.close();
}catch (Exception e) {
e.printStackTrace();
}
dataSource= null;
}
try{
Propertiesp = new Properties();
p.setProperty("driverClassName","oracle.jdbc.driver.OracleDriver");
p.setProperty("url","jdbc:oracle:thin:@192.168.1.1:1521:fanfangming");
p.setProperty("password","ffm");
p.setProperty("username","ffm");
p.setProperty("maxActive","6"); //
p.setProperty("maxIdle","3");
p.setProperty("maxWait","10");
p.setProperty("removeAbandoned","true");//移除不用的连接
p.setProperty("removeAbandonedTimeout","8");
p.setProperty("logAbandoned","true");
dataSource= (BasicDataSource) BasicDataSourceFactory
.createDataSource(p);
}catch (Exception e) {
e.printStackTrace();
}
}
publicstatic synchronized Connection getConnection() throws SQLException {
if(dataSource == null) {
init();
}
Connectionconn = null;
if(dataSource != null) {
conn= dataSource.getConnection();
}
returnconn;
}
publicstatic void main(String[] args) throws Exception {
for(inti=0;i < 10 ; i++){
getConAndNotClose(i+1);
}
}
//本方法使用数据库连接但是不释放,用来模拟连接池溢出情况
privatestatic void getConAndNotClose(int id) throws Exception{
Connectioncon = null;
try{
con= DbcpAbandone.getConnection();
Stringsql = " select sysdate from dual ";
PreparedStatementps = con.prepareStatement(sql);
ResultSetrs = ps.executeQuery();
while(rs.next()) {
Stringvalue = rs.getString("sysdate");
Thread.sleep(6* 1000);
System.out.println(StringUtils.center(value+","+id+"数据库连接成功", 50, "-"));
}
}catch (Exception e) {
e.printStackTrace();
}
//注意,这里没有关闭数据库连接池,实际代码不要这么写。
}
}
运行结果如下:
----------2014-12-16 14:12:45.0,1数据库连接成功----------
----------2014-12-16 14:12:53.0,2数据库连接成功----------
----------2014-12-16 14:13:00.0,3数据库连接成功----------
----------2014-12-16 14:13:06.0,4数据库连接成功----------
----------2014-12-16 14:13:13.0,5数据库连接成功----------
----------2014-12-16 14:13:20.0,6数据库连接成功----------
----------2014-12-16 14:13:27.0,7数据库连接成功----------
----------2014-12-16 14:13:33.0,8数据库连接成功----------
----------2014-12-16 14:13:41.0,9数据库连接成功----------
---------2014-12-1614:13:47.0,10数据库连接成功----------
10个数据库连接都生效了,简直太神奇了,removeAbandoned确实回收了部分连接,所以10个数据库都能够连接,那么有了removeAbandoned 参数是不是就以后能解决连接池溢出的问题呢,答案是只能缓解连接池问题,不能彻底解决,而且要和removeAbandonedTimeout参数配套使用。
removeAbandonedTimeout 参数要和maxWait(业务逻辑)参数配套使用。
在上面的代码中,有一段代码为:Thread.sleep(6 * 1000);
如果把这段代码注释掉,马上就能看到6个数据库连接之后,异常马上就来了。
我们把这段的异常取下来:
异常太长了就不直接贴了。
我们在代码中加了p.setProperty("logAbandoned","true"); 如果把
/* p.setProperty("removeAbandoned","true");//移除不用的连接
p.setProperty("removeAbandonedTimeout","8");
p.setProperty("logAbandoned","true");*/
之后的异常也取下来,保持之后看差异在哪里。
人笨只好用力做,把两端异常拿下来仔细分析,有那么一点点差异。
Caused by: java.util.NoSuchElementException:Timeout waiting for idle object
atorg.apache.commons.pool.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:1174)
atorg.apache.commons.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:79)
atorg.apache.commons.dbcp.PoolingDataSource.getConnection(PoolingDataSource.java:106)
... 4 more
org.apache.commons.dbcp.AbandonedObjectPool.borrowObject(AbandonedObjectPool.java:79)有一点差异。
但是根据网上搜索到的资料,“logAbandoned=true的话,将会在回收事件后,在log中打印出回收Connection的错误信息,包括在哪个地方用了Connection却忘记关闭了,在调试的时候很有用。”
尝试了一些方法和时间,没有找到这个要怎么测试,只能先放下,等以后遇到了这个问题再来记录。
经过一些测试后发现,这个日志不是一出现数据库连接池没有关闭就会被打印出来,需要一定的触发调整。在“Apache DBCP连接数据库异常重连”章节中的用例,出现了logAbandoned 打印出来的日志,养在深闺人未识啊,特摘录下来:这个日志,比较明确指出了那些地方数据库连接池没有关闭。
attest.ffm83.commons.dbcp.DbcpErrorConnection.main(DbcpErrorConnection.java:71)
org.apache.commons.dbcp.AbandonedTrace$AbandonedObjectException: DBCP object created 2014-12-16 15:41:53 by the following code was neverclosed:
atorg.apache.commons.dbcp.AbandonedTrace.init(AbandonedTrace.java:90)
atorg.apache.commons.dbcp.AbandonedTrace.<init>(AbandonedTrace.java:73)
atorg.apache.commons.dbcp.DelegatingStatement.<init>(DelegatingStatement.java:61)
atorg.apache.commons.dbcp.DelegatingPreparedStatement.<init>(DelegatingPreparedStatement.java:70)
atorg.apache.commons.dbcp.DelegatingConnection.prepareStatement(DelegatingConnection.java:281)