重点概括
其实Druid
整个连接池最核心的两个方法是shrink()
和recycle()
,它们两个的作用分别是shrink
会对连接池进行瘦身,将多余的连接释放掉,shrink
方法是在摧毁线程里会被周期性的调用,类似于一个定时任务。而recycle
方法调用不是定时任务,而是在连接关闭的时候进行的,这里的的关闭不是实际上的连接关闭,而是调用的DruidPoolConnect.close()
方法,具体实现其实就是先验证是否可以回收,可以的话扔进连接池,否则关闭,当然底层实现还有很多其他校验,如果感兴趣可以看我下面的源码分析。其实想想也很合理,连接一旦准备关闭,如果可能,放进连接池是最好的选择。
从程序设计的角度来讲,回收这个动作有close()
这样一个很好的触发条件,但是瘦身动作并没有触发条件,所以设计为定时任务。
源码解析
我们前面已经将shrink()的源码相信分析了,这里就不在赘述,感兴趣的朋友可以回去看下Druid源码阅读系列(五),今天就说说recycle()
方法。
recycle()
recycle()
方法的入口是在DruidPooledConnection
的close()
里。
@Override
public void close() throws SQLException {
if (this.disable) {
return;
}
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
DruidAbstractDataSource dataSource = holder.getDataSource();
boolean isSameThread = this.getOwnerThread() == Thread.currentThread();
if (!isSameThread) {
dataSource.setAsyncCloseConnectionEnable(true);
}
if (dataSource.isAsyncCloseConnectionEnable()) {
syncClose();
return;
}
if (!CLOSING_UPDATER.compareAndSet(this, 0, 1)) {
return;
}
try {
for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
listener.connectionClosed(new ConnectionEvent(this));
}
List<Filter> filters = dataSource.getProxyFilters();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(dataSource);
filterChain.dataSource_recycle(this);
} else {
recycle();
}
} finally {
CLOSING_UPDATER.set(this, 0);
}
this.disable = true;
}
这里需要注意的是下面这块逻辑:
boolean isSameThread = this.getOwnerThread() == Thread.currentThread();
if (!isSameThread) {
dataSource.setAsyncCloseConnectionEnable(true);
}
if (dataSource.isAsyncCloseConnectionEnable()) {
syncClose();
return;
}
这里会判断是否是同一个线程,如果不是同一个线程,不加锁可能造成同一时间一个连接被多个线程同时修改,引发错误,所以需要进行“同步”关闭。其实看“同步”的代码实现和非同步没啥区别,就是加了锁而已。
public void syncClose() throws SQLException {
lock.lock();
try {
if (this.disable || CLOSING_UPDATER.get(this) != 0) {
return;
}
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
if (!CLOSING_UPDATER.compareAndSet(this, 0, 1)) {
return;
}
for (ConnectionEventListener listener : holder.getConnectionEventListeners()) {
listener.connectionClosed(new ConnectionEvent(this));
}
DruidAbstractDataSource dataSource = holder.getDataSource();
List<Filter> filters = dataSource.getProxyFilters();
if (filters.size() > 0) {
FilterChainImpl filterChain = new FilterChainImpl(dataSource);
filterChain.dataSource_recycle(this);
} else {
recycle();
}
this.disable = true;
} finally {
CLOSING_UPDATER.set(this, 0);
lock.unlock();
}
}
这里需要注意的是即使是同一个线程,但是只要开启了removeAbandoned
属性,也需要“同步”关闭,因为removeAbandoned
是在其他线程里执行的,也会引发线程安全问题。removeAbandoned
是将一个连接直接关闭而不经过连接池,recycle
会直将可用连接放进连接池,如果不加锁,会出现一个已经关闭的连接却被放进连接池的情况。
public boolean isAsyncCloseConnectionEnable() {
if (isRemoveAbandoned()) {
return true;
}
return asyncCloseConnectionEnable;
}
recycle()
方法如下:
public void recycle() throws SQLException {
if (this.disable) {
return;
}
DruidConnectionHolder holder = this.holder;
if (holder == null) {
if (dupCloseLogEnable) {
LOG.error("dup close");
}
return;
}
if (!this.abandoned) {
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.recycle(this);
}
this.holder = null;
conn = null;
transactionInfo = null;
closed = true;
}
这里的abandoned
属性是不是有点眼熟?对的,就是在前面章节里说的removedAvandoned
方法里去设置的,当连接存活时间超过removeAbandonedTimeoutMillis
时,会将这个连接直接close()
并且打上abandoned
标记。回收时会排除掉这些已经打上标记的无效连接。
下面是回收真正的核心代码:
protected void recycle(DruidPooledConnection pooledConnection) throws SQLException {
final DruidConnectionHolder holder = pooledConnection.holder;
if (holder == null) {
LOG.warn("connectionHolder is null");
return;
}
//回收不同线程输出日志
if (logDifferentThread //
&& (!isAsyncCloseConnectionEnable()) //
&& pooledConnection.ownerThread != Thread.currentThread()//
) {
LOG.warn("get/close not same thread");
}
final Connection physicalConnection = holder.conn;
//traceEnable搭配removeAbandoned一起使用,获取连接时当开启removeAbandoned之后,这个状态设置为true,
// 当连接从activeConnections中取出或者connect disable的时候,设置为false.
//这块是做removeAbandoned(连接泄露)相关调试
if (pooledConnection.traceEnable) {
Object oldInfo = null;
activeConnectionLock.lock();
try {
if (pooledConnection.traceEnable) {
oldInfo = activeConnections.remove(pooledConnection);
pooledConnection.traceEnable = false;
}
} finally {
activeConnectionLock.unlock();
}
if (oldInfo == null) {
if (LOG.isWarnEnabled()) {
LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
}
}
}
final boolean isAutoCommit = holder.underlyingAutoCommit;
final boolean isReadOnly = holder.underlyingReadOnly;
final boolean testOnReturn = this.testOnReturn;
try {
// check need to rollback?
//自动提交或者只读同时不满足,说明代码需要回滚,执行回滚
if ((!isAutoCommit) && (!isReadOnly)) {
pooledConnection.rollback();
}
// reset holder, restore default settings, clear warnings
boolean isSameThread = pooledConnection.ownerThread == Thread.currentThread();
if (!isSameThread) {
final ReentrantLock lock = pooledConnection.lock;
lock.lock();
try {
holder.reset();
} finally {
lock.unlock();
}
} else {
holder.reset();
}
//如果连接已经丢弃放弃回收
if (holder.discard) {
return;
}
//phyMaxUseCount这个是连接最大使用次数,针对分布式数据库优化的,通过达到一定使用次数后断开重连,使得多个服务器间负载更均衡。
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
discardConnection(holder);
return;
}
//前面回滚操作如果发生异常,可能导致连接关闭,这个时候需要进行相应的数值刷新操作。
if (physicalConnection.isClosed()) {
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
//testOnReturn设置为true,回收的时候会检测该连接是否有用,如果不可用会直接将连接关闭。
if (testOnReturn) {
boolean validate = testConnectionInternal(holder, physicalConnection);
if (!validate) {
JdbcUtils.close(physicalConnection);
destroyCountUpdater.incrementAndGet(this);
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
}
if (holder.initSchema != null) {
holder.conn.setSchema(holder.initSchema);
holder.initSchema = null;
}
//这个指的是数据源是否可用
if (!enable) {
discardConnection(holder);
return;
}
boolean result;
final long currentTimeMillis = System.currentTimeMillis();
//物理超时时间验证
if (phyTimeoutMillis > 0) {
long phyConnectTimeMillis = currentTimeMillis - holder.connectTimeMillis;
if (phyConnectTimeMillis > phyTimeoutMillis) {
discardConnection(holder);
return;
}
}
//这里开始将活跃连接放进连接池,putLast将后续连接放进连接池数组尾部,并进行相应计数,整个连接池其实有点像一个队列
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
result = putLast(holder, currentTimeMillis);
recycleCount++;
} finally {
lock.unlock();
}
//如果放入连接池失败,将连接关闭并输出日志
if (!result) {
JdbcUtils.close(holder.conn);
LOG.info("connection recyle failed.");
}
} catch (Throwable e) {
holder.clearStatementCache();
if (!holder.discard) {
discardConnection(holder);
holder.discard = true;
}
LOG.error("recyle error", e);
recycleErrorCountUpdater.incrementAndGet(this);
}
}
代码里面都写了注释,整体流程是先输出不同线程的日志(如果设置了的话),如果开启了removeAbandoned
,将当前连接从activeConnections
集合里面拿出来,并更新traceEnable
状态,检查回滚执行条件,如何满足则执行回滚。不是同一个线程会重置holder的值。非discard
的连接,验证phyMaxUseCount
,连接使用次数超过则断开,这块主要是针对分布式数据库优化。前面回滚操作如果发生异常,可能导致连接关闭,需要进行相应的数值刷新操作。然后检查testOnReturn
相关逻辑,接着是数据源有效性校验。物理超时验证,最后才将活跃连接放进连接池,putLast
将后续连接放进连接池数组尾部,并进行相应计数,整个连接池其实有点像一个队列。如果放入失败则关闭连接打印日志。
这里强调一下这块逻辑:
if (physicalConnection.isClosed()) {
lock.lock();
try {
if (holder.active) {
activeCount--;
holder.active = false;
}
closeCount++;
} finally {
lock.unlock();
}
return;
}
这块代码我其实想了很久,前面"所有"关闭连接的操作都被return
了,那为啥还需要在去判断一下,一开始看着挺奇怪的,后面一步步往下看,看到回滚代码之后才发现里面有个catch
块,那里面如果异常的话,可能会关闭连接,这时候会将isClosed
设置为true,所以后面又这样一个判断。
疑问
今天看代码的时候有看到ConnectionEventListener
这样一个接口,不太懂怎么用的,有时间去研究下。