Druid连接池 communications link failure问题

背景

项目基于Spring boot 链接数据库用的Druid连接池1.1.16版。通过nginx代理连接数据库。数据库配置

1
2
3
show VARIABLES like '%timeout%'
interactive_timeout 1000
wait_timeout 1000

物理链接空闲1000秒后会进行回收。
druid连接池配置:

1
2
3
timeBetweenEvictionRunsMillis: 10000
minEvictableIdleTimeMillis: 30000
maxEvictableIdleTimeMillis: 84000

每隔10秒回进行判断空闲时间大于30秒的,或者总存活时间大于84秒的,是否需要回收。按照道理不会有问题,但是执行后会发现druid后台的逻辑链接打开次数
大于逻辑链接关闭次数。两者之差,正好等于物理连接打开次数-物理链接关闭次数-1。(-1是因为配置的最小链接数量)。通过后台日志查看到会报

1
Communications link failure\n\nThe last packet successfully received from the server was 3 milliseconds ago. The last packet sent successfully to the server was 4 milliseconds ago.; nested exception is com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure\n\nThe last packet successfully received from the server was 3 milliseconds ago. The last packet sent successfully to the server was 4 milliseconds ago.

看着很难受,也影响使用。

原因

druid通过MySqlValidConnectionChecker类进行链接健康检查。

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
public class   MySqlValidConnectionChecker{
//其他方法
@Override
public void configFromProperties(Properties properties) {
String property = properties.getProperty("druid.mysql.usePingMethod");
if ("true".equals(property)) {
setUsePingMethod(true);
} else if ("false".equals(property)) {
setUsePingMethod(false);
}
}


public boolean isValidConnection(Connection conn, String validateQuery, int validationQueryTimeout) throws Exception {
if (conn.isClosed()) {
return false;
}

if (usePingMethod) {
if (conn instanceof DruidPooledConnection) {
conn = ((DruidPooledConnection) conn).getConnection();
}

if (conn instanceof ConnectionProxy) {
conn = ((ConnectionProxy) conn).getRawObject();
}

if (clazz.isAssignableFrom(conn.getClass())) {
if (validationQueryTimeout < 0) {
validationQueryTimeout = DEFAULT_VALIDATION_QUERY_TIMEOUT;
}

try {
ping.invoke(conn, true, validationQueryTimeout * 1000);
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof SQLException) {
throw (SQLException) cause;
}
throw e;
}
return true;
}
}

String query = validateQuery;
if (validateQuery == null || validateQuery.isEmpty()) {
query = DEFAULT_VALIDATION_QUERY;
}

Statement stmt = null;
ResultSet rs = null;
try {
stmt = conn.createStatement();
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
rs = stmt.executeQuery(query);
return true;
} finally {
JdbcUtils.close(rs);
JdbcUtils.close(stmt);
}

}
//其他方法
}

检查链接是否存活。默认是用的internalPing方法。这个方法会一直返回true 当达到mysql的wait_timeout后。数据库将物理链接关闭,druid端还认为可用。
这就有问题了。

解决方法

使用validationQuery进行检查。
配置方法如下:

1
2
3
4
5
6
7
8
9
1. 增加yml配置
validationQuery: select 'x'
2. 启动时参数带上
-Ddruid.mysql.usePingMethod=false
或者在druid配置类上加入:
@PostConstruct
public void setProperties(){
System.setProperty("druid.mysql.usePingMethod","false");
}