一次Fegin CPU占用过高导致的事故

打印 上一主题 下一主题

主题 2302|帖子 2302|积分 6906

记载一下
一次应用事故分析、排查、处理
   背景介绍

9号上午收到CPU告警,同时业务反馈依赖该服务的上游服务接口响应耗时太长
  1. 应用告警-CPU使用率 告警变更
  2. 【WARNING】项目XXX,集群qd-aliyun,分区bbbb-prod,应用customer,实例customer-6fb6448688-m47jz, POD实例CPU请求使用率 >= 90.000000% 当前值138.4971051199925%
  3. 发生时间:2024/10/09 11:17:33
  4. 项目XXX,集群qd-aliyun,分区bbbb-prod,应用customer,实例customer-6fb6448688-28pvs, POD实例CPU请求使用率 >= 90.000000% 当前值157.7076205766934%告警已恢复
  5. 发生时间: 2024/10/09 11:06:33
  6. 恢复时间: 2024/10/09 12:24:33
复制代码
服务访问量

单实例峰值QPS100左右
   为啥要关注QPS,由于QPS100不应该消耗这么多CPU啊,而且请求、响应体都不大。
  


POD监控

POD配额


  • CPU请求 2 Core CPU上限 3 Core
  • 内存请求 7GiB 内存上限 9GiB



从图中可以看出


  • CPU负载不停很高
  • TCP链接及线程数从11点40开始陡峭上升
Arms

看下Trace监控发现,耗时重要是customer通过fegin调用外围接口导致的。

临时方案

临时处理方案:扩实例并增加CPU配置。
根因分析

   此处略过排查三方接口跟开放平台网关的过程,此处的结论是:依赖的三方接口跟开放平台网关没有题目。
为啥会先排查三方接口跟开放平台网关是由于中Trace上来看是调用三方接口响应时间过长。

  从Arms图看可以看出


  • CPU耗时会合在fegin调用的Decoder、Encoder
  • Decoder、Encoder耗时都会合在

    • HttpMessageConverters#getDefaultConverters()=>
    • WebMvcConfigurationSupport#addDefaultHttpMessageConverters=>
    • …(具体调用链看下方摘要)

  1. feign.ReflectiveFeign$BuildTemplateByResolvingArgs.create(Object[]) (14.37%, 1.43 minutes)
  2. feign.ReflectiveFeign$BuildEncodedTemplateFromArgs.reesolve(Object[], RequestTemplate, Map) (14.37%, 1.43minutes)
  3. org.springframework.cloud.openfeign.support.SpringEndcoder.encode(Object, Type, RequestTemplate) (14.28%,1.42 minutes)
  4. com.jiankunking.common.core.feign.FeignClientsConfig$$ambda$938.56729293.get0bject() (13.98%, 1.39 minutes
  5. com.jiankunking.common.core.feign.FeignClientsConfig.lambda$feignEncoder$2() (13.98%, 1.39 minutees)
  6. org.springframework.boot.autoconfigure.http.HttpmessaageConverters.<init>(HttpMessageConverter[]) (12.03%,1.19 minutes)
  7. prg.springframework.boot.autoconfigure.http.Http.HttpMessageConverters.<init>(Collection) (12.03%, 119 minutes)
  8. org.springframework.boot.autoconfigure.http.HttpmessaageConverters.<init>(boolean, Collection) (12.03%, 1.19 minutes)
  9. prg.springframework.boot.autoconfigure.http.Http.HttpMessageConverters.getDefaultConverters()(12.02%, 1.19 minutes
  10. org.springframework.boot.autoconfigure.http.HttpmessageConverters$1.defaultMessageConverters() (12.02%, 119 minutes)
  11. org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.getMessageConverters() (12.02%, 1.19 minutes)
  12. org.springframework.web.servlet.config.annotation. WebMvcConfigurationSupport.addDefaultHttpMessageConverters(List) (12.02%, 1
  13. org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.build() (5.93%, 0.59 minutes)
  14. org.springframework.http.converter.json.Jackson2ObjectMapperBuilder.configure(ObjectMapper)(5.91%, 0.59 minutes)
  15. org.springframework.http.converter.json.Jackson2Objec:tMapperBuilder.registerWellKnownModulesIfAvailable(Map)(5.89%,0.58 min
  16. org.springframework.util.ClassUtils.forName(String, CClassLoader)(5.84%, 0.58 minutes)
  17. java.lang.Class.forName(String, boolean, Classloader) (5.83%, 0.58 minutes)
  18. java.lang.Class.forName0(String, boolean, ClassLoader, Class) (5.83%, 0.58 minutes)
  19. ......
复制代码
自定义Encoder、Decoder

Encoder

看下jiankunking.common.core.feign.FeignClientsConfig中的Encoder
  1.     public Encoder feignEncoder() {
  2.         ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(new RMappingJackson2HttpMessageConverter());
  3.         return new SpringEncoder(objectFactory);
  4.     }
  5.     public class RMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
  6.         public RMappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
  7.             super(objectMapper);
  8.             List<MediaType> mediaTypes = new ArrayList<>();
  9.             mediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE));
  10.             mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
  11.             setSupportedMediaTypes(mediaTypes);
  12.         }
  13.         RMappingJackson2HttpMessageConverter() {
  14.             List<MediaType> mediaTypes = new ArrayList<>();
  15.             mediaTypes.add(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8_VALUE));
  16.             mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + ";charset=UTF-8"));
  17.             setSupportedMediaTypes(mediaTypes);
  18.         }
  19.     }   
复制代码
Decoder

