Druid连接池的监控stat造成内存泄漏

1. 起因

线上某台机器报警(堆内存使用率高),登录服务器将堆dump下来,进行分析:

 

发现:JdbcDataSourceStat中的sqlStatMap比较消耗内存。

因为就是Druid开启stat监控,所以sql信息就会存储到该Map中,占用内存,造成内存泄漏。

stat监控sql信息页面:可以看到会持有sql信息。

 

当然也有人在github的Druid的Issues上提出了这个问题。每个sql语句都会长期持有引用,加快FullGC频率

2. 实际分析

sql信息存储到sqlStatMap的源码如下所示:

public class JdbcDataSourceStat implements JdbcDataSourceStatMBean {
    private final LinkedHashMap<String, JdbcSqlStat>            sqlStatMap;

    public JdbcSqlStat createSqlStat(String sql) {
        lock.writeLock().lock();
        try {
            JdbcSqlStat sqlStat = sqlStatMap.get(sql);
            if (sqlStat == null) {
                sqlStat = new JdbcSqlStat(sql);
                sqlStat.setDbType(this.dbType);
                sqlStat.setName(this.name);
                sqlStatMap.put(sql, sqlStat);
            }

            return sqlStat;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

我们发现若是sql(key)相同,那么不会put到Map中,那么key是什么样子呢?

经过本地debug分析:

 

可以知道,sql并没有携带参数,是原始的sql信息。

 

但在进行分析时,发现sqlStatMap中存储的key好像都是一个sql???

进行分析后发现:此sql是一个批量语句!

案例复现:当批量操作参数个数不同时,对于sqlStatMap是不同的key。

 

分析结论:批量操作,由于参数个数不同,导致sqlStatMap存储的数据量大。

3. SpringBoot2.x会自动开启Druid的stat

有同学发现,自己的SpringBoot项目的配置文件中并没有开启stat配置,但是还是出现上面现象。

需要注意的是:SpringBoot2.x可以自动装配Druid。且会自动开启stat监控。

public class DruidFilterConfiguration {

    @Bean
    @ConfigurationProperties(FILTER_STAT_PREFIX)
    @ConditionalOnProperty(prefix ="spring.datasource.druid.filter.stat", name = "enabled", matchIfMissing = true)
    @ConditionalOnMissingBean
    public StatFilter statFilter() {
        return new StatFilter();
    }
}

matchIfMissing = true意思是没有配置spring.datasource.druid.filter.stat=true,那么会加载该Bean。

解决方案:是在配置类中使用spring.datasource.druid.filter.stat=false,或者在自己的Configuration配置StatFilter这个bean。

当然也会自动开启监控台:

@ConditionalOnWebApplication
@ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true", matchIfMissing = true)
public class DruidStatViewServletConfiguration {
    @Bean
    public ServletRegistrationBean statViewServletRegistrationBean(DruidStatProperties properties) {
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean();
        registrationBean.setServlet(new StatViewServlet());
        registrationBean.addUrlMappings(config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*");
        if (config.getAllow() != null) {
            registrationBean.addInitParameter("allow", config.getAllow());
        }
        if (config.getDeny() != null) {
            registrationBean.addInitParameter("deny", config.getDeny());
        }
        if (config.getLoginUsername() != null) {
            registrationBean.addInitParameter("loginUsername", config.getLoginUsername());
        }
        if (config.getLoginPassword() != null) {
            registrationBean.addInitParameter("loginPassword", config.getLoginPassword());
        }
        if (config.getResetEnable() != null) {
            registrationBean.addInitParameter("resetEnable", config.getResetEnable());
        }
        return registrationBean;
    }
}

可访问http://ip:端口/druid/sql.html查看控制台,默认密码没有配置。

4. 解决方案

Druid的监控统计功能是通过filter-chain扩展实现,如果你要打开监控统计功能,配置StatFilter,具体看这里:https://github.com/alibaba/druid/wiki/配置_StatFilter

4.1 方案一:直接关闭Druid的stat

显式的在配置文件使用spring.datasource.druid.filter.stat=false

4.2 方案二:开启sql合并

结构重复的sql语句的sql比较多,可以开启sql合并。例如:批量操作导致sqlStatMap过大可以采用这种方案。

SQL监控的LinkedHashMap<String, JdbcSqlStat> sqlStatMap是以SQL语句作为键的。针对上面批量处理导致大量的sql存储到sqlStatMap的问题,可以开启sql合并。

 

 

可以看到,只保留sql的结构,忽略sql的参数。

 

解决方案:
或者通过增加JVM的参数配置:

-Ddruid.stat.mergeSql=true
或者

spring:
    druid:
      connectionProperties: druid.stat.mergeSql=true

或者

@Configuration
public class DruidConfig {

    @Bean
    public StatFilter statFilter() {
        StatFilter statFilter = new StatFilter();
        statFilter.setMergeSql(true);
        return statFilter;
    }
}

4.3 方案三:控制sqlStatMap大小

有业务需求不能合并sql或者合并了sql也没有太大效果(结构重复的sql语句不多),也可以不设置sql合并而是设置druid.stat.sql.MaxSize(默认1000个)。

源码:

public class JdbcDataSourceStat implements JdbcDataSourceStatMBean {

        sqlStatMap = new LinkedHashMap<String, JdbcSqlStat>(16, 0.75f, false) {

            protected boolean removeEldestEntry(Map.Entry<String, JdbcSqlStat> eldest) {
                boolean remove = (size() > maxSqlSize);

                if (remove) {
                    JdbcSqlStat sqlStat = eldest.getValue();
                    if (sqlStat.getRunningCount() > 0 || sqlStat.getExecuteCount() > 0) {
                        skipSqlCount.incrementAndGet();
                    }
                }

                return remove;
            }
        };
}

LinkedHashMap有一个 removeEldestEntry(Map.Entry eldest)方法,通过覆盖这个方法,加入一定的条件,满足条件返回true。当put进新的值方法返回true时,便移除该map中最老的键和值。

sqlStatMap重写了removeEldestEntry方法,来控制最大数量。

解决方案:
或者通过增加JVM的参数配置:
-Ddruid.stat.sql.MaxSize=100

或者

spring:
    druid:
      connectionProperties: druid.stat.sql.MaxSize=100



作者:小胖学编程
链接:https://www.jianshu.com/p/fb37ab115121
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Druid连接池是一个专门用于监控数据库连接和SQL执行情况的工具。它被称为"为监控而生的数据库连接池",具有出色的功能、性能和扩展性,超过了其他常见的数据库连接池,如DBCP、C3P0、BoneCP、Proxool、JBoss DataSource等。 Druid连接池可以通过引入Druid提供的监控工具Druid Monitor来实现监控和统计数据源以及SQL的执行情况。这个工具可以帮助开发人员监测连接池的连接情况、性能指标、执行的SQL语句等信息,以便于进行性能调优和故障排查。通过使用Druid Monitor,开发人员可以方便地获取连接池的运行状态、连接数、活跃连接数、SQL执行情况、执行时间等详细信息,从而更好地了解系统的运行情况,及时发现和解决问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [数据库连接池 ( 五 ) Druid 数据监控](https://blog.csdn.net/yuanchun05/article/details/127174870)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [druid连接池监控](https://blog.csdn.net/zguoshuaiiii/article/details/78402883)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值