线上频繁fullgc问题-SpringActuator的坑

整体复盘

一个不算普通的周五中午,同事收到了大量了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;
   }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python练习03

题目 解题思路 Demo58 通过字符串切片来进行反转操作 def _reverse():"""这是一个反转整数的函数"""num input("请输入想要反转的整数")print(num[::-1]) 运行结果 Demo61 首先制作一个判断边长的函数&#xff0c;通过三角形两边…

又成长了,异常掉电踩到了MySQL主从同步的坑!

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

Google Earth Engine 洪水制图 - 使用 Sentinel-1 SAR GRD

Sentinel-1 提供从具有双极化功能的 C 波段合成孔径雷达 (SAR) 设备获得的信息。该数据包括地面范围检测 (GRD) 场景,这些场景已通过 Sentinel-1 工具箱进行处理,以创建经过校准和正射校正的产品。该集合每天都会更新,新获得的资产会在可用后两天内添加。 该集合包含所有 G…

《王者荣耀》Hello Kitty 小兵皮肤完整设置指南

王者荣耀与三丽鸥的联动活动上线了 Hello Kitty 小兵皮肤&#xff0c;让我们的峡谷小兵们也能穿上漂亮的衣服啦&#xff01;这款皮肤极具卡哇伊风格&#xff0c;引起了许多玩家的关注。许多小伙伴都想知道如何使用这款 Hello Kitty 小兵皮肤&#xff0c;今天小编将为大家整理出…

STC单片机与串口触摸屏通讯程序

/***串口1切换通讯测试,单片机发送数据给触摸屏***/ /***切换到3.0 3.1发送数据到串口通信软件 ***/ /***设置温度 加热时间读写EEPROM正确 ***/ #include <REG52.H> //2023 3 5 L330 CODE2667 #include <intrin…

使用JDK自带工具进行JVM内存分析之旅

进行jvm内存分析可以排查存在和潜在的问题。 通过借助jdk自带的常用工具&#xff0c;可以分析大概可能的问题定位以及确定优化方向。 JVM内存分析有很多好处。 内存泄漏排查&#xff1a;JVM 内存泄漏是指应用程序中的对象占用的内存无法被垃圾回收器释放&#xff0c;导致内存…

遥瞻智慧:排水系统远程监控的卓越解决方案

遥瞻智慧&#xff1a;排水系统远程监控的卓越解决方案 在城市脉络的深层肌理中&#xff0c;排水系统犹如一条条隐秘的生命线&#xff0c;默默承载着城市的呼吸与律动。然而&#xff0c;如何以科技之眼&#xff0c;赋予这些无形网络以实时感知、精准调控的能力&#xff0c;使之…

基于机器学习的车辆状态异常检测

基于马氏距离的车辆状态异常检测&#xff08;单一传感器&#xff09; 基于多元自动编码器的车辆状态异常检测 基于单传感器平滑马氏距离的车辆状态异常检测 工学博士&#xff0c;担任《Mechanical System and Signal Processing》等期刊审稿专家&#xff0c;擅长领域&#xff1…

数据分析场景,连号相关业务

连号相关业务 业务场景&#xff1a;现在需要从a列一堆编号中&#xff0c;将连号范围在10以内的数据分别分成一组。 先看实先效果 演示的为db2数据库&#xff0c;需要含有窗口函数&#xff0c;或者可以获取到当前数据偏移的上一位数据 第一步&#xff1a;将A列数据正序第二步…

量子密钥分发系统的设计与实现(三):量子信号的产生、调制及探测技术讨论

之前的文章我们对量子密钥分发系统功能的光路子系统进行了较为全面的分析&#xff0c;我们理解了光路子系统是量子密钥分发系统的基础。本文我们主要探讨下量子信号产生、调制及探测的基础技术&#xff0c;算是一篇承上启下的文章吧&#xff0c;对相关的原理进行探讨&#xff0…

如何使用 ArcGIS Pro 制作边界晕渲效果