看下jiankunking.common.core.feign.FeignClientsConfig中的Decoder
  1.     public Decoder feignDecoder() {
  2.         HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
  3.         ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(jacksonConverter);
  4.         return new ResponseEntityDecoder(new RSpringDecoder(objectFactory));
  5.     }
  6.     public ObjectMapper customObjectMapper() {
  7.         ObjectMapper objectMapper = new ObjectMapper();
  8.         objectMapper.registerModule(new StringToDateModule());
  9.         objectMapper.configure(JsonParser.Feature.ALLOW_COMMENTS, true);
  10.         objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
  11.         objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
  12.         objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true);
  13.         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
  14.         return objectMapper;
  15.     }
复制代码
Google了一下:‘spring feign encode jackson cpu usage high’
=> https://segmentfault.com/a/1190000043037032
=> https://mp.weixin.qq.com/s/RuqltkN9VdVQ1K3GKuJ-Gw
=> https://meantobe.github.io/2019/12/21/ClassLoader/
源码分析

查看registerWellKnownModulesIfAvailable处的代码
  1.         @SuppressWarnings("unchecked")
  2.         private void registerWellKnownModulesIfAvailable(Map<Object, Module> modulesToRegister) {
  3.                 try {
  4.                         Class<? extends Module> jdk8ModuleClass = (Class<? extends Module>)
  5.                                         ClassUtils.forName("com.fasterxml.jackson.datatype.jdk8.Jdk8Module", this.moduleClassLoader);
  6.                         Module jdk8Module = BeanUtils.instantiateClass(jdk8ModuleClass);
  7.                         modulesToRegister.put(jdk8Module.getTypeId(), jdk8Module);
  8.                 }
  9.                 catch (ClassNotFoundException ex) {
  10.                         // jackson-datatype-jdk8 not available
  11.                 }
  12.                 try {
  13.                         Class<? extends Module> javaTimeModuleClass = (Class<? extends Module>)
  14.                                         ClassUtils.forName("com.fasterxml.jackson.datatype.jsr310.JavaTimeModule", this.moduleClassLoader);
  15.                         Module javaTimeModule = BeanUtils.instantiateClass(javaTimeModuleClass);
  16.                         modulesToRegister.put(javaTimeModule.getTypeId(), javaTimeModule);
  17.                 }
  18.                 catch (ClassNotFoundException ex) {
  19.                         // jackson-datatype-jsr310 not available
  20.                 }
  21.                 // Joda-Time present?
  22.                 if (ClassUtils.isPresent("org.joda.time.LocalDate", this.moduleClassLoader)) {
  23.                         try {
  24.                                 Class<? extends Module> jodaModuleClass = (Class<? extends Module>)
  25.                                                 ClassUtils.forName("com.fasterxml.jackson.datatype.joda.JodaModule", this.moduleClassLoader);
  26.                                 Module jodaModule = BeanUtils.instantiateClass(jodaModuleClass);
  27.                                 modulesToRegister.put(jodaModule.getTypeId(), jodaModule);
  28.                         }
  29.                         catch (ClassNotFoundException ex) {
  30.                                 // jackson-datatype-joda not available
  31.                         }
  32.                 }
  33.                 // Kotlin present?
  34.                 if (KotlinDetector.isKotlinPresent()) {
  35.                         try {
  36.                                 Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
  37.                                                 ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
  38.                                 Module kotlinModule = BeanUtils.instantiateClass(kotlinModuleClass);
  39.                                 modulesToRegister.put(kotlinModule.getTypeId(), kotlinModule);
  40.                         }
  41.                         catch (ClassNotFoundException ex) {
  42.                                 if (!kotlinWarningLogged) {
  43.                                         kotlinWarningLogged = true;
  44.                                         logger.warn("For Jackson Kotlin classes support please add " +
  45.                                                         ""com.fasterxml.jackson.module:jackson-module-kotlin" to the classpath");
  46.                                 }
  47.                         }
  48.                 }
  49.         }
复制代码
可以看到其逻辑为若classpath中有JodaTime的LocalDate,则加载Jackson对应的JodaModule.LaunchedURLClassLoader.
为啥没有怀疑jdk8ModuleClass、javaTimeModuleClass这两个地方呢?由于common包中已经依赖了下面两个包
  1.     compile "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${v.jacksonDatatype}"
  2.     compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:${v.jacksonDatatype}"
复制代码
那么解决方案就很清晰了
解决方案

制止ClassLoader反复加载

将这个依赖添加到工程中。加载一次后,再次调用可以通过findLoadedClass获得,镌汰加载类导致的资源消耗。
  1. <dependency>
  2.     <groupId>com.fasterxml.jackson.datatype</groupId>
  3.     <artifactId>jackson-datatype-joda</artifactId>
  4.     <version>x.x.x</version>
  5. </dependency>
复制代码
制止HttpMessageConverters重复初始化

  1.     public Decoder feignDecoder() {
  2.         HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(customObjectMapper());
  3.         ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(false, Collections.singletonList(jacksonConverter));
  4.         return new ResponseEntityDecoder(new RSpringDecoder(objectFactory));
  5.     }
  6.     public Encoder feignEncoder() {
  7.         HttpMessageConverter jacksonConverter = new RMappingJackson2HttpMessageConverter(customObjectMapper());
  8.         ObjectFactory<HttpMessageConverters> objectFactory = () -> new HttpMessageConverters(false, Collections.singletonList(jacksonConverter));
  9.         return new SpringEncoder(objectFactory);
  10.     }
复制代码
总结

各人在自定义 Feign 的编解码器时,假如用到了 SpringEncoder / SpringDecoder,应制止 HttpMessageConverters 的重复初始化。假如不需要使用那些默认的 HttpMessageConverter,可以在初始化 HttpMessageConverters 时将第一个入参设置为 false,从而不初始化那些默认的 HttpMessageConverter。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

立聪堂德州十三局店

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表