【事故复盘】吐血整理一场线上事故——CPU飙升200%

news2024/11/19 4:47:09

🔍 经验使你在第二次犯相同错误时及时发现。 —— 琼斯

🔖 事故时间:12.7日 17:43 ~ 21:03

🔖 事故影响:服务接口查询慢,用户查看数据转圈

🔖 事故过程:

那是一个宛如平静的下午,一群程序员像往常一样工作着....
突然,在17:43时,运营和运维反馈我们的产品转圈了,且监控发现大量异常发生。
随后我去APM上看了监控,发现service请求响应时间激增。
第一时间怀疑是数据库TiDB的问题(因为就在前几天有个TiDB问题,且还没有修复上线)。

17:50 为了防止大量请求堆积把服务打挂,于是我便进行了服务的重启。
17:55 重启完发现响应时间确实降下来了。
18:20 观察了一阵,发现接口查询已经缓解,于是便先通知了运营。

此时已经算是松了一口气,随后,后端同学便开始排查问题原因。

最开始的排查思路,是这样的:
因为17:30 时突然有大量请求,怀疑是大量查询造成了DB压力,然后造成了查询超时。
于是开始逐一排查激增时间段的这些请求接口。
(我认为这里有一个误区,就当服务整体不可用时,单看某一个或者某几个接口很难排查到问题原因,
应尽量先从全局共享资源去考虑,例如:数据库、缓存、服务CPU、服务内存、网络等)
期间有一个schedule问题,也怀疑过是这个问题造成的,但最终分析不是主要原因。

随着时间的流逝,发现服务每半小时还是有抖动。

不料,20:14 运维和运营同学又相继反馈大量异常和查询慢问题。
20:31,后端同学发现service服务CPU自从12.6发版后异常的高,比平常要翻一倍多。且GC次数也异常增多。
20:33,拉上其他同学,召开紧急会议,确定回退服务对超级店长没有影响时,便进行了版本回滚。
20:49,把服务回滚完毕,继续观察。回滚完CPU果然回到了正常,一切恢复平静,总算松了一口气。
随后,便反馈给运营同学。

12.8 ~ 12.11号,后端同学耗费了大量时间和人力,终于排查到问题根因,以此文章记录事故发生过程及问题排查思路。

🔖 结果根因

根本原因是由于DevOps平台的maven构建近期可能进行了更新,后端同学在构建时发现报错,于是在pom中指定了maven-plugin的版本为项目的springboot版本,也就是2.3.2-RELEASE,怎料spring-boot-loader在2.3.2-REALEASE版本👉👉👉👉👉存在BUG:

当加载不存在类时,会产生大量JarFile对象实例未被引用,从而产生内存泄露,引发服务进行频繁GC而抢占大量的CPU资源,从而引发CPU占用激增,造成服务整体卡顿。
https://github.com/spring-projects/spring-boot/issues/22991
https://github.com/spring-projects/spring-boot/issues/28042

🔖 排查方式

我们花了四天时间,从各个角度对服务进行了问题排查,希望对以后排查问题的同学能有前车之鉴。
下面是不同角度的排查思路:

1.代码

