目录
[Tomcat JDBC Pool 相关参数](#Tomcat JDBC Pool 相关参数)
[1. 基本配置](#1. 基本配置)
[2. 连接池大小控制](#2. 连接池大小控制)
[3. 连接验证与测试](#3. 连接验证与测试)
[4. 空闲连接回收](#4. 空闲连接回收)
[5. 连接泄漏与超时](#5. 连接泄漏与超时)
[Tomcat JDBC Pool 源码分析(tomcat 8.5.3)](#Tomcat JDBC Pool 源码分析(tomcat 8.5.3))
对于JAVA开发者来说,JDBC肯定都比较熟悉,它其实是Java提供了一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可,不同的数据库厂商需要针对这套接口提供不同的实现。
为什么需要JDBC连接池
使用传统的jdbc做开发时,一方面频,繁的进行数据库连接操作将占用很多的系统资源,另一方面,每次拿到connection使用完都要主动去断开,那如果程序出现异常导致链接没有关闭,就会发生内存会泄露,因此可以发现使用jdbc开发时在连接管理上是存在风险的。另外,用传统的连接方法无法控制程序中创建的连接数量,如果连接过多也会导致内存泄漏。
那JDBC连接池的作用,就是可以避免频繁创建连接,帮我们做连接的生命周期管理,以及在请求较大时控制应用内连接的数量防止出现崩溃。
目前java生态里主流的JDBC连接池有HikariCP,Druid等,还有本文写的Tomcat JDBC Pool。
Tomcat JDBC Pool 相关参数
下面列举了一些基本参数和连接管理方面检测机制相关的参数
1. 基本配置
driverClassName
数据库驱动类名(如com.mysql.jdbc.Driver
)。url
数据库连接 URL(如jdbc:mysql://localhost:3306/mydb
)。username
数据库用户名。password
数据库密码。
2. 连接池大小控制
initialSize
连接池初始化时创建的连接数(默认0
)。maxActive
最大活跃连接数(默认100
)。maxIdle
最大空闲连接数(默认与maxActive
相同)。minIdle
最小空闲连接数(默认0
)。maxWait
获取连接的最大等待时间(毫秒,默认30000
,超时抛异常)。
3. 连接验证与测试
testOnBorrow
从池中获取连接时是否验证有效性(默认false
)。testOnReturn
归还连接时是否验证有效性(默认false
)。testWhileIdle
空闲时是否定期验证连接(默认false
)。validationQuery
用于验证连接的 SQL 查询(如SELECT 1
)。validationQueryTimeout
验证查询的超时时间(秒,默认-1
表示无限制)。
4. 空闲连接回收
timeBetweenEvictionRunsMillis
空闲连接检查线程的运行间隔(毫秒,默认5000
)。minEvictableIdleTimeMillis
连接被回收前的最小空闲时间(默认60000
)。numTestsPerEvictionRun
每次检查回收的空闲连接数(默认使用所有空闲连接)。
5. 连接泄漏与超时
removeAbandoned
是否自动回收泄露的连接(默认false
)。removeAbandonedTimeout
连接被标记为泄露的时间阈值(秒,默认60
)。logAbandoned
是否记录泄露连接的堆栈信息(默认false
)。
Tomcat JDBC Pool 源码分析(tomcat 8.5.3)
下面简单分析下tomcat连接池是如何创建,又是如何实现连接检测的。
DataSourceFactory
DataSourceFactory是Tomcat JDBC Pool 中用来创建DataSource的工厂类,这个类在org.apache.tomcat.jdbc.pool包下。
下面是类中与连接池创建相关的部分代码片段:
java
public DataSource createDataSource(Properties properties) throws Exception {
return createDataSource(properties,null,false);
}
public DataSource createDataSource(Properties properties,Context context, boolean XA) throws Exception {
PoolConfiguration poolProperties = DataSourceFactory.parsePoolProperties(properties);
if (poolProperties.getDataSourceJNDI()!=null && poolProperties.getDataSource()==null) {
performJNDILookup(context, poolProperties);
}
org.apache.tomcat.jdbc.pool.DataSource dataSource = XA? new org.apache.tomcat.jdbc.pool.XADataSource(poolProperties) : new org.apache.tomcat.jdbc.pool.DataSource(poolProperties);
//创建连接池
dataSource.createPool();
return dataSource;
}
可以看到 DataSourceFactory.createDataSource方法返回一个DataSource实例,DataSource初始化后调用了 createPool 方法来初始化连接池相关的组件。
DataSource
DataSource是内置了数据源连接池 ConnectionPool 的对象,基于JDBC标准对外提供了一些方法。
Tomcat JDBC Pool 中支持两种DataSource类型,一种是普通DataSource,另一种是XADataSourced。
ConnectionPool
ConnectionPool 就是连接池最核心的资源对象,但是他不会直接对外暴露,而是被包装在上面说的DataSource中,开发过程中通常先获取到的是DataSource对象实例。
了解了DataSource 和 ConnectionPool 后,继续来看DataSourceFactory.createDataSource方法中的createPool方法逻辑,这个方法在DataSource或XADataSourced共同的父类DataSourceProxy,打开DataSourceProxy类可以进一步看具体的方法内容:
java
public ConnectionPool createPool() throws SQLException {
if (pool != null) {
return pool;
} else {
return pCreatePool();
}
}
/**
* Sets up the connection pool, by creating a pooling driver.
*/
private synchronized ConnectionPool pCreatePool() throws SQLException {
if (pool != null) {
return pool;
} else {
pool = new ConnectionPool(poolProperties);
return pool;
}
}
public ConnectionPool(PoolConfiguration prop) throws SQLException {
init(prop);
}
createPool 方法最终后调用到 init 方法:
java
protected void init(PoolConfiguration properties) throws SQLException {
poolProperties = properties;
//make sure the pool is properly configured
checkPoolConfiguration(properties);
//make space for 10 extra in case we flow over a bit
busy = new LinkedBlockingQueue<>();
//busy = new FairBlockingQueue<PooledConnection>();
//make space for 10 extra in case we flow over a bit
if (properties.isFairQueue()) {
idle = new FairBlockingQueue<>();
//idle = new MultiLockFairBlockingQueue<PooledConnection>();
//idle = new LinkedTransferQueue<PooledConnection>();
//idle = new ArrayBlockingQueue<PooledConnection>(properties.getMaxActive(),false);
} else {
idle = new LinkedBlockingQueue<>();
}
initializePoolCleaner(properties);
//create JMX MBean
if (this.getPoolProperties().isJmxEnabled()) createMBean();
//Parse and create an initial set of interceptors. Letting them know the pool has started.
//These interceptors will not get any connection.
PoolProperties.InterceptorDefinition[] proxies = getPoolProperties().getJdbcInterceptorsAsArray();
for (int i=0; i<proxies.length; i++) {
try {
if (log.isDebugEnabled()) {
log.debug("Creating interceptor instance of class:"+proxies[i].getInterceptorClass());
}
JdbcInterceptor interceptor = proxies[i].getInterceptorClass().getConstructor().newInstance();
interceptor.setProperties(proxies[i].getProperties());
interceptor.poolStarted(this);
}catch (Exception x) {
log.error("Unable to inform interceptor of pool start.",x);
if (jmxPool!=null) jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
close(true);
SQLException ex = new SQLException();
ex.initCause(x);
throw ex;
}
}
//initialize the pool with its initial set of members
PooledConnection[] initialPool = new PooledConnection[poolProperties.getInitialSize()];
try {
for (int i = 0; i < initialPool.length; i++) {
initialPool[i] = this.borrowConnection(0, null, null); //don't wait, should be no contention
} //for
} catch (SQLException x) {
log.error("Unable to create initial connections of pool.", x);
if (!poolProperties.isIgnoreExceptionOnPreLoad()) {
if (jmxPool!=null) jmxPool.notify(org.apache.tomcat.jdbc.pool.jmx.ConnectionPool.NOTIFY_INIT, getStackTrace(x));
close(true);
throw x;
}
} finally {
//return the members as idle to the pool
for (int i = 0; i < initialPool.length; i++) {
if (initialPool[i] != null) {
try {this.returnConnection(initialPool[i]);}catch(Exception x){/*NOOP*/}
} //end if
} //for
} //catch
closed = false;
}
这个方法中执行了 initializePoolCleaner 这个方法
java
public void initializePoolCleaner(PoolConfiguration properties) {
//if the evictor thread is supposed to run, start it now
if (properties.isPoolSweeperEnabled()) {
poolCleaner = new PoolCleaner(this, properties.getTimeBetweenEvictionRunsMillis());
poolCleaner.start();
} //end if
}
initializePoolCleaner 方法的逻辑是初始化一个PoolCleaner,然后启动它。
注意初始化这个构造器,传入了一个关键参数:
betweenEvictionRunsMillis
这个参数用来指定对于空闲链接检测的任务多久执行一次,默认值是5000ms,默认值实在 org.apache.tomcat.jdbc.pool.PoolProperties 类中定义的固定值,如果外部设置了会覆盖。
PoolCleaner
PoolCleaner 是 ConnectionPool 中的一个内部静态类。这个类继承TimerTask ,实例化后其实是一个线程任务,Tomcat JDBC Pool 就是通过启动这个定时任务来实现连接池中的各种检测功能。
run方法中就是检测任务的具体逻辑,包括:
1.对最小连接数的维护,数量少于最小连接数则新建补充,数量多了则销毁多余的。
2.对空闲链接的检测,如果 testWhileIdle 参数设置为true,那在任务每次执行时会获取一定数量的链接,判断是否可用,如果不可用就销毁。
java
protected static class PoolCleaner extends TimerTask {
protected WeakReference<ConnectionPool> pool;
protected long sleepTime;
PoolCleaner(ConnectionPool pool, long sleepTime) {
this.pool = new WeakReference<>(pool);
this.sleepTime = sleepTime;
if (sleepTime <= 0) {
log.warn("Database connection pool evicter thread interval is set to 0, defaulting to 30 seconds");
this.sleepTime = 1000 * 30;
} else if (sleepTime < 1000) {
log.warn("Database connection pool evicter thread interval is set to lower than 1 second.");
}
}
@Override
public void run() {
ConnectionPool pool = this.pool.get();
if (pool == null) {
stopRunning();
} else if (!pool.isClosed()) {
try {
if (pool.getPoolProperties().isRemoveAbandoned()
|| pool.getPoolProperties().getSuspectTimeout() > 0)
pool.checkAbandoned();
if (pool.getPoolProperties().getMinIdle() < pool.idle
.size())
pool.checkIdle();
if (pool.getPoolProperties().isTestWhileIdle())
pool.testAllIdle();
} catch (Exception x) {
log.error("", x);
}
}
}
public void start() {
registerCleaner(this);
}
public void stopRunning() {
unregisterCleaner(this);
}
}