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

本文分析了Spring Actuator在2.6.2版本与Druid 1.1.20集成时,访问/actuator/configprops接口导致数据库连接池耗尽的问题。问题源于Actuator在序列化DataSource时触发死循环。解决方案包括禁用configprops接口、使用DruidDataSourceBuilder或Spring原生配置创建DataSource。最后提醒,技术选择应考虑适用场景。

参考来源: 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框架。

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

<think>我们正在处理的是使用Druid连接池时出现的连接中断问题。根据用户提供的引用[1]和之前的对话,问题可能源于连接池配置不当(如maxWait设置)导致在高频请求下连接无法及时获取。以下是针对Druid连接池的解决方案。 ### 问题原因分析 1. **连接池耗尽**:前端每分钟请求一次,但数据库响应慢或连接未释放,导致连接池中所有连接被占用。 2. **maxWait设置**:当连接池无可用连接时,应用在`maxWait`时间内等待获取连接,若超时则抛出`interrupt`异常[^1]。 3. **连接泄漏**:数据库操作后连接未关闭,导致连接无法回归连接池。 --- ### 解决方案(Druid配置优化) #### 1. **调整核心参数** 在`application.properties`中增加以下配置: ```properties # 初始连接数 spring.datasource.druid.initial-size=5 # 最小空闲连接 spring.datasource.druid.min-idle=5 # 最大活动连接(根据数据库承受能力调整) spring.datasource.druid.max-active=20 # 获取连接最大等待时间(毫秒),建议不低于3秒 spring.datasource.druid.max-wait=3000 # 验证连接可用性的SQL spring.datasource.druid.validation-query=SELECT 1 ``` #### 2. **启用连接泄漏检测** ```properties # 关闭长时间未归还连接的线程(单位毫秒) spring.datasource.druid.remove-abandoned=true # 超时时间设定为180秒 spring.datasource.druid.remove-abandoned-timeout=180 # 输出泄漏日志 spring.datasource.druid.log-abandoned=true ``` #### 3. **配置空闲连接回收** ```properties # 检测需要关闭的空闲连接间隔(毫秒) spring.datasource.druid.time-between-eviction-runs-millis=60000 # 连接最小生存时间 spring.datasource.druid.min-evictable-idle-time-millis=300000 ``` #### 4. **添加监控界面(可选)** 在Spring Boot中启用Druid监控: ```java @Configuration public class DruidConfig { @Bean public ServletRegistrationBean<StatViewServlet> druidServlet() { ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>( new StatViewServlet(), "/druid/*"); // 设置监控页账号密码 bean.addInitParameter("loginUsername", "admin"); bean.addInitParameter("loginPassword", "admin"); return bean; } } ``` --- ### 关键参数说明 | 参数名 | 建议值 | 作用 | |-------------------------------|-----------|--------------------------| | `max-active` | 20 | 连接池最大容量 | | `max-wait` | 3000ms | 获取连接最大等待时间 | | `remove-abandoned-timeout` | 180s | 泄漏连接强制回收阈值 | | `time-between-eviction-runs-millis` | 60000ms | 空闲连接检测周期 | > **注意**:`max-wait`不宜过短(<1秒),否则高并发下易触发超时;也不宜过长(>10秒),避免线程堆积。 --- ### 连接泄漏调试步骤 1. 访问Druid监控页(`http://localhost:8080/druid`) 2. 查看 **数据源** → **活动连接**,检查未关闭连接堆栈 3. 根据堆栈定位未释放连接的代码位置 --- §§相关问题§§ 1. Druid连接池与HikariCP性能对比如何? 2. 如何通过Spring Boot Actuator监控数据库连接池? 3. 数据库连接泄漏如何通过日志定位? </think> ### 解决 Druid 连接池出现 `CannotGetJdbcConnectionException: interrupt` 的方法 #### 问题根源分析 根据引用[1]的描述,该异常通常由以下原因引起: 1. **连接池耗尽**:前端高频请求(每分钟一次)导致连接池被占 2. **maxWait 设置不当**:获取连接等待时间过短,超时后触发中断 3. **连接未释放**:数据库操作完成后连接未正确归还连接池 4. **慢查询阻塞**:SQL 执行时间过长占用连接 --- ### 解决方案(Druid 专属配置) #### 1. 优化关键连接池参数 在 `application.properties` 中调整配置: ```properties # 连接池基础配置 spring.datasource.druid.initial-size=5 spring.datasource.druid.min-idle=5 spring.datasource.druid.max-active=20 # 根据数据库负载调整 # 关键解决参数:增加获取连接等待时间 spring.datasource.druid.max-wait=5000 # 从默认值调至5秒 # 启用泄漏检测(单位:毫秒) spring.datasource.druid.remove-abandoned=true spring.datasource.druid.remove-abandoned-timeout=180 # 3分钟未关闭视为泄漏 spring.datasource.druid.log-abandoned=true # 记录泄漏日志 ``` #### 2. 添加连接有效性验证 ```properties # 连接健康检查 spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-on-borrow=true spring.datasource.druid.validation-query=SELECT 1 ``` #### 3. 配置连接回收策略 ```properties # 定期回收空闲连接(单位:毫秒) spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 ``` #### 4. 监控连接使用情况 在配置类中添加 Druid 监控: ```java @Bean public ServletRegistrationBean<StatViewServlet> druidServlet() { ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*"); // 添加监控页账号密码 bean.addInitParameter("loginUsername", "admin"); bean.addInitParameter("loginPassword", "admin"); return bean; } ``` 访问 `http://localhost:8080/druid` 查看连接池实时状态 --- ### 关键参数说明 | 参数名 | 建议值 | 作用 | |-------------------------------|-----------|--------------------------| | `max-wait` | 3000-5000ms | 获取连接最大等待时间 | | `max-active` | 20-50 | 连接池最大容量 | | `remove-abandoned-timeout` | 180s | 泄漏连接强制回收阈值 | | `time-between-eviction-runs-millis` | 60000ms | 空闲连接检测周期 | > **高频场景提示**: > 1. 当 QPS > 50 时,建议 `max-active ≥ QPS × 平均查询时间(s) × 2` > 2. 监控页中若 **活跃连接 ≈ max-active** 说明需要扩容[^1] --- ### 连接泄漏排查技巧 1. 在监控页查看 **活动连接** 列表 2. 检查持有连接时间过长的线程 3. 根据堆栈信息定位未关闭连接的代码: ```java // 错误示例:未关闭连接 public void leakMethod() throws SQLException { Connection conn = dataSource.getConnection(); // 业务操作... // 忘记 conn.close() } // 正确做法:使用 try-with-resources public void safeMethod() { try (Connection conn = dataSource.getConnection(); Statement stmt = conn.createStatement()) { // 业务操作... } // 自动关闭连接 } ```
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值