用了一天的时间进行了新发代码的Review,发现除了有个interface版本不一致问题(后来验证该问题不影响主要逻辑),其他业务代码并没有致命问题。
(但最终问题还是因为一行代码引发的血泪事故,这告诫我们——不要忽略任何一行不起眼的代码

2.日志

除了零碎的几个NPM异常,并没有其他的大量错误日志。

3.JVM(调试三件套:IDEA + arthas + jProfiler)

此处涉及大量的底层知识,如果有些你看不懂,证明你该学习了。
3.1 堆内存

起初的堆内存配置是这样的:

java   -jar 
-Dcom.sun.management.jmxremote.authenticate=false 
-Dcom.sun.management.jmxremote.port=1899 
-Dcom.sun.management.jmxremote.ssl=false 
-Xms1024m -Xmx6144m -Xmn2048m -Xss2048K 
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m 

通过Arthas的memory命令监控得到:
在这里插入图片描述

持续监控,发现堆内存的survivor区域持续100%,也就是新生代在不停的进行ParaNewGC,以至于大对象会随着GC次数的增加逐渐进入老年代,而老年代用CSM收集器再会进行回收。

在这里插入图片描述

我画了一下此时的JVM内存结构图:

在这里插入图片描述
这里的话,有一个问题就是初始堆内存分配的1024M(-Xms1024m),有点小的其实。既然Pod已经分配了4~8个g内存,干脆直接把起始内存分配的大一些。通过实践,发现确实有所改善,但并不是引发事故的主要原因。

-Xms4096m -Xmx8000m -Xmn2048m -Xss2048K -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m

3.2 线程

通过arthas的thread --state BLOCKED命令查看阻塞线程,发现确实每隔几秒钟就有几个线程处于BLOCKED阻塞状态,其中占用CPU资源最大的就是Finalizer线程。
在这里插入图片描述
那么什么是Finalizer线程?它都做了什么事情?
https://binkery.com/archives/362.html

其实通过这里,其实已经可以定位到是由于GC大量消耗CPU造成的资源紧张了。

顺着上面的思路,我们生成几份JVM堆内存快照,拷贝下来进行分析:
arthas生成内存快照命令:

heapdump --live /root/jvm.hprof
这里有一个Tips,就是如果你拉下来了大量不同间隔时间内存快照,怎么知道优先分析哪个?
原则是——优先选择文件最大的那个
因为文件越大,证明占用内存越多,越容易得到更多的信息。

通过JProfiler利器可以对快照进行分析:
异常Pod和正常Pod的快照实例数对比,发现异常Pod的Finalizer类很多,相差数倍:
在这里插入图片描述
在这里插入图片描述

继续分析Finalizer这个对象,发现它大量都与JarFile这个类相关联,而JarFile是做什么的呢?
它是spring boot 对JDK的JarFile的一种拓展,用于我们的spring boot Jar包运行时,把包中的依赖包加载到内存中,通过spring boot的类加载器进行加载。

在这里插入图片描述
在这里拓展一些知识:
就是spring boot 打包后的结构(你可以自己解压一个jar包试试):

project/
├── BOOT-INF/                                                                   
│   ├── classes                                 # 当前项目结果文件放置在 classes 路径下
│   │   │   └── application.properties          # 项目中配置文件
│   │   ├── org/                                # 项目中 java 路径下,编译成 class 文件路径
│   │   ├── static/                             # 项目中 resources 路径下的静态文件夹
│   │   └── templates/                          # 项目中 resources 路径下的模板文件夹
│   └── lib/                                    # 项目所依赖的第三方 jar(Tomcat,SpringBoot 等)
├── META-INF/                                                                   
│   └── MANIFEST.MF                             # 清单文件,用于描述可执行 jar 的一些基本信息
└── org/springframework/boot/loader/            # jar 包启动相关的引导
    ├── archive/
    ├── data
    ├── ExectableArchiveLauncher.class
    ├── jar/
    ├── JarLauncher.class
    ├── LaunchedURLClassLoader.class
    ├── LaunchedURLClassLoader$UseFastConnectionExceptionsEnumeration.class
    ├── Launcher.class
    ├── MainMethodRunner.class
    ├── PropertiesLauncher.class
    ├── PropertiesLauncher$1.class
    ├── PropertiesLauncher$ArchiveEntryFilter.class
    ├── PropertiesLauncher$PrefixMatchingArchiveFilter.class
    ├── PropertiesLauncher$ArchiveEntryFilter.class
    ├── util/
    └── WarLauncher.class

MANIFEST.MF结构:

Manifest-Version: 1.0                                       # 清单版本号
Start-Class: org.incoder.start.SpringbootStartApplication   # 项目 main 方法所在的类
Spring-Boot-Classes: BOOT-INF/classes/                      # 项目相关代码在打包后 jar 中的路径
Spring-Boot-Lib: BOOT-INF/lib/                              # 项目中所依赖的第三方 jar 在打包后 jar 中的路径
Spring-Boot-Version: 2.1.6.RELEASE                          # 项目  SpringBoot 版本
Main-Class: org.springframework.boot.loader.JarLauncher     # 当前 jar 文件的执行入口类(main 方法所在的类)
回车换行(在清单文件中,必须有,否则会出错)

再拓展一个知识,spring boot的启动流程:
在这里插入图片描述
要知道我们平时启动一个普通的jar都是通过jdk自带的三种类加载器进行『双亲委派』加载的,
不同的内置classloader的scope 如下:

  • Bootstrap ClassLoader(加载JDK的/lib目录下的类)
  • Extension ClassLoader(加载JDK的/lib/ext目录下的类)
  • Application ClassLoader(程序自己classpath下的类)

但是spring-boot为了适配fatjar结构,只能打破『双亲委派加载原则』,通过使用spring-boot自定义的类加载器加载我们的程序依赖的Jar包。

通过对比正常和异常的service.jar包解压后的文件,基本定位到该问题就是spring-boot-loader导致的了:
在这里插入图片描述
随后我下来了spring-boot的源码,通过对比spring-boot-loader的 2.3.2 和 2.3.4(这个版本正常)版本提交记录,发现了以下问题(可以在github 的issue中搜22991):
在这里插入图片描述
到这里基本确定是JarFile 内存泄露导致的了,且spring-boot在2.3.4版本进行了修复,但值得注意的是,在JDK 11中仍然会存在问题(还好我们用的是JDK8)。

但你以为到这里就分析结束了吗?作为一名程序员,一定要有刨根问底的不懈精神!

于是,我在本地进行了问题复现:
方式也很简单,在service服务中引入 spring-boot-loader源码,通过JarLauncher类启动spring-boot项目,然后模拟大量的请求发送,观察JVM状态,通过捕捉内存快照,果然复现了问题。
在这里插入图片描述
在这里插入图片描述
通过github的issue得知,是加载了不存在的类造成的内存泄露,那我们去查查哪些类没被加载到:
https://github.com/spring-projects/spring-boot/issues/28042
在这里插入图片描述
于是,顺着思路查一下哪些类没有被加载到:
通过,调试源码发现,spring-boot不会对类加载进行异常捕获,于是我手动加上一个try cache,运行。

在这里插入图片描述
果然发现了端倪:
在这里插入图片描述
这个类是从阿里巴巴的druid连接池中调用的:
于是跟踪到druid的源代码,发现这里捕获了异常,但是又不打印异常,你说难受不难受,要我们都没发现有报错信息呢。
在这里插入图片描述
于是再往上跟踪,发现是这里调用的上述方法,这个方法是获取最后一个包发送的时间,用来判断是否需要断开与数据库的连接,为了适配不同的 MySQL 版本,这里先加载第一个类,如果加载不到就加载第二个类。

在这里插入图片描述
通过调试,我终于发现了这里的大坑:
我们的mysql驱动是6.0.3,理论上应该能找到第二个类并加载

在这里插入图片描述
但是!
6.0.3驱动包中的MysqlConnection类名称确实正确,可是包路径却不一致。

在这里插入图片描述
在这里插入图片描述
至此,终于定位到问题的根因,解决办法是把
MySQL驱动包升到8.0.21版本,同时把spring-boot-maven-plugin 升级到最新2.7.5

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>
<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <version>2.7.5</version>
    <configuration>
        <mainClass>com.hualala.report.HualalaReportServiceApplication</mainClass>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
        </execution>
    </executions>
</plugin>

至此,感谢运营和运维同学的及时提醒,感谢每个后端小伙伴的辛苦付出!
希望在今后的日子里,希望我们能慎重对待每一行代码!🎉🎉🎉🎉🎉

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

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

相关文章

128-150-mysql-高级篇-索引及调优篇

115-mysql-高级篇-索引及调优篇&#xff1a; 索引及调优篇 1、索引的创建与设计原则 1. 索引的声明与使用 1.1 索引的分类 从功能逻辑上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。按照物理实现方式&#xff0c;索引可以分…

BUUCTF Web1

[极客大挑战 2019]EasySQL 试了个万能密码就能上去了 flag: flag{8e685472-02e4-440a-b04e-b0d9b6d9c27f} [HCTF 2018]WarmUp 看源码有个source.php <?phphighlight_file(__FILE__);class emmm{public static function checkFile(&$page){$whitelist ["source&qu…

Mysql 创建存储过程和函数及各种例子

Mysql 创建存储过程和函数及各种例子1. Mysql 创建存储过程1.1 前言知识1.1.1 语法结构1.1.2 简单解释1.2 创建存储过程入门例子1.2.1 无参存储过程1.2.1.1 不带变量1.2.1.2 带变量1.2.2 有入参的存储过程1.2.3 有出参的存储过程1.2.4 有入参和存储的存储过程1.2.5 inout的存储…

如何将镜像上传至阿里云?如何从阿里云中拉取自己的镜像?

目录 如何将制作好的镜像上传至阿里云&#xff1f; 一、前期准备 1、注册阿里云账户 2、登录账号 3、配置Docker加速器 4、创建镜像仓库的命名空间&#xff08;私有的&#xff09; 5.创建镜像仓库(创建镜像仓库时要绑定一个代码托管网站&#xff0c;例如&#xff1a;githu…

Zookeeper集群搭建

文章目录前言Zookeeper集群搭建&#xff08;一&#xff09;LeaderFollower模式&#xff08;二&#xff09;下载zookeeeper的压缩包&#xff08;三&#xff09;解压&#xff08;四&#xff09;修改配置文件&#xff08;五&#xff09;添加myid配置&#xff08;六&#xff09;安装…

痞子衡嵌入式:存储器大厂Micron的NOR Flash芯片特殊丝印设计(FBGA代码)

大家好&#xff0c;我是痞子衡&#xff0c;是正经搞技术的痞子。今天痞子衡给大家讲的是存储器大厂Micron的NOR Flash芯片特殊丝印设计(FBGA代码)。 痞子衡之前写过一篇文章 《J-Flash在Micron Flash固定区域下载校验失败的故事》&#xff0c;这篇文章里提及了 Micron 家的串行…

明峰医疗IPO终止:亏损超过14亿元,王瑶法、潘华素夫妇为实控人

近日&#xff0c;上海证券交易所科创板披露的信息显示&#xff0c;明峰医疗系统股份有限公司&#xff08;下称“明峰医疗”&#xff09;向上海证券交易所提交了撤回上市申请文件的申请&#xff0c;保荐人海通证券提交了撤回保荐的申请。 因此&#xff0c;上海证券交易所决定终…

设计模式之创建型模式---单例模式

文章目录1.介绍2.应用场景3.实现3.1 结构3.2 类图3.3 代码示例3.3.1 饿汉式3.3.2 懒汉式3.3.3 双重检验锁3.3.3 静态内部类实现单例3.3.4 枚举类实现单例总结1.介绍 单例模式(singleton) 是指某个类中能生成一个实例&#xff0c;该类提供了一个全局访问点&#xff0c;提供一个唯…

JUC并发编程与源码分析笔记07-volatile与JMM

被volatile修饰的变量有两大特点 可见性、有序性&#xff0c;但是不保证原子性。 当写一个volatile变量时&#xff0c;JMM会把该线程对应的本地内存中的共享变量值立即刷新回主内存中。当读一个volatile变量时&#xff0c;JMM会把该线程对应的本地内存设置为无效&#xff0c;重…

Java开发:汇编语言

一、为什么学习汇编语言 中国人和中国人沟通需要学习汉语 中国人和美国人沟通在会汉语的基础上还要学习英语 那么&#xff0c;人和机器沟通的话需要学习哪些语言呢&#xff1f; 答案是&#xff1a;人类的语言机器语言 但是&#xff0c;机器语言都是由0和1组成&#xff0c;人类…

opencv图像直方图

灰度直方图&#xff1a;从数学上来说&#xff0c;图像直方图是描述图像的各个灰度级的统计特性&#xff0c;它是图像灰度值的函数&#xff0c;统计图像中各个灰度级出现的次数或频率。从图像上来说&#xff0c;灰度直方图是一个二维图像&#xff0c;横坐标为图像中各个像素点的…

git stash命令用法详解(临时存储代码)

1、需求背景 有时候在开发过程中&#xff0c;在一个分支上&#xff08;dev1&#xff09;已经写了一部分代码&#xff0c;但是需要紧急切换到别的分支(dev2)上修改某个代码&#xff0c;这时候不能直接从dev1分支上切换到dev2分支上&#xff0c;提示你需要保存代码。此时dev1分支…

Linux内存模型

sparse内存模型前言1.SPARSEMEM原理:2.vmemmap在虚拟地址空间位置3.virt&#xff0c;phys&#xff0c;page&#xff0c;pfn之间的转换关系3.1内核态虚拟地址和物理内存地址转换关系3.2页帧pfn、物理内存的page指针的关系3.3其他快捷的转换总结前言 Linux中的物理内存被按页框划…

408 考研《操作系统》第二章第五节:信号量机制和用信号量机制实现进程互斥、同步、前驱关系

文章目录教程1. 信号量机制1.1 概念1.2 信号量机制——整型信号量1.3 信号量机制——记录型信号量&#xff08;important&#xff09;1.4 总结2. 用信号量机制实现进程互斥、同步、前驱关系2.1 信号量机制实现进程互斥&#xff08;important&#xff09;2.2 信号量机制实现进程…

java基于SpringBoot的在线答疑系统的研究与实现-计算机毕业设计

项目介绍 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大师生的喜爱&#xff0c;也逐渐进入了每个学生的使用。互联网具有便利性&#xff0c;速度快&#xff0c;效率高&#xff0c;成本低等优点。 因此&#xff0c;构建符合…

【matplotlib】2-使用统计函数绘制简单图形

文章目录使用统计函数绘制简单图形1.函数bar()--用于绘制柱状图2.函数barh()--用于绘制条形图3.函数hist()--用于绘制条形图4.函数pie()--用于绘制饼图5.函数polar()--用于绘制极线图6.函数scatter()--用于绘制气泡图7.函数stem()--用于绘制棉棒图8.函数boxplot()--用于绘制箱线…

openGauss洗冤录 之 copy from

openGauss洗冤录 之 copy from 引子 之前一篇《测评报告&#xff1a;文件导入哪家强&#xff1f;》关于openGauss性能与预期不符的问题留下了个坑&#xff0c;今天回来填坑。 前文提到使用openGauss的copy from导入csv文件耗时是mysql的2倍&#xff0c;是PostgreSQL的6倍&#…

下载nacos-server-1.1.4安装包,使用mvn打包

git官方地址下载nacos-server-1.1.4.zip速度太慢&#xff0c;码云上下载地址没有安装包。采用从码云上下载源码&#xff0c;自行打包。(https://gitee.com/mirrors/Nacos/tree/1.1.4)下载完成之后&#xff0c;进入项目目录如图 下载源码后 &#xff0c;解压 本地在本文件夹 在d…

这十套练习,教你如何用Pandas做数据分析(03)

练习3-数据分组 探索酒类消费数据 步骤1 导入必要的库 运行以下代码 import pandas as pd 步骤2 从以下地址导入数据 运行以下代码 path3 ‘…/input/pandas_exercise/pandas_exercise/exercise_data/drinks.csv’ #‘drinks.csv’ 步骤3 将数据框命名为drinks 运行以下代…

SpringBoot+Vue实现前后端分离的航空售票管理系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…