线上频繁fullgc问题-SpringActuator的坑

news2024/11/24 3:54:25

整体复盘

一个不算普通的周五中午,同事收到了大量了cpu异常的报警。根据报警表现和通过arthas查看,很明显的问题就是内存不足,疯狂无效gc。而且结合arthas和gc日志查看,老年代打满了,gc不了一点。既然问题是内存问题,那么老样子,通过jmap和heap dump 文件分析。
不感兴趣的可以直接看结论

  1. 通过jmap命令查看的类似下图,并没有项目中明显的自定义类,而占空间最大的又是char数组,当时线上占900M左右,整个老年代也就1.8个G;此时dump文件同事还在下载,网速较慢。

    image

  2. 通过业务日志查看,很多restTempalte请求报错,根据报错信息可知是某xx认证过期了,导致接收到回调,业务处理时调接口报错了;查询数据库,大概有20多万回调。根据过期时间和内存监控,大概能对的上号,表明内存异常和这个认证过期有关。怀疑度最高的只有回调以及回调补偿任务,但是一行一行代码看过去,并不觉得有什么异常。


    下载完dump文件后,先重启了服务器,避免影响业务,然后着手分析文件。


  3. 在dump文件下载完之后,使用jvisualvm分析,最多的char里大部分都是一些请求的路径,如“example/test/1",”“example/test/2"之类的,都是接口统一,但是参数不一样,因为是GET请求,所以实际路径都不一样。Jvisualvm点击gc_root又一直计算不出来,在等待计算的过程中,一度走了弯路
     

    image


    于是又现下载jprofiler,通过jprofiler的聚类,确定了一定是这个Meter导致的,而通过JProfile的分析,终于定位到是
    org.springframework.boot.actuate.metrics.web.client.MetricsClientHttpRequestInterceptor#intercept这个类。然后发现,MetricsClientHttpRequestInterceptor 持有一个meterRegistry,里面核心是个map,所以是map没有清除。根据依赖分析,发现是有次需求引入了redisson-spring-boot-starter,而redisson依赖了spring-boot-starter-actuator,这东西默认启动了,会拦截所有的RestTempalte请求,然后记录一些指标。

    image

    image

所以问题变成了,为什么map没有清掉已经执行完的请求?
我之前并没有研究过spring的actuator,只是看过skywalking的流程,所以我以为也和skywalking一样,记录然后上报,上报之后删除本地的。所以当时怀疑,难道是和我们请求都异常了有关,但是正如下面的代码,无论是否异常,都是执行finnally,所以又不太可能。

meterRegistry点击查看代码

而在我自己尝试复现之后,meterRegistry的指标根本不会被自动清除,生命周期和应用的生命周期一样。因为并不存在上报,数据全部在内存(虽然可以导出到数据库,但并没有深入研究)。其实也合理,因为如果要通过Grafana等可视化平台查看的时候,我们也希望查看任意时刻的监控。而且其有一个属性是maxUriTags,默认值是100,其作用是限制meterMap里uri的个数,理论上并不会记录太多。

结论

所以到此为止,可以定结论,那就是因为引入了redisson-spring-boot-starter,导致不知情引入了spring-boot-starter-actuator。
因此默认开启了http.client.request指标的监控,关于http.client.request,有一个属性是maxUriTags,默认值是100,其作用是限制meterMap里uri的个数。但是maxUriTags起作用的地方MeterFilter没有生效。
由于maxUriTags没有生效,导致监控信息里的uri因为业务大量的GET请求中存在唯一id,本身就很占内存。压死内存的最后稻草是认证过期和补偿任务。补偿任务为保证及时性一直在频繁执行,而接口的uri里两个变量(token和uniId)导致meterMap里的key不重复,一直在插入,20万回调,token两小时更新一次,持续了两天,最终产生了124万条字符串,被map持有,无法回收。

解决方案

  1. 不需要监控
    直接排除掉spring-boot-starter-actuator
  2. 需要监控但不需要http.client.request指标
    management:
      metrics:
    	web:
    	  client:
    		request:
    		  autotime:
    			enabled: false
    
  3. 需要http.client.request指标
    jar包升到2.5.1或以上
    <dependency>
    	<groupId>org.springframework.boot</groupId>
    	<artifactId>spring-boot-actuator-autoconfigure</artifactId>
    	<version>2.5.1</version>
    </dependency>
    

复现:

新建测试项目

image

相关代码和配置如下

点击查看代码

启动项目通过jconsole查看整个堆的监控和老年代监控分别如下,可以看出老年代一直在增长,并不会回收

image

image

甚至手动触发GC,老年代也回收不了

