Druid
Druid是java语言中最好的数据库连接池之一,经常在项目中使用。本文基于目前最新的版本1.1.6,探索下Druid连接池的实现原理。因为涉及到各种复杂逻辑,暂未想到比较好的图示来描述源码,因此本文更多的是代码+文字,大家见谅哈
首先,提出几个问题,我们带着这几个疑问去学习druid连接池源码
druid常用的参数
druid如何创建连接的
connection是如何被回收利用的
druid如何维护、清理连接
druid怎么解决并发情况下获取、创建、关闭连接的问题
DruidDataSource基本原理
总体来说,DruidDataSource的原理很简单,就是ReentrantLock + Condition,经典的生产者/消费者模式的实际应用,伪代码如下
public class DruidDataSource {
// 默认使用公平锁
private ReentrantLock lock = new ReentrantLock(false);
private Condition notEmpty = lock.newCondition();
private Condition empty = lock.newCondition();
public Connection getConnection() {
lock.lock;
if ( "池中有连接" ) {
// 从池中取出连接
} else {
empty.signal(); // 唤醒创建连接的线程进行创建连接
notEmpty.await(); // 等创建连接的线程进行通知
}
lock.unlock();
}
// 调用Connection.close(),会调用DruidDataSource的recycle方法
public void recycle( Connection conn ) {
// 对连接进行检测
lock.lock;
putLast();
lock.unlock();
}
public CreateThread extends Thread {
public void run() {
for (;;) {
lock.lockInterruptibly();
if ( "池中有可用连接" ) {
empty.await(); // 进入waiting,等等获取连接的线程唤醒
} else {
//TODO 创建连接
notEmpty.signal(); // 唤醒等等连接的线程
}
lock.unlock();
}
}
}
public DestoryThread extends Thread {
public void run() {
for (;;) {
lock.lockInterruptibly();
// 清理连接池内的过期连接
// (可选)清理从连接池拿走,正在使用的连接
lock.unlock();
Thread.sleep( times );
}
}
}
}
DruidDataSource内部结构
首先来看下继承关系,DruidDataSource继承至DruidAbstractDataSource(内部定义了各种参数和常量)
javax.naming.Referenceable:对JNDI支持
javax.management.MBeanRegistration:对jmx的支持,用于对内部数据进行jmx监控
com.alibaba.druid.pool.DruidDataSourceMBean:druid用于对DruidDataSource监控而抽象的接口
构造方法
构造方法可以指定DruidDataSource是否使用公平锁,默认使用公平锁,这个重入锁用于解决create、destory、get链接的并发问题。因此,如果使用公平锁,应用程序调用getConnection()是根据请求的先后顺序依次返回Connection。如果我们指定为非公平锁,则允许后来居上,后面到达的请求反而可能先获得Connection
ReentrantLock lock:创建Connection的线程、销毁的线程、获取连接的线程,需要获得重入锁,才可以对内部数据进行操作,当然只是在需要的地方加锁,后面会具体分析
Condition notEmpty:如果获取连接的线程发现连接池空了,一方面会唤醒empty,另外一方面自己会调用notEmpty.await()进入等待,由CreateConnectionThread唤醒,或者其他线程释放连接时唤醒
Condition empty:CreateConnectionThread只有在连接池不够用的情况下才会创建,否则调用empty.await()挂起线程。如果获取连接的线程发现连接全部被拿走了,则会调用empty.signal()唤醒CreateConnectionThread创建连接,同时也会调用notEmpty.await()进入等待
public DruidDataSource(){
this(false);
}
public DruidDataSource(boolean fairLock){
// 调用父类DruidAbstractDataSource,用于初始化Lock和Condition
super(fairLock);
// 使用系统参数对DruidDataSource进行设值
configFromPropety(System.getProperties());
}
public class DruidAbstractDataSource {
// other code...
protected ReentrantLock lock;
protected Condition notEmpty;
protected Condition empty;
public DruidAbstractDataSource(boolean lockFair){
lock = new ReentrantLock(lockFair);
notEmpty = lock.newCondition();
empty = lock.newCondition();
}
}
初始化
每次获取Connection都会调用init,内部使用inited标识DataSource是否已经初始化OK,init主要完成以下工作:
- 初始化Filter,这些Filter可以嵌入各个环节,包括创建、销毁链接,提交、回滚事务等等,比如常见的ConfigFilter(支持密码加密)、StatFilter(监控,比如打印慢查询SQL)、LogFilter(打印各种日志)。依次从wrapper jdbcUrl、setFilters指定的Filters、SPI加载Filter并进行初始化
加载数据库驱动Driver
根据不同的数据库,实例化ExceptionSorter,主要的api就是isExceptionFatal(SQLException e),用于判断是否是Fatal级别的异常
初始化连接检测器,不同数据库的实现不一样,比如mysql是调用pingInternal检测连接是否OK。ValidConnectionChecker在获取连接、回收连接的时候会用到
初始化JdbcDataSourceStat,主要目的是做监控
初始化connections、evictConnections、keepAliveConnections数组,分别用于存放可被获取的连接池、待清理的连接池、存活的连接池,数组的大小都是maxActive
初始化initialSize个Connection
开启LogStatsThread线程,用于定期打印DruidDataSource的一些数据,默认是不开启的,需要开启的话只需要设置timeBetweenLogStatsMillis指定打印的时间周期,log步骤需要获取主锁,建议时间不要设得太短
创建CreateConnectionThread线程,druid内部默认使用一个线程异步地创建连接,当然可以指定createScheduler线程池,开启多个线程创建连接,但是请把keepAlive设为true,否则不会开启异步线程创建连接
创建DestroyConnectionThread线程,定期扫描连接池内过期的连接,如果想对连接池外面正在使用的连接也进行清理的话,需要指定removeAbandoned为true,清理线程会判断连接是否正在使用,是否超过了清理时间而进行清理
初始化工作还是有点繁锁,但也比较关键吧。有些童鞋喜欢设置各种参数,需要注意下。比如期望开启多个创建连接的线程,可能只指定一个createScheduler线程池,然而可能并没有达到预期的效果,因为keepAlive默认是false的,不会启动多线程创建连接
public void init() throws SQLException {
// 由volatite修改,每次获取连接,也会调用init()
if (inited) {
return;
}
// 可以被中断的lock操作
final ReentrantLock lock = this.lock;
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
throw new SQLException("interrupt", e);
}
boolean init = false;
try {
if (inited) {
return;
}
// 获取程序的调用栈,标注由哪个函数调用的init方法
initStackTrace = Utils.toString(Thread.currentThread().getStackTrace());
this.id = DruidDriver.createDataSourceId();
if (this.id > 1) {
long delta = (this.id - 1) * 100000;
this.connectionIdSeed.addAndGet(delta);
this.statementIdSeed.addAndGet(delta);
this.resultSetIdSeed.addAndGet(delta);
this.transactionIdSeed.addAndGet(delta);
}
// 这个地方用于支持wrapper jdbcUrl,可以使用filter=xxx,jmx=xxx这种方式,具体请参考官方文档
// 比如使用jdbc:wrap-jdbc:filters=stat,log4j:jdbc:mysql:xxx这种方式可以配置Filter用于收集监控信息
// 具体请参考源码 DruidDriver.parseConfig(jdbcUrl, null)
if (this.jdbcUrl != null) {
this.jdbcUrl = this.jdbcUrl.trim();
initFromWrapDriverUrl();
}
// 初始化Filter
for (Filter filter : filters) {
filter.init(this);
}
// 各种参数校验,other code......
// 从SPI中加载Filter,如果前面加载的Filter不存在则还需要进行初始化,可以指定系统参数druid.load.spifilter.skip=false禁用该SPI
initFromSPIServiceLoader();
// 初始化Driver实例
if (this.driver == null) {
if (this.driverClass == null || this.driverClass.isEmpty()) {
this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);
}
// 用于支持com.alibaba.druid.mock.MockDriver
if (MockDriver.class.getName().equals(driverClass)) {
driver = MockDriver.instance;
} else {
driver = JdbcUtils.createDriver(driverClassLoader, driverClass);
}
} else {
if (this.driverClass == null) {
this.driverClass = driver.getClass().getName();
}
}
// 针对不同数据库的一些校验逻辑
initCheck();
// 初始化ExceptionSorter
initExceptionSorter();
// 初始化连接检测器,不同数据库的实现不一样,比如mysql是调用pingInternal检测连接是否OK
initValidConnectionChecker();
validationQueryCheck();
// 初始化DataSource的监控器
if (isUseGlobalDataSourceStat()) {
dataSourceStat = JdbcDataSourceStat.getGlobal();
if (dataSourceStat == null) {
dataSourceStat = new JdbcDataSourceStat("Global", "Global", this.dbType);
JdbcDataSourceStat.setGlobal(dataSourceStat);
}
if (dataSourceStat.getDbType() == null) {
dataSourceStat.setDbType(this.dbType);
}
} else {
dataSourceStat = new JdbcDataSourceStat(this.name, this.jdbcUrl, this.dbType, this.connectProperties);
}
dataSourceStat.setResetStatEnable(this.resetStatEnable);
// 连接池中可用的连接(未被拿走),内部会维护一个poolingCount值代表队列中剩余可用的连接,每次从末尾拿走连接
connections = new DruidConnectionHolder[maxActive];
// 失效、过期的连接,会暂时放在这个数组里面
evictConnections = new DruidConnectionHolder[maxActive];
// 销毁线程会检测线程,如果检测存活的线程放暂时放在这里,然后统一放入connections中
keepAliveConnections = new DruidConnectionHolder[maxActive];
SQLException connectError = null;
// 是否异常初始化连接池,如果不是的话,则初始化指定的initialSize个连接数
boolean asyncInit = this.asyncInit && createScheduler == null;
if (!asyncInit) {
try {
// init connections
for (int i = 0, size = getInitialSize(); i < size; ++i) {
PhysicalConnectionInfo pyConnectInfo = createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
connections[poolingCount] = holder;
incrementPoolingCount();
}
if (poolingCount > 0) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
} catch (SQLException ex) {
LOG.error("init datasource error, url: " + this.getUrl(), ex);
connectError = ex;
}
}
/** 初始化必须的线程 */
createAndLogThread(); // 开启logger日志打印的线程
createAndStartCreatorThread(); // 开启创建连接的线程,如果线程池createScheduler为null,则开启单个创建连接的线程
createAndStartDestroyThread(); // 开启销毁过期连接的线程
// 等待前面的线程初始化完成
initedLatch.await();
init = true;
initedTime = new Date();
// 初始DataSource注册到jmx中
registerMbean();
if (connectError != null && poolingCount == 0) {
throw connectError;
}
// keepAlive为true时,并且createScheduler不为null,则初始化minIdle个线程用于创建连接
if (keepAlive) {
// async fill to minIdle
if (createScheduler != null) {
for (int i = 0; i < minIdle; ++i) {
createTaskCount++;
CreateConnectionTask task = new CreateConnectionTask();
this.createSchedulerFuture = createScheduler.submit(task);
}
} else {
this.emptySignal();
}
}
}
// catch各种异常,进行日志打印,并抛出异常
catch (Error e) {
LOG.error("{dataSource-" + this.getID() + "} init error", e);
throw e;
}
finally {
inited = true;
lock.unlock();
// 省略日志输出代码
}
}
获取连接
maxWait是获取连接时最长的等待时间,默认是-1,代表获取连接不会进行超时处理
public DruidPooledConnection getConnection() throws SQLException {
return getConnection(maxWait);
}
public DruidPooledConnection getConnection(long maxWaitMillis) throws SQLException {
init();
// 如果存在filter,则使用filter进行创建连接
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(this);
return filterChain.dataSource_connect(this, maxWaitMillis);
} else {
return getConnectionDirect(maxWaitMillis);
}
}
分为以下几个动作:
调用getConnectionInternal获取经过各种包装的Connection,这个是获取连接的主要逻辑,支持超时时间,由DruidDataSource的maxWait参数指定,单位毫秒
如果testOnBorrow为true,则进行对连接进行校验,校验失败则进行清理并重新进入循环,否则跳到下一步
如果testWhileIdle为true,距离上次激活时间超过timeBetweenEvictionRunsMillis,则进行清理
如果removeAbandoned为true,则会把连接存放在activeConnections中,清理线程会对其定期进行处理
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
// 循环
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
// logger error info
throw ex;
}
// 如果testOnBorrow设为true的话,则在返回连接之前,需要进行校验,校验的逻辑不仅仅是判断是否被关闭,还需要调用ValidConnectionChecker进行check,前面的init里面也分析过
// 需要注意的是,如果check失败,则会discard处理,并且重新走一遍循环
if (testOnBorrow) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
Connection realConnection = poolableConnection.conn;
discardConnection(realConnection);
continue;
}
} else {
Connection realConnection = poolableConnection.conn;
if (poolableConnection.conn.isClosed()) {
discardConnection(null); // 传入null,避免重复关闭
continue;
}
// 如果testWhileIdle为true,并且上一次激活的时间如果超过清理线程执行的间距,则进行check动作
if (testWhileIdle) {
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = poolableConnection.holder.lastActiveTimeMillis;
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0 // unexcepted branch
) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
discardConnection(realConnection);
continue;
}
}
}
}
// 如果removeAbandoned设为true,则把返回的线程保存起来,便于清理线程进行清理,注意只有removeAbandoned为true清理线程才会对池外的连接进行清理
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano();
poolableConnection.traceEnable = true;
// 放在activeConnections中,它是一个IdentityHashMap,个人觉得完成可以使用并发的Map,可能是考虑hashCode的问题吧
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT);
} finally {
activeConnectionLock.unlock();
}
}
if (!this.defaultAutoCommit) {
poolableConnection.setAutoCommit(false);
}
return poolableConnection;
}
}
getConnectionInternal
这一块没啥好分析的,主要是控制了创建连接的线程数量,以及处理异常,主要的逻辑由pollLast(nanos)或者takeLast()完成
private DruidPooledConnection getConnectionInternal(long maxWait) throws SQLException {
// 省略部分代码
final long nanos = TimeUnit.MILLISECONDS.toNanos(maxWait);
final int maxWaitThreadCount = this.maxWaitThreadCount;
DruidConnectionHolder holder;
// createDirect只是针对createScheduler(创建连接的线程池)进行处理
for (boolean createDirect = false;;) {
if (createDirect) {
//TODO 忽略
}
// 对主锁加锁
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("interrupt", e);
}
try {
// 如果等待创建连接的线程数如果大于maxWaitThreadCount,抛出异常,这个notEmptyWaitThreadCount是在pollLast(nanos)和takeLast()中设置
if (maxWaitThreadCount > 0 && notEmptyWaitThreadCount >= maxWaitThreadCount) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException("maxWaitThreadCount " + maxWaitThreadCount + ", current wait Thread count "
+ lock.getQueueLength());
}
// 针对fatalError,抛出异常
if (onFatalError
&& onFatalErrorMaxActive > 0
&& activeCount >= onFatalErrorMaxActive) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException(errorMsg, lastFatalError);
}
connectCount++;
// 省略...... 针对存在createScheduler线程池的处理
// 获取连接的核心逻辑,后续分析
if (maxWait > 0) {
holder = pollLast(nanos);
} else {
holder = takeLast();
}
} catch (InterruptedException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw new SQLException(e.getMessage(), e);
} catch (SQLException e) {
connectErrorCountUpdater.incrementAndGet(this);
throw e;
} finally {
lock.unlock();
}
break;
}
if (holder == null) {
//TODO 抛出异常
}
holder.incrementUseCount();
// 包装下Connection
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;
}
pollLast(nanos)
poll操作很简单,如果连接池内没有连接了,则调用empty.signal(),通知CreateThread创建连接,并且等待指定的时间,被唤醒之后再去查看是否有可用连接
private DruidConnectionHolder pollLast(long nanos) throws InterruptedException, SQLException {
long estimate = nanos;
for (;;) {
if (poolingCount == 0) {
// 唤醒CreateThread创建连接
emptySignal(); // send signal to CreateThread create connection
if (failFast && failContinuous.get()) {
throw new DataSourceNotAvailableException(createError);
}
// 已经超时了
if (estimate <= 0) {
waitNanosLocal.set(nanos - estimate);
return null;
}
notEmptyWaitThreadCount++;
if (notEmptyWaitThreadCount > notEmptyWaitThreadPeak) {
notEmptyWaitThreadPeak = notEmptyWaitThreadCount;
}
// 等待指定的时间
try {
long startEstimate = estimate;
estimate = notEmpty.awaitNanos(estimate); // signal by recycle or creator
notEmptyWaitCount++;
notEmptyWaitNanos += (startEstimate - estimate);
if (!enable) {
connectErrorCountUpdater.incrementAndGet(this);
throw new DataSourceDisableException();
}
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to non-interrupted thread
notEmptySignalCount++;
throw ie;
} finally {
notEmptyWaitThreadCount--;
}
// 说明在等待时间内连接仍然未创建完成,返回null
if (poolingCount == 0) {
if (estimate > 0) {
continue;
}
waitNanosLocal.set(nanos - estimate);
return null;
}
}
// poolingCount值减1,取出poolingCount索引的DruidConnectionHolder,并置为null
decrementPoolingCount();
DruidConnectionHolder last = connections[poolingCount];
connections[poolingCount] = null;
long waitNanos = nanos - estimate;
last.setLastNotEmptyWaitNanos(waitNanos);
return last;
}
}
/**
-
如果不存在createScheduler,则唤醒创建线程
-
@return {[type]} [description]
*/
private void emptySignal() {
if (createScheduler == null) {
empty.signal();
return;
}if (createTaskCount >= maxCreateTaskCount) {
return;
}if (activeCount + poolingCount + createTaskCount >= maxActive) {
return;
}createTaskCount++;
CreateConnectionTask task = new CreateConnectionTask();
this.createSchedulerFuture = createScheduler.submit(task);
}
takeLast()
take操作和poll只是存在等待时间的差异,take会多次尝试获取连接,获取成功才会返回
异步创建Connection线程
CreateConnectionThread是一个守护线程,在需要时创建连接。在以下两个条件下进入await,获取连接的线程唤醒它才会创建物理连接:
连接池内的连接个数大于等待的线程数量,如果keepAlive为true的话,还需要考虑池外、池内的连接数,其中activeCount在返回连接时+1,回收链接时-1,也就是池外的连接数
如果池内、池外的连接数大于maxActive,也进入await
创建好物理连接之后,需要使用DruidConnectionHolder代理实际的物理连接,该对象持有DruidDataSource的引用,调用Connection最终会调用DruidDataSource的recyle(DruidPooledConnection conn)回收该连接,创建物理连接的过程是不加锁的,避免影响性能。
创建好连接之后,还需要把该连接put到连接池中,重新进行加锁。put过程是将连接存放在poolingCount索引,并且通知notEmpty取走连接,也就是需要获取连接的线程
public class CreateConnectionThread extends Thread {
public CreateConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
long lastDiscardCount = 0;
int errorCount = 0;
for (;;) {
// addLast
try {
lock.lockInterruptibly();
} catch (InterruptedException e2) {
break;
}
// 省略部分代码......
try {
boolean emptyWait = true;
// 省略部分代码......
if (emptyWait) {
// 必须存在线程等待,才创建连接
if (poolingCount >= notEmptyWaitThreadCount //
&& !(keepAlive && activeCount + poolingCount < minIdle)) {
empty.await();
}
// 防止创建超过maxActive数量的连接,需要把池外的连接考虑进来
if (activeCount + poolingCount >= maxActive) {
empty.await();
continue;
}
}
} catch (InterruptedException e) {
// 异常处理,忽略
break;
} finally {
lock.unlock();
}
PhysicalConnectionInfo connection = null;
try {
connection = createPhysicalConnection();
setFailContinuous(false);
} catch (SQLException e) {
// 异常处理,忽略......
}
if (connection == null) {
continue;
}
boolean result = put(connection);
errorCount = 0; // reset errorCount
}
}
}
protected boolean put(PhysicalConnectionInfo physicalConnectionInfo) {
DruidConnectionHolder holder = null;
try {
holder = new DruidConnectionHolder(DruidDataSource.this, physicalConnectionInfo);
} catch (SQLException ex) {
// 异常处理
return false;
}
return put(holder);
}
private boolean put(DruidConnectionHolder holder) {
lock.lock();
try {
// 再次校验,避免创建的连接超过maxActive
if (poolingCount >= maxActive) {
return false;
}
// 放回连接池中
connections[poolingCount] = holder;
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = System.currentTimeMillis();
}
// 通知获取连接的线程来取走
notEmpty.signal();
notEmptySignalCount++;
if (createScheduler != null) {
// ......
}
} finally {
lock.unlock();
}
return true;
}
Connection回收
在前面也提到了线程的回收,当我们应用程序调用Connection#close(),实际上调用的是DruidDataSource的recyle(DruidPooledConnection conn),我们直接分析recyle的实现
先是做一些检测工作,比如是否被关闭
根据testOnReturn参数校验连接的可用性,主要是调用testConnectionInternal函数,前面也提到过
把连接放回连接池,需要加锁处理,也是把连接放到poolingCount位置,并且通知notEmpty来消费连接
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
// testOnReturn为true的话,则会校验链接的可用性,和testOnBorrow类似
if (testOnReturn) {
boolean validate = testConnectionInternal(holder, physicalConnection);
if (!validate) {
// 失效处理...
return;
}
}
// 放在连接池的末尾
lock.lock();
try {
activeCount--;
closeCount++;
result = putLast(holder, lastActiveTimeMillis);
recycleCount++;
} finally {
lock.unlock();
}
}
boolean putLast(DruidConnectionHolder e, long lastActiveTimeMillis) {
if (poolingCount >= maxActive) {
return false;
}
e.lastActiveTimeMillis = lastActiveTimeMillis;
connections[poolingCount] = e;
incrementPoolingCount();
if (poolingCount > poolingPeak) {
poolingPeak = poolingCount;
poolingPeakTime = lastActiveTimeMillis;
}
notEmpty.signal();
notEmptySignalCount++;
return true;
}
异步清理Connection
DestroyConnectionThread线程会定期执行一次清理动作,默认是60000ms执行一次,可以指定timeBetweenEvictionRunsMillis控制清理的频率,主要逻辑在于DestroyTask,首先会执行shrink对过期时间进行处理,然后根据removeAbandoned的值判断是否需要进行清理abandoned的连接。shrink只针对连接池的连接进行清理,而removeAbandoned会对从连接池外的连接进行清理
public class DestroyConnectionThread extends Thread {
public DestroyConnectionThread(String name){
super(name);
this.setDaemon(true);
}
public void run() {
initedLatch.countDown();
// 忽略异常等细节
for (;;) {
if (timeBetweenEvictionRunsMillis > 0) {
Thread.sleep(timeBetweenEvictionRunsMillis);
} else {
Thread.sleep(1000); //
}
if (Thread.interrupted()) {
break;
}
destroyTask.run();
}
}
}
public class DestroyTask implements Runnable {
public void run() {
shrink(true, keepAlive);
if (isRemoveAbandoned()) {
removeAbandoned();
}
}
}
shrink
shrink只会清理连接池内的连接,有几个参数,需要注意下:
phyTimeoutMillis:连接最大存活时间,默认是-1(不限制物理连接时间),从创建连接开始计算,如果超过该时间,则会被清理
keepAlive:默认是false,标记连接池内的连接是否需要保持存活,如果设为true的话,则会每次清理的时候,都会调用validateConnection,针对mysql而言就会往db发ping维持连接;这和前面提到的testOnBorrow、testWhileIdle类似
lastActiveTimeMillis:上一次激活的时间,在连接被recycle,或者清理线程在清理的时候会重新标记该时间(需要指定kepAlive为true)
minEvictableIdleTimeMillis:默认是1800000ms,如果上次激活时间lastActiveTimeMillis至当前时刻,如果小于该时间,则不进行清理
maxEvictableIdleTimeMillis:默认是25200000ms,和上面的minEvictableIdleTimeMillis类似,如果超过了maxEvictableIdleTimeMillis,则该连接会被清理掉
shrink对时间的处理比较复杂,下面基于checkTime为true的情况进行分析
如果是指定了phyTimeoutMillis,并且创建时间大于该值,则直接回收
如果上次激活时间小于minEvictableIdleTimeMillis值则不进行处理
如果上次激活时间小于minEvictableIdleTimeMillis,则清理掉checkCount个连接(需要维持在minIdle个连接)
如果超过了maxEvictableIdleTimeMillis直接清理
如果介于minEvictableIdleTimeMillis和maxEvictableIdleTimeMillis之间,并且keepAlive是true,则需要keepAlive
基于上面的规则,然后对connections连接池数组进行copy,清理掉无效的连接以及需要keepAlive的连接;然后关闭失效的连接,针对keepAlive的连接还需要校验连接,如果校验失败则直接关闭,校验成功则更新其lastActiveTimeMillis时间,并重新放入连接池中
public void shrink(boolean checkTime, boolean keepAlive) {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
return;
}
int evictCount = 0;
int keepAliveCount = 0;
try {
if (!inited) {
return;
}
// checkCount代表超过minIdle的连接数
final int checkCount = poolingCount - minIdle;
final long currentTimeMillis = System.currentTimeMillis();
// 清理连接池内的连接
for (int i = 0; i < poolingCount; ++i) {
DruidConnectionHolder connection = connections[i];
if (checkTime) {
// 检测物理连接时间限制,从创建时间作为起始点
if (phyTimeoutMillis > 0) {
long phyConnectTimeMillis = currentTimeMillis - connection.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
evictConnections[evictCount++] = connection;
continue;
}
}
// 上次激活时间小于minEvictableIdleTimeMillis值则不管它
long idleMillis = currentTimeMillis - connection.lastActiveTimeMillis;
if (idleMillis < minEvictableIdleTimeMillis) {
break;
}
// 咦,明明这个checkTime是true,如果进入了上面的if,可能是笔误吧
// 如果上次激活时间小于minEvictableIdleTimeMillis,则清理掉checkCount个连接(需要维持在minIdle个连接)
if (checkTime && i < checkCount) {
evictConnections[evictCount++] = connection;
}
// 超过了maxEvictableIdleTimeMillis直接清理
else if (idleMillis > maxEvictableIdleTimeMillis) {
evictConnections[evictCount++] = connection;
}
// 介于minEvictableIdleTimeMillis和maxEvictableIdleTimeMillis之间,并且keepAlive是true,则需要keepAlive
else if (keepAlive) {
keepAliveConnections[keepAliveCount++] = connection;
}
} else {
if (i < checkCount) {
evictConnections[evictCount++] = connection;
} else {
break;
}
}
}
// 把不需要的连接干掉,包括keepAlive的连接,keepAlive的连接在下面会重新put到连接池中
int removeCount = evictCount + keepAliveCount;
if (removeCount > 0) {
System.arraycopy(connections, removeCount, connections, 0, poolingCount - removeCount);
Arrays.fill(connections, poolingCount - removeCount, poolingCount, null);
poolingCount -= removeCount;
}
keepAliveCheckCount += keepAliveCount;
} finally {
lock.unlock();
}
// 关闭失效的连接
if (evictCount > 0) {
for (int i = 0; i < evictCount; ++i) {
DruidConnectionHolder item = evictConnections[i];
Connection connection = item.getConnection();
JdbcUtils.close(connection);
destroyCount.incrementAndGet();
}
Arrays.fill(evictConnections, null);
}
// 处理需要保持存活的连接,首先要调用validateConnection校验是否有效
// 如果有效,则更新其lastActiveTimeMillis时间,并且放回连接池中
// 如果失效,则直接关闭连接
if (keepAliveCount > 0) {
this.getDataSourceStat().addKeepAliveCheckCount(keepAliveCount);
// keep order
for (int i = keepAliveCount - 1; i >= 0; --i) {
DruidConnectionHolder holer = keepAliveConnections[i];
Connection connection = holer.getConnection();
holer.incrementKeepAliveCheckCount();
boolean validate = false;
try {
this.validateConnection(connection);
validate = true;
} catch (Throwable error) {
// logger
}
if (validate) {
holer.lastActiveTimeMillis = System.currentTimeMillis();
put(holer);
} else {
JdbcUtils.close(connection);
}
}
// 清空数据,避免内存泄露
Arrays.fill(keepAliveConnections, null);
}
}
removeAbandoned
针对连接池外的连接进行清理,可以避免数据库死锁导致连接无法被释放的问题,这个应该是设计的初衷吧。
如果Connection仍然处在running状态则不进行处理,但是个人有个疑问,比如sql执行过程中数据库发生死锁了,那么这个running值应该为true,也不会被处理
如果连接时间超过了removeAbandonedTimeoutMillis值,则进行后续的废弃操作
废弃操作会判断连接是否disable的,是的话则会关闭连接,否则不进行处理
public int removeAbandoned() {
int removeCount = 0;
long currrentNanos = System.nanoTime();
List abandonedList = new ArrayList();
activeConnectionLock.lock();
try {
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
for (; iter.hasNext();) {
DruidPooledConnection pooledConnection = iter.next();
// 判断是否在running,这个是由Filter为其设置值
if (pooledConnection.isRunning()) {
continue;
}
// 如果连接时间超过了removeAbandonedTimeoutMillis值,则进行后续的废弃操作
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
pooledConnection.setTraceEnable(false);
abandonedList.add(pooledConnection);
}
}
} finally {
activeConnectionLock.unlock();
}
// 如果连接是disable的,则会关闭连接,否则不进行处理
if (abandonedList.size() > 0) {
for (DruidPooledConnection pooledConnection : abandonedList) {
final ReentrantLock lock = pooledConnection.lock;
lock.lock();
try {
if (pooledConnection.isDisable()) {
continue;
}
} finally {
lock.unlock();
}
JdbcUtils.close(pooledConnection);
pooledConnection.abandond();
removeAbandonedCount++;
removeCount++;
if (isLogAbandoned()) {
// logger
}
}
}
return removeCount;
}
总结
整个DruidDataSource的实现并不复杂,但是各种参数,各种逻辑的处理比较繁锁,但是通过阅读源码,我们可以更好的理解Druid,比如设计原理、参数优化等等,而不是随意调整设置参数
DruidDataSource用到了重入锁来控制读写的并发问题,但是很多地方尽量将锁细粒度化,在需要的那一小段逻辑内才会使用
Filter使用了责任链模式,这使得druid的扩展性很强,比如druid的密码加密处理、监控数据的采集,都是通过Filter完成的