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

本文涉及的产品
应用实时监控服务-应用监控,每月50GB免费额度
可观测可视化 Grafana 版,10个用户账号 1个月
函数计算FC,每月15万CU 3个月
简介: 几行代码轻松复现druid连接泄露的BUG之keepalive

背景介绍

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

复现过程

连接泄露场景

模拟当配置了keepalive选项的情况下出现连接泄露的场景。

连接泄露模拟代码

import com.alibaba.druid.pool.DruidConnectionHolder;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DruidAbandonedCase4Keepalive {
    public static void main(String[] args) throws Exception {
        Map<Long,DruidConnectionHolder> holderMap = new HashMap<>();
        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);
        dataSource.setKeepAlive(true);
        dataSource.setTimeBetweenEvictionRunsMillis(500);
        long minEvictableIdleTimeMillis = 500L;
        dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        long keepAliveBetweenTimeMillis = 1000L;
        dataSource.setKeepAliveBetweenTimeMillis(keepAliveBetweenTimeMillis);
        dataSource.init();
        try{
            Field destroyConnectionThreadField = DruidDataSource.class.getDeclaredField("destroyConnectionThread");
            destroyConnectionThreadField.setAccessible(true);
            Thread destroyConnectionThread = (Thread)destroyConnectionThreadField.get(dataSource);
            destroyConnectionThread.interrupt();
            Thread.State state = destroyConnectionThread.getState();
            System.out.println("destroyConnectionThread state : " + state);
        }catch (Exception e){
            e.printStackTrace();
        }
        DruidPooledConnection connection1 = dataSource.getConnection();
        DruidConnectionHolder holder1 = connection1.getConnectionHolder();
        print(holder1);
        holderMap.put(holder1.getConnectionId(),holder1);
        DruidPooledConnection connection2 = dataSource.getConnection();
        DruidConnectionHolder holder2 = connection2.getConnectionHolder();
        print(holder2);
        holderMap.put(holder2.getConnectionId(),holder2);
        connection2.close();
        sleep(keepAliveBetweenTimeMillis - minEvictableIdleTimeMillis + 100L);
        connection1.close();
        sleep(minEvictableIdleTimeMillis - 100L);
        pooling(dataSource,holderMap);
        shrink(dataSource);
        pooling(dataSource,holderMap);
        sleep(100L);
        shrink(dataSource);
        pooling(dataSource,holderMap);
        System.out.println();
        print(holder1);
        print(holder2);
    }
    private static void sleep(long millis){
        try{
            Thread.sleep(millis);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    private static void shrink(DruidDataSource dataSource){
        System.out.println();
        System.out.println("调用shrink");
        // 手动执行shrink
        dataSource.shrink(true);
    }
    private static void pooling(DruidDataSource dataSource,Map<Long,DruidConnectionHolder> holderMap) throws SQLException {
        System.out.println();
        System.out.println("连接池中连接情况,begin");
        List<Map<String, Object>> conns = dataSource.getPoolingConnectionInfo();
        for (Map<String, Object> conn : conns) {
            print(holderMap.get(conn.get("connectionId")));
        }
        System.out.println("连接池中连接情况,end");
    }
    private static void print(DruidConnectionHolder holder) throws SQLException {
        System.out.println(holder.getConnectionId() + /*" : " + holder +*/
                " idleMillis : " + (System.currentTimeMillis() - holder.getLastActiveTimeMillis()) + " isClosed:" + holder.getConnection().isClosed());
    }
}

运行以上程序,输出结果如下(druid 1.2.8):

destroyConnectionThread state : TIMED_WAITING
10001 idleMillis : 6 isClosed:false
10002 idleMillis : 1 isClosed:false
连接池中连接情况,begin
10002 idleMillis : 1015 isClosed:false
10001 idleMillis : 404 isClosed:false
连接池中连接情况,end
调用shrink
连接池中连接情况,begin
10001 idleMillis : 405 isClosed:false
10002 idleMillis : 1016 isClosed:false
连接池中连接情况,end
调用shrink
连接池中连接情况,begin
10001 idleMillis : 405 isClosed:false
10002 idleMillis : 1016 isClosed:false
连接池中连接情况,end
调用shrink
连接池中连接情况,begin
10002 idleMillis : 1133 isClosed:true
连接池中连接情况,end
10001 idleMillis : 522 isClosed:false
10002 idleMillis : 1133 isClosed:true

代码执行逻辑描述:

  1. 为了便于测试,中断Druid-ConnectionPool-Destroy-xx线程,以便手动调用shrink;
  2. 获取connectionId为1001的连接,打印连接信息;
  3. 获取connectionId为1002的连接,打印连接信息;
  4. 调用close,归还connectionId为1002的连接
  5. sleep (keepAliveBetweenTimeMillis - minEvictableIdleTimeMillis + 100L)时长
  6. 调用close,归还connectionId为1001的连接
  7. sleep (minEvictableIdleTimeMillis - 100L)时长(两个sleep是为了让1002 idleMillis大于keepAliveBetweenTimeMillis,让1001小于minEvictableIdleTimeMillis)
  8. 打印连接池中连接信息
  9. 执行shrink
  10. 打印连接池中连接信息
  11. sleep 100ms
  12. 执行shrink
  13. 打印连接池中连接信息
  14. 打印1001和1002连接信息

期望执行结果:

  • connectionId为1001和1002的连接都应该被连接池管理,且连接状态都应该是未关闭状态

实际执行结果:

  • connectionId为1001的连接已从连接池中删除,且连接状态是未关闭状态,泄露的连接【不符合期望】;
  • connectionId为1002的连接虽然在连接池中,但是连接状态是关闭状态【不符合期望】。

测试看,keepalive选项是从druid 1.1.16引入的,druid 1.1.16-1.1.24,1.2.0-1.2.17都存在连接泄露问题,druid 1.2.18-1.2.20不存在连接泄露问题。

目录
相关文章
|
Arthas druid Java
一次druid数据库连接池连接泄露的排查分析
一次druid数据库连接池连接泄露的排查分析
1832 1
|
监控 druid Java
监控druid数据库连接池连接泄露的思路
监控druid数据库连接池连接泄露的思路
1555 2
|
druid Java
一次 Druid 连接池泄露引发的血案!
www.javastack.cn 优秀的Java技术公众号
一次 Druid 连接池泄露引发的血案!
|
SQL Oracle 关系型数据库
DruidDataSource配置属性列表
DruidDataSource配置属性列表
646 0
|
druid 关系型数据库 MySQL
【数据库】数据库连接池配置 testOnBorrow
【数据库】数据库连接池配置 testOnBorrow
1113 0
|
应用服务中间件 Linux
Tomcat不定期close_wait过多
Tomcat不定期close_wait过多
568 0
|
安全 Java 数据安全/隐私保护
密钥在手,安全无忧:探索Spring Boot中SecretKeySpec与Cipher的神秘力量
【8月更文挑战第29天】在现代软件开发中,数据安全至关重要。本文作为教程,介绍如何在Spring Boot应用中利用`SecretKeySpec`和`Cipher`实现基本的加密和解密功能。首先,需添加相关依赖,然后创建工具类封装加密与解密方法。通过示例演示了如何生成密钥、加密及解密数据。正确实现加密能有效保护敏感信息,增强应用安全性。开发者应根据需求选择合适算法和密钥长度,确保数据安全。
460 0
|
JSON 数据格式
HttpClient远程调用基本使用(详解)
HttpClient远程调用基本使用(详解)
511 0
|
存储 SQL NoSQL
云数据库提供了哪些优势和功能?
云数据库提供了哪些优势和功能?
451 0