[Full GC (System.gc()) [Tenured: 195217K->195457K(204800K), 0.3975261 secs] 233021K->195457K(296960K), [Metaspace: 30823K->30823K(33152K)], 0.3976223 secs] [Times: user=0.39 sys=0.00, real=0.40 secs] 

通过jprofiler确定主要是meterMap占据内存了,最多的都是字符串。

image

image

分析

actuator导致rest启动了metrics记录
在使用RestTemplateBuilder构建RestTemplate的时候,会触发懒加载的RestTemplateAutoConfiguration里的RestTemplateBuilderConfigurer,在此期间,config中会注入RestTempalteCustomizer类型的bean。

image

而项目中引用了redisson-spring-boot-starter,从依赖分析可以看出间接引用了actuator相关的包。

image

这导致会在RestTemplateMetricsConfiguration配置类中实例化一个叫做MetricsRestTemplateCustomizer的bean,这个bean会通过上面的restTepalteBuilderConfigurer.configure方法给restTemplate添加拦截器MetricsClientHttpRequestInterceptor。

image

拦截器的intercept方法会在finnally中最终记录此次请求的一些指标

image

io.micrometer.core.instrument.Timer.Builder#register->
io.micrometer.core.instrument.MeterRegistry#time->
io.micrometer.core.instrument.MeterRegistry#registerMeterIfNecessary->
io.micrometer.core.instrument.MeterRegistry#getOrCreateMeter{
meterMap.put(mappedId, m);
}

image

最终存到了是SimpleMeterRegistry这个bean的meterMap中去,这个bean也是actuator-autoconfigure自动注入的

image

但是到目前为止,只是启动了metrics记录,假如maxUriTags有效的话,会在超过100条记录后getOrCreateMeter方法里的accept这里过滤掉,并不会走到下面的meterMap.put(mappedId, m)

image

为什么maxUriTags没有生效?

maxUriTags只在下图这个位置使用了,作用是构建了一个MeterFilter,根据debug我们可以确定bean是产生了的

image

但是在accept这里打上断点,再触发一些请求可以发现,代码并不会走到这里

image

往上跟,没有走到这里的情况只能是filters里没有这个MeterFilter,但我们刚才又确定metricsHttpCLientUriTagFilter这个bean是产生了的,那么就只能是没有添加到filters,也就是没有调用过meterFilter

image

image

从meterFilter往上只有可能是addFilters,一层一层往上最终到了MeterRegistryPostProcessor#postProcessAfterInitialization这个方法
 

image


 

image

image

我们上面说过负责记录的bean叫做simpleMeterRegistry,但是我们在这里打上条件断点发现并没有走到这里

image

找到SimpleMeterRegistry和MeterRegistryPostProcessor这两个bean注入的地方打断点观察,都产生了,且MeterRegistryPostProcessor比SimpleMeterRegistry产生的要早
 

image

image

理论上没问题,但现在确实没走到,所以只能在SimpleMeterRegistry产生的时候在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization打断点,然后可以发现,在simpleMeterRegistry实例化快结束的时候,调用后处理器时this.beanPostProcessors确实没有MeterRegistryPostProcessor
 

image

image

一般来说,postPorcessor的bean注入是在refresh方法的registerBeanPostProcessors中,是早于普通bean的实例化

image

所以simpleMeterRegistry实例化的时候没有MeterRegistryPostProcessor是不合理的情况,定位simpleMeterRegistry是何时实例化的成了关键问题

simpleMeterRegistry的实例化时机

在new SimpleMeterRegistry这里打上断点观察堆栈发现,simpleMeterRegistry是MetricsRepositoryMethodInvocationListener的参数,MetricsRepositoryMethodInvocationListener则是metricsRepositoryMethodInvocationListenerBeanPostProcessor的参数
所以是在实例化metricsRepositoryMethodInvocationListenerBeanPostProcessor这个处理器的时候,因为依赖导致先实例化了simpleMeterRegistry这个bean依赖
 

image


 

image


 

image

image

导致实例化了SimpleMeterRegistry,而这个时候由于没有注册,所以SimpleMeterRegistry在执行applyBeanPostProcessorsAfterInitialization时就执行不到meterRegistryPostProcessor了

image

image

spring已经修复了这个问题,spring-boot-actuator-autoconfigure版本大于2.5.0的都已经没有问题了。解决方案
2.5.1 版本中,添加了一个这个ObjectProvider,在源头上不会立即把依赖的bean初始化完

image

image

2.5.0 版本

