几行代码轻松复现druid连接泄露的BUG之onFatalError

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
函数计算FC,每月15万CU 3个月
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
简介: 几行代码轻松复现druid连接泄露的BUG之onFatalError

背景介绍

一次druid数据库连接池连接泄露的排查分析介绍了连接泄露的分析排查过程,在几行代码轻松复现druid连接泄露的BUG之PhyTimeout介绍了当配置了phyTimeoutMillis参数情况下,连接泄露的场景,下面通过代码的方式来复现当数据库操作出现FatalError情况下连接泄露的场景。

复现过程

连接泄露场景

模拟当数据库操作出现FatalError的情况下出现连接泄露的场景。

连接泄露模拟代码

import com.alibaba.druid.pool.DruidConnectionHolder;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import com.alibaba.druid.pool.ExceptionSorter;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.Properties;
public class DruidAbandonedCase4OnFatalError {
    public static void main(String[] args) throws Exception{
        DruidDataSource dataSource =  new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false");
        dataSource.setMinIdle(2);
        // 保证不会触发DestroyTask,以便手动调用shrink
        dataSource.setTimeBetweenEvictionRunsMillis(1 * 60 * 60 * 1000);
        dataSource.setMinEvictableIdleTimeMillis(0);
        // 用来mock出现FatalError的数据库异常
        dataSource.setExceptionSorter(new MockExceptionSorter());
        dataSource.init();
        // connectioin1为正常的连接
        DruidPooledConnection connection1 = dataSource.getConnection();
        DruidConnectionHolder holder1 = connection1.getConnectionHolder();
        System.out.println(holder1.getConnectionId());
        // connectioin2是数据库操作出现异常的连接
        DruidPooledConnection connection2 = dataSource.getConnection();
        DruidConnectionHolder holder2 = connection2.getConnectionHolder();
        System.out.println(holder2.getConnectionId());
        // 数据库操作出现FatalError
        fatalError(connection2);
        connection2.close();
        // connectioin3是正常的连接
        DruidPooledConnection connection3 = dataSource.getConnection();
        DruidConnectionHolder holder3 = connection3.getConnectionHolder();
        System.out.println(holder3.getConnectionId());
        connection3.close();
        connection1.close();
        System.out.println();
        System.out.println("调用shrink【之前】,连接池中连接情况,begin");
        List<Map<String, Object>> conns = dataSource.getPoolingConnectionInfo();
        for(Map<String,Object> conn : conns){
            System.out.println(conn.get("connectionId"));
        }
        System.out.println("调用shrink【之前】,连接池中连接情况,end");
        // 手动执行shrink
        dataSource.shrink(true);
        System.out.println();
        System.out.println("调用shrink【之后】,连接池中连接情况,begin");
        conns = dataSource.getPoolingConnectionInfo();
        for(Map<String,Object> conn : conns){
            System.out.println(conn.get("connectionId"));
        }
        System.out.println("调用shrink【之后】,连接池中连接情况,end");
        System.out.println();
        System.out.println(holder1.getConnectionId() + " isClosed:" + holder1.getConnection().isClosed());
        System.out.println(holder2.getConnectionId() + " isClosed:" + holder2.getConnection().isClosed());
        System.out.println(holder3.getConnectionId() + " isClosed:" + holder3.getConnection().isClosed());
    }
    private static void fatalError(Connection conn){
        Statement pstmt = null;
        ResultSet rs = null;
        try {
            String sql = "select * from unExistTable";
            pstmt = conn.createStatement();
            rs = pstmt.executeQuery(sql);
            while (rs.next()) {
                int id = rs.getInt("id");
                System.out.println("id: " + id);
            }
        } catch (SQLException e) {
            // e.printStackTrace();
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (pstmt != null) {
                    pstmt.close();
                }
            } catch (SQLException e) {
                // e.printStackTrace();
            }
        }
    }
    static class MockExceptionSorter implements ExceptionSorter {
        @Override
        public boolean isExceptionFatal(SQLException e) {
            return true;
        }
        @Override
        public void configFromProperties(Properties properties) {
        }
    }
}

运行以上程序,输出结果如下:

10001
10002
严重: {conn-10002} discard
java.sql.SQLSyntaxErrorException: Table 'test.unexisttable' doesn't exist
  at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:121)
  at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
  at com.mysql.cj.jdbc.StatementImpl.executeQuery(StatementImpl.java:1200)
  at com.alibaba.druid.pool.DruidPooledStatement.executeQuery(DruidPooledStatement.java:300)
  at DruidAbandonedCase4OnFatalError.fatalError(DruidAbandonedCase4OnFatalError.java:82)
  at DruidAbandonedCase4OnFatalError.main(DruidAbandonedCase4OnFatalError.java:40)
10003
调用shrink【之前】,连接池中连接情况,begin
10003
10001
调用shrink【之前】,连接池中连接情况,end
调用shrink【之后】,连接池中连接情况,begin
10001
调用shrink【之后】,连接池中连接情况,end
10001 isClosed:true
10002 isClosed:true
10003 isClosed:false

