欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot集成Druid出现异常报错的原因及解决

程序员文章站 2022-03-03 09:41:17
spring boot集成druid异常在spring boot集成druid项目中,发现错误日志中频繁的出现如下错误信息:discard long time none received connec...

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 = mysqlutils.getlastpacketreceivedtimems(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;
            log.error(errormsg);
            return false;
        }
    }
}

上述代码中,mysqlutils.getlastpacketreceivedtimems(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.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);
    }

}

我们可以看到上述方法中有三个返回的地方:第一个连接已关闭;第二个使用ping的形式进行检查;第三,使用select 1的方式进行检查。而使用ping的形式检查时,无论是否抛异常都会返回true。这里我们禁用该模式即可。

进入ping的业务逻辑主要靠变量usepingmethod来判断,追踪代码会发现在这里进行的设置:

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);
    }
}

那么,也就是说,当我们把系统属性druid.mysql.usepingmethod设置为false即可禁用该功能。

禁用ping method

找到了问题的根源,那么剩下的就是如何禁用了,通常有三种形式。

第一,在启动程序时在运行参数中增加:-ddruid.mysql.usepingmethod=false。

第二,在spring boot项目中,可在启动类中添加如下静态代码快:

static {
    system.setproperty("druid.mysql.usepingmethod","false");
}

第三,类文件配置。在项目的druidconfig类中新增加:

/*
* 解决druid 日志报错:discard long time none received connection:xxx
* */
@postconstruct
public void setproperties(){
    system.setproperty("druid.mysql.usepingmethod","false");
}

至此,已可以成功关闭该功能,异常信息再也不会出现了。

为什么要清空空闲60秒以上的连接

猜测,阿里给数据库设置的数据库空闲等待时间是60秒,mysql数据库到了空闲等待时间将关闭空闲的连接,以提升数据库服务器的处理能力。

mysql的默认空闲等待时间是8小时,就是「wait_timeout」的配置值。如果数据库主动关闭了空闲的连接,而连接池并不知道,还在使用这个连接,就会产生异常。

以上就是spring boot集成druid出现异常报错的原因及解决的详细内容,更多关于spring boot集成druid出现异常的资料请关注其它相关文章!