【分析】Spring Actuator + Druid数据库连接池耗尽问题

参考来源: https://github.com/alibaba/druid/issues/3059

问题环境

  1. spring boot actuator : 2.6.2系列(可能支持actuator的2.x都会引发该问题)
  2. druid:1.1.20(应该也不限制版本)
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
  1. 自定义DataSource数据源
spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password: Dongle@123
      url: jdbc:mysql://db.dongle.com:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false
management:
  endpoints:
    web:
      exposure:
        include: "*"
  server:
    ssl:
      enabled: false
    base-path: /
@Configuration
public class DruidDataSouceConfig {

    @Bean("druidDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource setDataSource(){
        return new DruidDataSource();
        //return
    }

    @Bean(name = "resourceProcessTransactionManager")
    public DataSourceTransactionManager setTransactionManager(@Qualifier("druidDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

问题一:访问actuator/configprops陷入死循环

Spring Actuator检测服务健康状态,当使用使用Druid管理数据库链接池时,若自定义DataSource源,访问/actuator/configprops接口时会导致数据库链接占满且服务死循环问题。

问题原因

主要问题是Spring Actuator输出配置信息时,会对DataSource做序列化处理,DruidDataSource有一个getConnection方法属性,会获得DruidPooledConnection,然后新获得的JDBCConnection继续获取DataSource,循环往复,导致数据库连接池被沾满,

问题分析

断点运行(完整调用逻辑请参考下方扩展:actuator/configprops调用流程
在这里插入图片描述
DruidPooledConnection含有DruidConnectionHolder属性
DruidConnectionHolder存在DataSource属性
DataSource实现类又是DruidDataSource
从上图看结果就是DruidDataSource -> DruidPooledConnection -> DruidConnectionHolder -> DruidDataSource循环,属性嵌套属性,导致一直循环读取属性方法。

属性嵌套预览

1.DruidDataSource获取DruidPooledConnection
DruidDataSource获取DruidPooledConnection2.DruidPooledConnection存在DruidConnectionHolder属性
DruidPooledConnection存在DruidConnectionHolder属性3.DruidConnectionHolder存在DruidAbstractDataSource属性
DruidConnectionHolder存在DruidAbstractDataSource属性
4.DruidDataSource 继承 DruidAbstractDataSource
DruidDataSource 继承 DruidAbstractDataSource

问题二:数据连接池资源耗尽

问题分析

上面得到结果会永远死循环,DruidDataSource也会一直调用getConnection方法获取DruidPooledConnection ,由于死循环存在,数据库链接池肯定会被占满,并且死循环跳不出去,链接就不会被释放,导致当链接满了之后,下一次循环就会等待,
锁定数据据库链接
若自定义DataSource未定义maxWait最大等待时长,就会调用takeLast方法一直等待新连接释放
等待获取数据库链接
等待数据链接

解决方案

一、禁用actuator/configprops接口

management:
  endpoints:
    web:
      exposure:
        exclude: "configprops"

二、使用DruidDataSourceBuilder创建DataSource源

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>
    @Bean("druidDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource setDataSource(){
        //return new DruidDataSource();
        // com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
        return DruidDataSourceBuilder.create().build();
        
        // 建议配置maxWait最大等待时长,否则会服务卡住
        // DruidDataSourceWrapper dataSource = (DruidDataSourceWrapper) DruidDataSourceBuilder.create().build();
        // dataSource.setMaxWait(3000);
        
    }

三、使用Spring原生配置创建DataSource源

当系统没有多数据源时,可以保留Spring原生DataSource创建方式,不需要自定义数据源

spring:
  datasource:
    name: druidDataSource
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: Dongle@123
    url: jdbc:mysql://db.dongle.com:3306/test?useUnicode=true&characterEncoding=UTF-8&useSSL=false

扩展:actuator/configprops调用流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
提示:后续逻辑请参照上面问题分析

总结

Druid本身个人认为应该是没有问题,毕竟也是一个成熟产品,只是原生Druid和Spring Actuator组合不友好,所有Druid也专门开发了druid-spring用于支持Spring框架。

技术只有在适宜场景使用才能达到最佳效果,不是说一定适用任何场景。保持学习,不要盲目使用是作为一个技术人员应该掌握的技术。

posted @ 2022-01-11 23:59  CryDongle  阅读(436)  评论(0编辑  收藏  举报