image

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

   descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
   if (Optional.class == descriptor.getDependencyType()) {
      return createOptionalDependency(descriptor, requestingBeanName);
   }
   //由于使用了ObjectProvider,所以这里只是返回了一个DependencyObjectProvider
   else if (ObjectFactory.class == descriptor.getDependencyType() ||
         ObjectProvider.class == descriptor.getDependencyType()) {
      return new DependencyObjectProvider(descriptor, requestingBeanName);
   }
   else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
      return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
   }
   else {
   //2.5.0版本中会在这个方法加载入参依赖的bean
      Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
            descriptor, requestingBeanName);
      if (result == null) {
         result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
      }
      return result;
   }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1600898.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[html]一个动态js倒计时小组件

先看效果 代码 <style>.alert-sec-circle {stroke-dasharray: 735;transition: stroke-dashoffset 1s linear;} </style><div style"width: 110px; height: 110px; float: left;"><svg style"width:110px;height:110px;"><cir…

新零售门店、商品、会员管理指标体系总览

新零售&#xff0c;旨在打破传统零售业的边界&#xff0c;引入先进科技和数字化手段&#xff0c;通过整合线上线下渠道&#xff0c;全面提升用户体验&#xff0c;并实现更智能、高效、个性化的零售运营模式。这一模式不仅仅关注销售产品&#xff0c;更注重构建全方位的购物生态…

SpringBoot整合minio服务

这里我选用的是JDK1.8 SpringBoot2.3.12.RELEASE 一、导入依赖 <dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.2.2</version> </dependency> 二、导入工具类 注意&#xff1a;需要在…

eclipse中tomcat环境配置,2024年最新Web前端面试选择题

先自我介绍一下&#xff0c;小编浙江大学毕业&#xff0c;去过华为、字节跳动等大厂&#xff0c;目前阿里P7 深知大多数程序员&#xff0c;想要提升技能&#xff0c;往往是自己摸索成长&#xff0c;但自己不成体系的自学效果低效又漫长&#xff0c;而且极易碰到天花板技术停滞…

【位运算 子集状态压缩】982按位与为零的三元组

算法可以发掘本质&#xff0c;如&#xff1a; 一&#xff0c;若干师傅和徒弟互有好感&#xff0c;有好感的师徒可以结对学习。师傅和徒弟都只能参加一个对子。如何让对子最多。 二&#xff0c;有无限多1X2和2X1的骨牌&#xff0c;某个棋盘若干格子坏了&#xff0c;如何在没有坏…

IP协议如何进行地址管理?

如今&#xff0c;IP协议有两个版本&#xff0c;分别是IPv4和IPv6&#xff0c;IPv4是目前主要应用的版本。IPv4的IP地址是以4个字节的数字来表示的&#xff0c;比如 127.0.0.1。因此&#xff0c;IPv4所能表示IP地址的个数是2^32次方&#xff0c;也就是42亿多个&#xff0c;看起来…

当全连接队列满了,tcp客户端收到服务端RST信令的模拟

当tcp服务端全连接队列满了后&#xff0c;并且服务端也不accept取出连接&#xff0c;客户端再次连接时&#xff0c;服务端能够看到SYN_RECV状态。但是客户端看到的是ESTABLISHED状态&#xff0c;所以客户端自认为成功建立了连接&#xff0c;故其写往服务端写数据&#xff0c;发…

大数据平台搭建2024(三)

三&#xff1a;HBase安装 提前上传hbase安装包至虚拟机 1 上传、解压 tar -zxvf hbase-2.0.0-alpha2-bin.tar.gz -C /hadoop2 修改配置文件 在/hadoop/hbase-2.0.0-alpha2-bin/conf文件夹里 vi /hadoop/hbase-2.0.0-alpha2/conf/hbase-env.sh修改hbase-env.sh文件 export…

【单例模式】饿汉式、懒汉式、静态内部类--简单例子

单例模式是⼀个单例类在任何情况下都只存在⼀个实例&#xff0c;构造⽅法必须是私有的、由⾃⼰创建⼀个静态变量存储实例&#xff0c;对外提供⼀个静态公有⽅法获取实例。 目录 一、单例模式 饿汉式 静态内部类 懒汉式 反射可以破坏单例 道高一尺魔高一丈 枚举 一、单例…

【Conda基础命令】使用conda创建、查看、删除虚拟环境及可能的报错处理

文章目录 前言&#xff08;1&#xff09; 在默认路径下创建一个新的虚拟环境&#xff08;2&#xff09; 查看已有的虚拟环境&#xff08;3&#xff09; 删除已有的虚拟环境&#xff08;谨慎操作&#xff09;&#xff08;4&#xff09;激活虚拟环境&#xff08;5&#xff09;退出…

