DruidCP源码阅读8 -- removeAbandoned机制

1、traceEnable与abandoned属性

先看下tranceEnable和abandoned这两个DruidDataSource类中的成员变量

traceEnable和abandoned都是DruidPooledConnection对象的成员属性

traceEnable:在获取连接getConnectionDirect时判断如果开启了removeAbandoned那么这个连接的traceEnable=true,防止在发生removeAbandoned时,再次遍历到这个连接对象。

        // com.alibaba.druid.pool.DruidDataSource#recycle        

        // DCL双重锁检测,防止在removeAbandoned时再次遍历到这个连接
        if (pooledConnection.traceEnable) {
            Object oldInfo = null;
            activeConnectionLock.lock();
            try {
                if (pooledConnection.traceEnable) {
                    
                    // traceEnable=true时,将连接从activeConnections中移除这个连接
                    // 防止在removeAbandoned时再次遍历到这个连接
                    oldInfo = activeConnections.remove(pooledConnection);
                    pooledConnection.traceEnable = false;
                }
            } finally {
                activeConnectionLock.unlock();
            }
            if (oldInfo == null) {
                if (LOG.isWarnEnabled()) {
                    LOG.warn("remove abandonded failed. activeConnections.size " + activeConnections.size());
                }
            }
        }
                // com.alibaba.druid.pool.DruidDataSource#removeAbandoned
                // removeAbandonedTimeoutMillis: 连接回收的超时时间,默认300秒
                // 如果当前连接使用的时间 大于 超时时间阈值(默认5分钟),则该连接将被回收
                if (timeMillis >= removeAbandonedTimeoutMillis) {
                    iter.remove();
                    // 监控连接参数置为false,在removeAbandoned=false时,traceEnable永远是false
                    // getConnectionDirect时如果removeAbandoned=true时,给连接赋值traceEnable=true
                    // com.alibaba.druid.pool.DruidDataSource.recycle回收连接时,将traceEnable=false
                    pooledConnection.setTraceEnable(false);
                    // 废弃连接存入容器
                    abandonedList.add(pooledConnection);
                }

abandoned:在开启removeAbandoned时,判断如果当前从connections连接池借出去的连接使用时长是否超过阈值(默认5分钟),超过阈值的连接被打上了abandoned=true的标记,那么在close连接时,这些连接会被丢弃而不会再次回收,目的是防止连接执行时间过长带来的内存泄漏,如果连接时间过长在数据库端会产生大量的内存而且无法回收。

    public void recycle() throws SQLException {
        。。。

        // 没有开启removeAbandoned,则不会直接丢掉连接,而是进入dataSource.recycle阶段,将连接重新放入连接池
        if (!this.abandoned) {
            DruidAbstractDataSource dataSource = holder.getDataSource();
            dataSource.recycle(this);
        }

        // 断掉与holder的关联,监控线程不会走上面的回收流程,直接断掉
        this.holder = null;
        conn = null;
        transactionInfo = null;
        closed = true;
    }
if (removeAbandoned) {
     StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
     poolableConnection.connectStackTrace = stackTrace;
     poolableConnection.setConnectedTimeNano();
     poolableConnection.traceEnable = true;

     activeConnectionLock.lock();
     try {
         activeConnections.put(poolableConnection, PRESENT);
     } finally {
         activeConnectionLock.unlock();
     }
}

traceEnable防止连接重复被removeAbandoned机制从activeConnections数组中检测到;

abandoned为true时说明当前连接将被直接丢弃

2、removeAbandoned机制

removeAbandoned:连接泄漏检测机制

防止内存泄漏,如果连接长期没有close,那么在数据库中当前连接用到的一些资源无法被释放导致数据库的压力增加,而且activeConnections(从连接池中借出去的连接),无法将连接放回connections,带来的问题就是无法生产新连接,业务线程会阻塞;

在removeAbandoned机制中会把借出去的连接超过阈值(默认5分钟)的连接放入abandonedList容器中,并且把这些连接的abandoned属性设置为true,这些连接将别直接丢弃。

