数据库连接池是如何管理 Connection 的?
为什么有这个疑惑,两个原因。
第二、从架构师封装的框架里拿了 Connection,我就问他要不要手动 close 掉,他的回答是关不关都可以。这个答案让我非常困惑。
数据库连接池的种类挺多的,如 C3P0、DBCP、Druid、HikariCP 等。其中 Druid 和 HikariCP 特点最明显,前者功能最全、为监控而生;后者性能最佳。今天得空,就翻了下 Druid 的源码去寻找答案。
先说个背景知识,JDK 1.4 推出了 javax.sql.DataSource 接口,Connection 的工厂;官方文档给出说明,实现这个接口主要来处理 Connection 相关的基础功能、Connection 连接池、分布式事务。
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// 超时次数与连接池是否已满处理
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
if (LOG.isWarnEnabled()) {
LOG.warn("get connection timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}
//是否检测 Connection 有效性
if (testOnBorrow) {
//使用 validationQuery 配置的 sql 检测 Connection 是否有效
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
//丢弃 Connection
discardConnection(poolableConnection.holder);
continue;
}
} else {
//Connection已关闭,丢弃
if (poolableConnection.conn.isClosed()) {
discardConnection(poolableConnection.holder); // 传入null,避免重复关闭
continue;
}
//申请连接的时候检测,如果空闲时间大于 timeBetweenEvictionRunsMillis,执行 validationQuery sql 检测连接是否有效
if (testWhileIdle) {
final DruidConnectionHolder holder = poolableConnection.holder;
long currentTimeMillis = System.currentTimeMillis();
long lastActiveTimeMillis = holder.lastActiveTimeMillis;
long lastExecTimeMillis = holder.lastExecTimeMillis;
long lastKeepTimeMillis = holder.lastKeepTimeMillis;
if (checkExecuteTime
&& lastExecTimeMillis != lastActiveTimeMillis) {
lastActiveTimeMillis = lastExecTimeMillis;
}
if (lastKeepTimeMillis > lastActiveTimeMillis) {
lastActiveTimeMillis = lastKeepTimeMillis;
}
long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.timeBetweenEvictionRunsMillis;
if (timeBetweenEvictionRunsMillis <= 0) {
timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
}
if (idleMillis >= timeBetweenEvictionRunsMillis
|| idleMillis < 0) {
boolean validate = testConnectionInternal(poolableConnection.holder, poolableConnection.conn);
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}
discardConnection(poolableConnection.holder);
continue;
}
}
}
}
//关闭长时间不使用的 Connection 的处理逻辑
if (removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano();
poolableConnection.traceEnable = true;
activeConnectionLock.lock();
try {
activeConnections.put(poolableConnection, PRESENT);
} finally {
activeConnectionLock.unlock();
}
}
//设置是否自动提交
if (!this.defaultAutoCommit) {
poolableConnection.setAutoCommit(false);
}
return poolableConnection;
}
}
从上面 getConnectionDirect 的代码可以看出,想要解决 Connection closed 的问题,可以
-
开启 testOnBorrow,配上 validationQuery 执行的 sql; 开启 testWhileIdle,配上 timeBetweenEvictionRunsMillis 空闲时间,超过进行 Connection 校验。
疑惑二,是否要手动关闭拿到的 Connection,即是否要调用 Connection 的 close() 的方法?
会对这个问题产生疑惑的原因就是,我把 Connection 关闭了,不就发挥不了数据库连接池的作用了吗?再次获取 Connection 还需要重新连接数据库。
@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;
}
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();
}
this.disable = true;
}
//Connection 回收方法
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();
//连接池回收该连接,但未关闭物理 Connection,可以通过配置进一步校验它的有效性
dataSource.recycle(this);
}
this.holder = null;
conn = null;
transactionInfo = null;
closed = true;
}
close 方法里主要提现的是 Connection 的回收逻辑,未并非是真的 close 物理连接。