一次压力测试引起的内存溢出排查(apollo)

发布时间 2023-11-06 17:19:36作者: 搬砖党路过

项目从nacos配置中心适配apollo后,线上压测运行4个小时,内存告警,FGC达到了惊人的100+次

拿到压测dump文件使用mat分析发现com.ctrip.framework.apollo.spring.property.SpringValueRegistry占比达到91.68%,很明显SpringValueRegistry导致的内存泄漏

 找到了导致内存泄漏的原因,那么为什么会导致内存泄漏呢

1、SpringValueRegistry是apollo保存配置的注册类,实现了配置的热更新

  在 Apollo 控制台进行配置修改并发布后,对应的 client 端拉取到更新后,会调用到 com.ctrip.framework.apollo.spring.property.AutoUpdateConfigChangeListener#onChange 方法

        在调用 onChange 会收到对应的修改的配置信息 ConfigChangeEvent, 其中包含改动的 key 和 value, 则改动流程如下

    根据改动的配置的 key 从 springValueRegistry 找到对应的关联到这个 key 的 Spring Bean 信息,如果找不到则不处理

    根据找到的 Spring Bean 信息,进行对应关联配置的更新

2、SpringValueRegistry的配置时如何注册的

 

  在启动的时候,apollo客户端会注册SpringValueProcessor到spring容器中,下面是SpringValueProcessor的类图,BeanPostProcessor的实现类ApolloProcessor中对postProcessAfterInitialization方法进行了实现,该方法是Bean生命周期中最后一个执行的方法,所以这里处理bean的属性注入是最终的

   SpringValueProcessor中具体实现了Apollo对Value注解的处理,对value字段进行判断,并注册到自己的处理类中去处理,源码如下,只会对@Value注解注解到的属性和方法进行处理

 3、apollo中属性注册的背景上述做了简单的介绍,在我们的项目是如何引起内存泄漏的呢

  

  在项目存在了很多获取prototype模式bean的逻辑,在这些bean存在比较多的@Value注解,这就导致了只要获取一次这些bean都要执行一次springbean的整个生命周期流程,必然就会执行到SpringValueProcessor,也就会解析bean中的@Value注解的属性值并注册到SpringValueRegistry中。

  在300并发下大概每秒会创建3000个SpringValue,随着时间的推移会有越来越多的SpringValue对象被创建,占用了大量的内存。虽然SpringValueRegistry中定时清理过期的策略,但是在高并发下也于事无补,最后导致大量的FGC,系统卡顿,MQ消息堆积。

解决方法:在我们项目中没有使用配置热更新的策略,所以修改方案也很简单直接重写apollo中ConfigPropertySourcesProcessor类去掉SpringValueProcessor的注册,阻止后续逻辑的执行。

修复后:压测,FGC次数正常,内存使用正常