AI大模型日报#0416:李飞飞《2024年人工智能指数报告》、Sora加入Adobe、李彦宏聊百度大模型之路

​导读&#xff1a; 欢迎阅读《AI大模型日报》&#xff0c;内容基于Python爬虫和LLM自动生成。目前采用“文心一言”生成了每条资讯的摘要。标题: 刚刚&#xff0c;李飞飞团队发布《2024年人工智能指数报告》&#xff1a;10大趋势&#xff0c;揭示AI大模型的“喜”与“忧” 摘…

ubuntu 设置 root 用户密码,创建新用户并赋权限

ubuntu 设置 root 用户密码&#xff0c;创建新用户并赋权限 在适用于 Linux 的 Windows 子系统上运行 Linux GUI 应用&#xff0c; 安装 Ubuntu-20.04 系统&#xff0c;新安装好的系统&#xff0c;设置用户名密码时&#xff0c; root 用户密码默认为空&#xff0c;这时需要设置…

《系统架构设计师教程(第2版)》第9章-软件可靠性基础知识-05-软件可靠性测试

文章目录 1. 概述2. 定义软件运行剖面2.1 软件的使用行为建模2.2 输入域分层2.3 弧上的概率分配2.4 其他注意点 3. 可靠性测试用例设计4. 可靠性测试的实施4.1 测试前检查4.2 注意点4.2 可靠性测试的难点1&#xff09;失效判断的主观性2&#xff09;计算的错误结果不易被发现 4…

企业微信主体的修改方法

企业微信变更主体有什么作用&#xff1f;当我们的企业因为各种原因需要注销或已经注销&#xff0c;或者运营变更等情况&#xff0c;企业微信无法继续使用原主体继续使用时&#xff0c;可以申请企业主体变更&#xff0c;变更为新的主体。企业微信变更主体的条件有哪些&#xff1…

【教程】ubuntu20.04 下配置 Charm-crypto 0.5 实验环境

目录 前言先决条件基本依赖安装准备好 gcc&#xff0c;make 和 perl准备好 m4&#xff0c;flex&#xff0c;bison 和 libssl-dev安装 Python3.x&#xff0c;pip3 和 pyparsing 安装 OpenSSL安装 GMP5.x安装 PBC安装 Charm-crypto5.0安装开发环境检验 Charm-crypto5.0 安装成功参…

【高端电流检测IC储能产品应用方案】耐压45V侧轨的电流检测芯片FP137 应用于电脑电源,开关电源以及多口快充充电器,户外移动电源,适配器,电池充电器等

近年来&#xff0c;随着电子产品的飞速发展&#xff0c;对电流检测精度和可靠性的要求也越来越高。特别是在电脑电源、开关电源以及多口快充充电器、户外移动电源、适配器、电池充电器等领域&#xff0c;对电流检测技术的需求更是日益增长。 电流检测芯片是一种关键的电子元器…

异地组网如何安装?

【天联】是一款强大的异地组网安装工具&#xff0c;可以帮助企业实现远程设备的统一管理和协同办公。以下是【天联】可以应用的一些场景&#xff1a; 零售、收银软件应用统一管理&#xff1a;【天联】可以结合医药、餐饮、商超等零售业的收银软件&#xff0c;实现异地统一管理。…

企业邮箱迁移是什么?如何通过IMAP/POP协议进行邮箱迁移?

使用公司邮箱工作的过程中&#xff0c;公司可能遇到公司规模的扩大或技术架构升级&#xff0c;可能要换公司邮箱。假如马上使用新的公司邮箱&#xff0c;业务处理要被终断。企业邮箱转移是公司更换邮箱不可或缺的一步&#xff0c;不仅是技术操作&#xff0c;更是企业信息安全、…

【机器学习300问】71、神经网络中前向传播和反向传播是什么?

我之前写了一篇有关计算图如何帮助人们理解反向传播的文章&#xff0c;那为什么我还要写这篇文章呢&#xff1f;是因为我又学习了一个新的方法来可视化前向传播和反向传播&#xff0c;我想把两种方法总结在一起&#xff0c;方便我自己后续的复习。对了顺便附上往期文章的链接方…

和鲸科技将参与第五届空间数据智能学术会议并于应急减灾与可持续发展专题论坛做报告分享

ACM SIGSPATIAL中国分会致力于推动空间数据的研究范式及空间智能理论与技术在时空大数据、智慧城市、交通科学、社会治理等领域的创新与应用。ACM SIGSPATIAL中国分会创办了空间数据智能学术会议&#xff08;SpatialDI&#xff09;&#xff0c;分会将于2024年4月25日-27日在南京…