代码执行逻辑描述:

  1. 获取connectionId为1001的连接;
  2. 获取connectionId为1002的连接,1002在执行数据库操作的时候出现了FatalError,然后调用close方法归还连接;
  3. 获取connectionId为1003的连接,调用close归还连接;
  4. 调用close归还connectionId为1001的连接;
  5. 打印出连接池管理的连接有哪些;
  6. 调用数据库连接池的shrink方法;
  7. 打印出连接池管理的连接有哪些;
  8. 打印出每个连接是否已被关闭。

期望执行结果:

  • 由于connectionId为1002的连接发生了FatalError,该连接会被关闭且从连接池中删除;
  • 连接池管理的连接包括connectionId为1001和1003,且连接状态都是非关闭状态

实际执行结果:

  • connectionId为1002的连接已被关闭且从连接池删除【符合期望】;
  • 连接池管理的连接只包括connectionId为1001的连接,不包含connectionId为1003的连接(泄露的连接)【不符合期望】;
  • connectionId为1001的连接为已关闭状态(不应该关闭掉)【不符合预期】;

测试了druid 1.1.11,1.2.8-1.2.17,1.2.18-1.2.20,其中1.2.8-1.2.17都存在以上连接泄露的问题,1.1.11,1.2.18-1.2.20不存在以上连接泄露的问题。

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
druid 数据库
几行代码轻松复现druid连接泄露的BUG之keepalive
几行代码轻松复现druid连接泄露的BUG之keepalive
1422 6
|
Arthas druid Java
一次druid数据库连接池连接泄露的排查分析
一次druid数据库连接池连接泄露的排查分析
2169 1
|
监控 druid Java
监控druid数据库连接池连接泄露的思路
监控druid数据库连接池连接泄露的思路
1723 2
|
druid 关系型数据库 MySQL
【数据库】数据库连接池配置 testOnBorrow
【数据库】数据库连接池配置 testOnBorrow
1311 0
|
5月前
|
canal 数据可视化 关系型数据库
2025年5大国产ETL工具横向评测
在企业数据管理中,ETL工具成为整合分散数据的关键。本文介绍了五款主流国产ETL工具:FineDataLink(低代码、功能全面)、Kettle(开源易用)、DataX(高速同步)、Canal(MySQL实时增量处理)和StreamSets(可视化强),帮助用户根据需求选择最合适的工具,提升数据效率与业务价值。
1168 56
|
7月前
|
人工智能 前端开发 JavaScript
SpringBoot实现网页消息推送的5种方法
本文详细介绍了在SpringBoot中实现网页消息推送的几种主流方案,包括短轮询、长轮询、SSE(Server-Sent Events)、WebSocket以及STOMP。每种方案各有优缺点,适用于不同的场景需求。短轮询简单易实现但效率低;长轮询提升了实时性但仍有限制;SSE适合单向通信且轻量高效;WebSocket支持全双工通信,适合高实时性要求的场景;STOMP基于WebSocket,提供更高级的消息传递功能。通过对比分析,开发者可根据业务需求、性能要求及浏览器兼容性选择最适合的技术方案,同时可结合多种技术实现优雅降级,优化用户体验。
1428 57
|
12月前
|
决策智能 数据库 开发者
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
本项目旨在解决智能体的“超级入口”问题,通过开发基于意图识别的多智能体框架,实现用户通过单一交互入口使用所有智能体。项目依托阿里开源的Qwen2.5大模型,利用其强大的FunctionCall能力,精准识别用户意图并调用相应智能体。 核心功能包括: - 意图识别:基于Qwen2.5的大模型方法调用能力,准确识别用户意图。 - 业务调用中心:解耦框架与业务逻辑,集中处理业务方法调用,提升系统灵活性。 - 会话管理:支持连续对话,保存用户会话历史,确保上下文连贯性。 - 流式返回:支持打字机效果的流式返回,增强用户体验。 感谢Qwen2.5系列大模型的支持,使项目得以顺利实施。
3631 8
使用Qwen2.5+SpringBoot+SpringAI+SpringWebFlux的基于意图识别的多智能体架构方案
|
12月前
|
人工智能 前端开发 Java
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
本文介绍了如何使用Spring AI Alibaba开发一个简单的AI对话应用。通过引入`spring-ai-alibaba-starter`依赖和配置API密钥,结合Spring Boot项目,只需几行代码即可实现与AI模型的交互。具体步骤包括创建Spring Boot项目、编写Controller处理对话请求以及前端页面展示对话内容。此外,文章还介绍了如何通过添加对话记忆功能,使AI能够理解上下文并进行连贯对话。最后,总结了Spring AI为Java开发者带来的便利,简化了AI应用的开发流程。
9080 2
Spring AI Alibaba + 通义千问,开发AI应用如此简单!!!
ly~
|
消息中间件 存储 数据库连接
RocketMQ 消息的重试机制是怎样的?
RocketMQ的消息重试机制确保消息消费失败时能自动重试,直至成功。默认重试16次,时间间隔逐次翻倍,从10秒至数分钟不等。重试在同组内不同消费者间进行,由异常抛出或特定状态返回触发。支持自定义重试次数与时间间隔,建议合理配置避免无限重试,保障系统稳定性和性能。
ly~
3869 2