银川做淘宝网站的爱链
目录
为什么需要JDBC连接池
Tomcat JDBC Pool 相关参数
1. 基本配置
2. 连接池大小控制
3. 连接验证与测试
4. 空闲连接回收
5. 连接泄漏与超时
Tomcat JDBC Pool 源码分析(tomcat 8.5.3)
DataSourceFactory
DataSource
ConnectionPool
PoolCleaner
对于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包下。
下面是类中与连接池创建相关的部分代码片段:
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类可以进一步看具体的方法内容:
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 方法:
protected void init(PoolConfiguration properties) throws SQLException {poolProperties = properties;//make sure the pool is properly configuredcheckPoolConfiguration(properties);//make space for 10 extra in case we flow over a bitbusy = new LinkedBlockingQueue<>();//busy = new FairBlockingQueue<PooledConnection>();//make space for 10 extra in case we flow over a bitif (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 MBeanif (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 membersPooledConnection[] 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 poolfor (int i = 0; i < initialPool.length; i++) {if (initialPool[i] != null) {try {this.returnConnection(initialPool[i]);}catch(Exception x){/*NOOP*/}} //end if} //for} //catchclosed = false;
}
这个方法中执行了 initializePoolCleaner 这个方法
public void initializePoolCleaner(PoolConfiguration properties) {//if the evictor thread is supposed to run, start it nowif (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,那在任务每次执行时会获取一定数量的链接,判断是否可用,如果不可用就销毁。
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.");}}@Overridepublic 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);}}