在某些出版的地图中&#xff0c;边界有类似于“发光”的晕渲效果&#xff0c;这里为大家介绍一下如何使用ArcGIS Pro 制作这种晕渲效果&#xff0c;希望能对你有所帮助。 数据来源 教程所使用的数据是从水经微图中下载的行政区划数据&#xff0c;除了行政区划数据&#xff0c…

wsl2 Ubuntu子系统内存只有一半的解决办法

物理机的内存是64G&#xff0c;在wsl2安装完Ubuntu20.04后&#xff0c;输入命令&#xff1a; free -g 发现只有32G&#xff0c;原因是默认只能获得物理机一半的内存&#xff1a; WSL 中的高级设置配置 | Microsoft Learn 因此可手动修改为与物理机同等大小&#xff1a; 1&a…

如何解决DDoS攻击?群联科技做出回答。

DDoS攻击&#xff08;分布式拒绝服务攻击&#xff09;是一种恶意利用多台傀儡机协同发起大规模网络流量&#xff0c;旨在压垮目标系统或网络资源&#xff0c;使其无法正常服务的网络攻击手段。由于现代计算机和网络性能的提升&#xff0c;单点发起的DoS攻击已难以奏效&#xff…

Day01——NestJS学习之了解、安装、运行

什么是 Nest.js&#xff1f; NestJs 官方简介: Nest (NestJS) 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的开发框架。它利用 JavaScript 的渐进增强的能力&#xff0c;使用并完全支持 TypeScript &#xff08;仍然允许开发者使用纯 JavaScript 进行开发&#x…

数据仓库作业五:第8章 关联规则挖掘

目录 第8章 关联规则挖掘作业题 第8章 关联规则挖掘 作业题 1、设4-项集 X { a , b , c , d } X\{a,b,c,d\} X{a,b,c,d}&#xff0c;试求出由 X X X 导出的所有关联规则。 解&#xff1a; 首先生成项集的所有非空真子集。这包括&#xff1a; { a } , { b } , { c } , {…

ansible执行mysql脚本

目录 概述实践环境要求ansible yml脚本命令离线包 概述 ansible执行mysql脚本 实践 官网文档 环境要求 环境需要安装以下内容: 1.mysql客户端(安装了mysql即会有)2.安装MySQL-python (Python 2.X) 详细插件安装链接 ansible yml脚本 关键代码如下&#xff1a; # 剧本…

ROS2学习笔记(一) 基本概念

1. Node 节点 节点: 完成具体功能的模块 相关命令 #运行命令 ros2 run <package_name> <executable_name>#当前节点查询查询 ros2 node list#重映射 Remapping ros2 run <package_name> <executable_name> --ros-args --remap __node:<node_na…

KaiwuDB CTO 魏可伟:AIoT,用行业定义数据库

4月12日&#xff0c;由中国 DBA 联盟&#xff08;ACDU&#xff09;与墨天轮社区联合主办的第十三届数据技术嘉年华&#xff08;DTC 2024&#xff09;于北京盛大召开。KaiwuDB CTO 魏可伟受邀发表《智创当下&#xff0c;KaiwuDB 从多模到 AI 的探索实践》主题演讲&#xff0c;向…

Axure如何实现限制选择项数量的交互

大家经常会看到这样的功能设计&#xff1a;可以多选&#xff0c;但是限制多选。比如某招聘网站城市的选择只能选择5个。再选择第6个的时候会提示最多只能选择5项。 这个效果是我们经常会遇到的&#xff0c;在工作中也经常会遇到需要制作这样的效果。今天我们一起来看看&#xf…

Mac M3 安装Ollama和llama3,本地部署LobeChat和刘皇叔聊三国!

OllamaLobeChat&#xff0c;本地部署聊天助手 Ollama安装下载OllamaOllama常用指令和链接运行OllamaAPI 交互Ollama基于Llama 3角色扮演 LobeChat安装首先安装docker安装LobeChat的docker 镜像和运行 Ollama安装 下载Ollama 网址&#xff1a;https://ollama.com/ 支持macOS、…