druid双刃剑参数之removeAbandoned
如何界定一个connection是被遗弃的对象呢?不得不提另一个参数,removeAbandonedTimeout,这个参数的单位为秒,从字面意思可以看出当时间超过这个参数时,就认定为被遗弃对象。关键代码在DruidDataSource类的removeAbandoned()方法中,代码如下:
public int removeAbandoned() {
int removeCount = 0;
long currrentNanos = System.nanoTime();
List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();
activeConnectionLock.lock();
try {
Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();
for (; iter.hasNext();) {
DruidPooledConnection pooledConnection = iter.next();
if (pooledConnection.isRunning()) {
continue;
}
long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);
if (timeMillis >= removeAbandonedTimeoutMillis) {
iter.remove();
pooledConnection.setTraceEnable(false);
abandonedList.add(pooledConnection);
}
}
} finally {
activeConnectionLock.unlock();
}
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()) {
StringBuilder buf = new StringBuilder();
buf.append("abandon connection, owner thread: ");
buf.append(pooledConnection.getOwnerThread().getName());
buf.append(", connected at : ");
buf.append(pooledConnection.getConnectedTimeMillis());
buf.append(", open stackTrace\n");
StackTraceElement[] trace = pooledConnection.getConnectStackTrace();
for (int i = 0; i < trace.length; i++) {
buf.append("\tat ");
buf.append(trace[i].toString());
buf.append("\n");
}
buf.append("ownerThread current state is " + pooledConnection.getOwnerThread().getState()
+ ", current stackTrace\n");
trace = pooledConnection.getOwnerThread().getStackTrace();
for (int i = 0; i < trace.length; i++) {
buf.append("\tat ");
buf.append(trace[i].toString());
buf.append("\n");
}
LOG.error(buf.toString());
}
}
}
return removeCount;
}
从函数功能,我们可以看到活动的连接,从被获取到开始计时,如果在removeAbandonedTimeout规定的时间内,还没有关闭的话,就自动进行移除处理。druid的开发者增加这个功能的目的是为了自动移除程序员编程过程中忘记close的connection。而如何断定程序员忘记关闭连接,是通过时间来界定的,removeAbandonedTimeout的默认值是5分钟,我看很多产品都把这个值设置为10分钟,也就是说,如果连接从被获取到被关闭超过10分钟,就会被自动移除。
有人可能会说了,函数中有个running的条件判断啊,代码如下:
if (pooledConnection.isRunning()) {
continue;
}
如果连接正在运行,就不进行移除。我们可以从DruidPooledConnection类中找到对running的赋值代码,分别在beforeExecute()方法和afterExecute()方法中。
final void beforeExecute() {
final DruidConnectionHolder holder = this.holder;
if (holder != null && holder.getDataSource().isRemoveAbandoned()) {
running = true;
}
}
final void afterExecute() {
final DruidConnectionHolder holder = this.holder;
if (holder != null && holder.getDataSource().isRemoveAbandoned()) {
running = false;
holder.setLastActiveTimeMillis(System.currentTimeMillis());
}
}
这两个方法的被调用,发生在DruidPooledPreparedStatement的execute()方法中,代码如下:
@Override
public boolean execute() throws SQLException {
checkOpen();
incrementExecuteCount();
transactionRecord(sql);
// oracleSetRowPrefetch();
conn.beforeExecute();
try {
return stmt.execute();
} catch (Throwable t) {
throw checkException(t);
} finally {
conn.afterExecute();
}
}
也就是说只有sql语句被执行的时候running才为true,其他时间都是false的。从而验证出,connection的持有时间一旦超过removeAbandonedTimeout,被移除的概率是非常高的。
对于removeAbandoned()方法的执行频率,我们可以在内部类DestroyTask中找到。DestroyTask的执行频率由参数timeBetweenEvictionRunsMillis决定,此参数默认是1秒,大部分产品,一般把这个参数设置为1分钟。
public class DestroyTask implements Runnable {
@Override
public void run() {
shrink(true, keepAlive);
if (isRemoveAbandoned()) {
removeAbandoned();
}
}
}
其实这种移除被遗弃的连接,在开发环境中还是很有用的,可以帮我们排查出忘记关闭连接的业务,从而避免连接泄露,但是在真正的生产环境中还是建议把removeAbandoned参数关闭,因为很多复杂业务的耗时是无法衡量的,一旦连接因为超时被移除,势必引起程序报错。我们完全可以在开发的时候就把忘记关闭连接的业务修正过来,不让这种bug延伸到生产环境中。
转自:https://www.modb.pro/db/171346