/*
    连接泄漏检测机制
    防止内存泄漏,activeConnections是保存从连接池借出去的连接,如果连接长期没有close,那么
    activeConnections中的连接无法放回connections,带来的问题就是无法生产新连接,
    所以在removeAbandoned机制中,会把超过阈值的连接放入abandonedList中,并且把他们的abandoned属性
    标记为true,
    在com.alibaba.druid.pool.DruidPooledConnection.recycle中会判断abandoned这个变量,如果为
    true,那么不会进行连接回收操作,而是直接丢弃掉
 */
    public int removeAbandoned() {
        // 移除连接计数器
        int removeCount = 0;

        long currrentNanos = System.nanoTime();

        // 初始化存放被丢弃连接的容器
        List<DruidPooledConnection> abandonedList = new ArrayList<DruidPooledConnection>();

        /*
            com.alibaba.druid.pool.DruidAbstractDataSource.getActiveConnections
            com.alibaba.druid.pool.DruidDataSource.getConnectionDirect
            com.alibaba.druid.pool.DruidDataSource.handleFatalError
            com.alibaba.druid.pool.DruidDataSource.recycle
            com.alibaba.druid.pool.DruidDataSource.removeAbandoned
         */
        activeConnectionLock.lock();
        try {
            // 获取正在使用的连接集合
            Iterator<DruidPooledConnection> iter = activeConnections.keySet().iterator();

            // 遍历正在使用的连接
            for (; iter.hasNext(); ) {
                DruidPooledConnection pooledConnection = iter.next();

                // 判断改连接是否还在运行,不运行则回收
                if (pooledConnection.isRunning()) {
                    continue;
                }

                // 当前连接已经使用的时间
                long timeMillis = (currrentNanos - pooledConnection.getConnectedTimeNano()) / (1000 * 1000);

                // removeAbandonedTimeoutMillis: 连接回收的超时时间,默认300秒
                // 如果当前连接使用的时间 大于 超时时间阈值(默认5分钟),则该连接将被回收
                if (timeMillis >= removeAbandonedTimeoutMillis) {
                    iter.remove();
                    // 监控连接参数置为false,在removeAbandoned=false时,traceEnable永远是false
                    // getConnectionDirect时如果removeAbandoned=true时,给连接赋值traceEnable=true
                    // com.alibaba.druid.pool.DruidDataSource.recycle回收连接时,将traceEnable=false
                    pooledConnection.setTraceEnable(false);
                    // 废弃连接存入容器
                    abandonedList.add(pooledConnection);
                }
            }
        } finally {
            activeConnectionLock.unlock();
        }

        // 执行丢弃连接逻辑
        if (abandonedList.size() > 0) {
            for (DruidPooledConnection pooledConnection : abandonedList) {
                final ReentrantLock lock = pooledConnection.lock;
                lock.lock();
                try {
                    if (pooledConnection.isDisable()) {
                        continue;
                    }
                } finally {
                    lock.unlock();
                }

                JdbcUtils.close(pooledConnection);
                // 这个被DruidPooledConnection封装的连接将被丢弃
                pooledConnection.abandond();
                removeAbandonedCount++;
                removeCount++;
                
                //日志代码暂时跳过不分析
                。。。
            }
        }

        return removeCount;
    }

销毁连接的守护线程DestroyCOnnectionThread会每隔(默认1分钟)一段时间调用destroyTask线程的run方法,destroyTask会判断是否开启removeAbandoned机制。

removeAbandoned只会回收不活跃的连接,正在running的连接不回收

// 判断连接是否还在运行,不运行则回收
if (pooledConnection.isRunning()) {
    continue;
}

 总结

removeAbandoned是一个连接泄漏检测机制,能够将执行未关闭的连接检测出来,但是官方建议不开启,我个人觉得是假如一个conn上一次使用未关闭,又执行了一个sql那么可能会超时这时候如果强行关闭链接的话会抛出connection closed异常,那么业务就崩溃了,所以首先要保证业务侧能正常运行,不开启removeAbandoned。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要使用 `druid-spring-boot-starter`,你需要在你的 Maven 项目中添加以下依赖: ```xml <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> ``` 然后,你可以在你的 `application.properties` 或 `application.yml` 文件中配置 `druid` 数据源。以下是一个示例: ```yaml spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db_name?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC username: root password: yourpassword type: com.alibaba.druid.pool.DruidDataSource # 下面是 druid 配置 # 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 initial-size: 5 # 最小连接池数量 min-idle: 5 # 最大连接池数量 max-active: 20 # 获取连接时最大等待时间,单位毫秒 max-wait: 60000 # 是否开启 PSCache pool-prepared-statements: true # 指定每个连接上 PSCache 的大小 max-pool-prepared-statement-per-connection-size: 20 # 打开removeAbandoned功能 remove-abandoned: true # 180秒,也就是3分钟 remove-abandoned-timeout: 180 # 关闭abanded连接时输出错误日志 log-abandoned: true # 监控配置 filter: # 开启监控统计功能 stat: enabled: true # 是否打印 SQL 语句 log-slow-sql: true # 慢 SQL 记录时间阈值,单位毫秒 slow-sql-millis: 5000 # 配置监控统计拦截的 URI,多个用逗号隔开 web-stat-filter: exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 配置 Druid 的 StatViewServlet stat-view-servlet: url-pattern: /druid/* # IP 白名单 allow: 127.0.0.1 # IP 黑名单(共同存在时,deny优先于allow) deny: 192.168.0.1 # 登录用户名 login-username: admin # 登录密码 login-password: admin123 ``` 以上是一个基本的 `druid` 配置,你可以根据自己的实际需求进行调整。配置完成后,你就可以在代码中使用 `DataSource` 了,例如: ```java @Autowired DataSource dataSource; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zyc_2754

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值