SpringBoot集成Druid出现异常报错的原因及解决
⽬录
Spring Boot集成Druid异常
异常分析
原理追踪
禁⽤Ping Method
为什么要清空空闲60秒以上的连接
Spring Boot集成Druid异常
在Spring Boot集成Druid项⽬中,发现错误⽇志中频繁的出现如下错误信息:
discard long time none received connection. , jdbcUrl : jdbc:mysql://******?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8, version : 1.2.3, lastPacketReceivedIdleMillis : 172675经过排查发现是Druid版本导致的异常,在1.2.2及以前版本并
未出现如此异常。⽽在其以上版本均存在此问题,下⾯就来分析⼀下异常原因及解决⽅案。
异常分析
⾸先上⾯的异常并不影响程序的正常运⾏,但作为程序员看到程序中不停的出现异常还是难以忍受的。所以还是要刨根问底的解决⼀下的。
跟踪堆栈信息会发现对应的异常是从com.alibaba.druid.pool.DruidAbstractDataSource#testConnectionInternal⽅法中抛出的,对应的代码如下:
if (valid && isMySql) { // unexcepted branch
long lastPacketReceivedTimeMs = LastPacketReceivedTimeMs(conn);
if (lastPacketReceivedTimeMs > 0) {
long mysqlIdleMillis = currentTimeMillis - lastPacketReceivedTimeMs;
if (lastPacketReceivedTimeMs > 0 //
&& mysqlIdleMillis >= timeBetweenEvictionRunsMillis) {
discardConnection(holder);
String errorMsg = "discard long time none received connection. "
+ ", jdbcUrl : " + jdbcUrl
+ ", jdbcUrl : " + jdbcUrl
+ ", lastPacketReceivedIdleMillis : " + mysqlIdleMillis;
<(errorMsg);
return false;
}
}
}
上述代码中,LastPacketReceivedTimeMs(conn) 是获取上⼀次使⽤的时间,mysqlIdleMillis 就是计算出来空闲的时间,timeBetweenEvictionRunsMillis 是常量60
秒。如果连接空闲了60秒以上,那就discardConnection(holder) 丢弃这个旧连接并顺带打印了⼀个⽇志LOG.warn(errorMsg)。
原理追踪
在上述代码中,我们看到进⼊该业务逻辑是有前提条件的,也就是valid和isMySql变量同时为true。isMySql为true是必须的,我们使⽤的本⾝就是Mysql数据库。那么是否可以让valid 为false呢?这样不就不会进⼊该业务处理了吗?
来看看valid的来源,还是在该⽅法的上⾯:
boolean valid = validConnectionChecker.isValidConnection(conn, validationQuery, validationQueryTimeout);
我们到validConnectionChecker的Mysql实现⼦类MySqlValidConnectionChecker,该类中对isValidConnection的实现如下:
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.Class())) {
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 = ateStatement();
if (validationQueryTimeout > 0) {
stmt.setQueryTimeout(validationQueryTimeout);
}
spring boot依赖注入原理rs = uteQuery(query);
return true;
} finally {
JdbcUtils.close(rs);
JdbcUtils.close(stmt);
}
}
我们可以看到上述⽅法中有三个返回的地⽅:第⼀个连接已关闭;第⼆个使⽤ping的形式进⾏检查;第三,使⽤select 1的⽅式进⾏检查。⽽使⽤ping的形式检查时,⽆论是否抛异常都会返回true。这⾥我们禁⽤该模式即可。
进⼊ping的业务逻辑主要靠变量usePingMethod来判断,追踪代码会发现在这⾥进⾏的设置:
public void configFromProperties(Properties properties) {
String property = Property("sql.usePingMethod");
if ("true".equals(property)) {
setUsePingMethod(true);
} else if ("false".equals(property)) {
setUsePingMethod(false);
}
}
那么,也就是说,当我们把系统属性sql.usePingMethod设置为false即可禁⽤该功能。
禁⽤Ping Method
到了问题的根源,那么剩下的就是如何禁⽤了,通常有三种形式。
第⼀,在启动程序时在运⾏参数中增加:-sql.usePingMethod=false。
第⼆,在Spring Boot项⽬中,可在启动类中添加如下静态代码快:
static {
System.setProperty("sql.usePingMethod","false");
}
第三,类⽂件配置。在项⽬的DruidConfig类中新增加:
/*
* 解决druid ⽇志报错:discard long time none received connection:xxx
* */
@PostConstruct
public void setProperties(){
System.setProperty("sql.usePingMethod","false");
}
⾄此,已可以成功关闭该功能,异常信息再也不会出现了。
为什么要清空空闲60秒以上的连接
猜测,阿⾥给数据库设置的数据库空闲等待时间是60秒,mysql数据库到了空闲等待时间将关闭空闲的连接,以提升数据库服务器的处理能⼒。
MySQL的默认空闲等待时间是8⼩时,就是「wait_timeout」的配置值。如果数据库主动关闭了空闲的连接,⽽连接池并不知道,还在使⽤这个连接,就会产⽣异常。
以上就是Spring Boot集成Druid出现异常报错的原因及解决的详细内容,更多关于Spring Boot集成Druid出现异常的资料请关注其它相关⽂章!

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。