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

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出现异常的资料请关注我们其它相关文章!

(0)

相关推荐

  • SpringBoot整合JDBC、Druid数据源的示例代码

    1.SpringBoot整合JDBCTemplate 1.1.导入jdbc相关依赖包 主要的依赖包: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</gro

  • SpringBoot中使用com.alibaba.druid.filter.config.ConfigTools对数据库密码加密的方法

    SpringBoot中使用com.alibaba.druid.filter.config.ConfigTools对数据库密码加密 1.在本地Maven仓库中打开Powershell2.输入命令,然后点击回车3.将生成公钥和加密的数据库密码配置到SpringBoot项目中的yml配置文件中druid的pom版本 1.在本地Maven仓库中打开Powershell 2.输入命令,然后点击回车 scotttiger为未加密的数据库密码 privateKey为生成的私钥 publicKey为生成的公钥

  • Springboot Druid 自定义加密数据库密码的几种方案

    前言 开发过程中,配置的数据库密码通常是明文形式,这样首先第一个安全性不好(相对来说),不符合一个开发规范(如项目中不能出现明文账号密码),其实就是当出现特殊需求时,比如要对非运维人员开方服务器部分权限,但是又涉及项目部署的目录时,容易泄漏数据库密码,虽然一般生产环境中,数据库往往放入内网,访问只能通过内网访问,但是不管怎么说账号密码直接让人知道总归不好,甚至有些项目需要部署到客户环境中,但是可能共用一个公共数据库(数据库只向指定服务器开放外网端口或组建内网环境),这样的情况下,如果数据库密码再

  • Springboot mybatis plus druid多数据源解决方案 dynamic-datasource的使用详解

    依赖 <dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>p6spy</groupId>

  • SpringBoot在yml配置文件中配置druid的操作

    最新版的druid和旧版在filter配置方面有些不同,以下是旧版druid中配置filter: spring: ##数据库连接信息 datasource: url: jdbc:mysql://localhost:3306/young username: root password: root driver-class-name: com.mysql.jdbc.Driver ###################以下为druid增加的配置########################### t

  • SpringBoot对Druid配置SQL监控功能失效问题及解决方法

    由于我使用的是properties类型的配置文件,在对druid的参数进行配置的时候,多加了druid,也就是spring.datasource.druid.xxx,运行sql语句后SQL监控功能无效. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/userinfo?useUnicode=true&characterEnc

  • springboot项目整合druid数据库连接池的实现

    Druid连接池是阿里巴巴开源的数据库连接池项目,后来贡献给Apache开源: Druid的作用是负责分配.管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个: Druid连接池内置强大的监控功能,其中的StatFilter功能,能采集非常完备的连接池执行信息,方便进行监控,而监控特性不影响性能. Druid连接池内置了一个监控页面,提供了非常完备的监控信息,可以快速诊断系统的瓶颈. SpringBoot 1.x版本默认使用的的tomcat的jdbc连接池,由

  • springboot集成druid连接池配置的方法

    在开发项目中如果数据库选型为mysql,很大概率下连接池会使用druid 这里介绍springboot集成durid springboot : 2.1.9 druid : 1.1.10 案例地址 github地址 springboot集成druid配置 需要引入的pom <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactI

  • SpringBoot集成Druid配置(yaml版本配置文件)详解

    maven 配置 <!-- https://mvnrepository.com/artifact/com.alibaba/druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.3</version> </dependency> <dependency&g

  • 如何在SpringBoot 中使用 Druid 数据库连接池

    Druid是阿里开源的一款数据库连接池,除了常规的连接池功能外,它还提供了强大的监控和扩展功能.这对没有做数据库监控的小项目有很大的吸引力. 下列步骤可以让你无脑式的在SpringBoot2.x中使用Druid. 1.Maven中的pom文件 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.14</ve

  • springboot 整合druid数据库密码加密功能的实现代码

    在之前给大家介绍过Springboot Druid 自定义加密数据库密码的几种方案,感兴趣的朋友可以点击查看下,今天通过本文给大家介绍springboot 整合druid数据库密码加密功能,具体内容如下所示: 1.依赖引入 <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1

随机推荐