0%

Druid-getConnection性能优化-removeAbandoned功能

问题背景

在生成服务火焰图的时候,发现了服务在getConnection的时候出现了平台,其中会瓶颈点在Thread.getStackTrace()

Druid 版本 1.0.12

看下源码

可以看到如果,isRemoveAbandoned()为true时,会调用getStackTrace()获取当前线程信息,记录在类中,这个是一个比较慢的操作,也是慢的原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public DruidPooledConnection getConnectionDirect(long maxWaitMillis) throws SQLException {
int notFullTimeoutRetryCnt = 0;
for (;;) {
// handle notFullTimeoutRetry
DruidPooledConnection poolableConnection;
try {
poolableConnection = getConnectionInternal(maxWaitMillis);
} catch (GetConnectionTimeoutException ex) {
if (notFullTimeoutRetryCnt <= this.notFullTimeoutRetryCount && !isFull()) {
notFullTimeoutRetryCnt++;
if (LOG.isWarnEnabled()) {
LOG.warn("not full timeout retry : " + notFullTimeoutRetryCnt);
}
continue;
}
throw ex;
}

if (isTestOnBorrow()) {
boolean validate = testConnectionInternal(poolableConnection.getConnection());
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}

Connection realConnection = poolableConnection.getConnection();
discardConnection(realConnection);
continue;
}
} else {
Connection realConnection = poolableConnection.getConnection();
if (realConnection.isClosed()) {
discardConnection(null); // 传入null,避免重复关闭
continue;
}

if (isTestWhileIdle()) {
final long currentTimeMillis = System.currentTimeMillis();
final long lastActiveTimeMillis = poolableConnection.getConnectionHolder().getLastActiveTimeMillis();
final long idleMillis = currentTimeMillis - lastActiveTimeMillis;
long timeBetweenEvictionRunsMillis = this.getTimeBetweenEvictionRunsMillis();
if (timeBetweenEvictionRunsMillis <= 0) {
timeBetweenEvictionRunsMillis = DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
}

if (idleMillis >= timeBetweenEvictionRunsMillis) {
boolean validate = testConnectionInternal(poolableConnection.getConnection());
if (!validate) {
if (LOG.isDebugEnabled()) {
LOG.debug("skip not validate connection.");
}

discardConnection(realConnection);
continue;
}
}
}
}

if (isRemoveAbandoned()) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.setConnectStackTrace(stackTrace);
poolableConnection.setConnectedTimeNano();
poolableConnection.setTraceEnable(true);

synchronized (activeConnections) {
activeConnections.put(poolableConnection, PRESENT);
}
}

if (!this.isDefaultAutoCommit()) {
poolableConnection.setAutoCommit(false);
}

return poolableConnection;
}
}

removeAbandoned 功能

这个功能是干什么的呢?
实际上是Druid用于定时检测是不是有申请但是没有归还的链接的,如果一个链接拿走了,但是超过时间没有归还,那就这个功能会自动回收。本质是为了防止连接泄漏的。

优化

这个功能在公司组件里是默认开启的,如果开启了这个功能,在每次获取链接的时候都会尝试获取当前线程栈,获取当前线程栈的调用是比较消耗性能的。

理论上目前大多数应用都是基于Mybatis或者Spring框架使用DataSource的,一般不会有裸用dataSource的情况,所以也用不上这个功能,徒增了消耗,所以当前功能也不是很有必要。想不通为啥会默认开启…

在官方的实现里是默认关闭,并且在issue中还有建议是在出现泄漏问题或者排查问题的时候在开启,所以如果没有必要的话,关闭掉这个功能就好了~~

单个拿链接的时间可以缩短到十分之一~