CPU飙高问题排查过程(1)

✅什么是CPU利用率?怎么算的? 问题发现 前段时间我们新上了一个新的应用,因为流量一直不大,集群QPS大概只有5左右,写接口的rt在30ms左右。 因为最近接入了新的业务,业务方给出的数据是日常QPS可以达到2000,大促峰值QPS可能会达到1万。 所以,为了评估水位,我们进行了一次压测。压测在预发布环境执行。压测过程中发现,当单机QPS达到200左右时,接口的rt没有明显变化,但是CPU利用率急剧升高,直到被打满。 压测停止后,CPU利用率立刻降了下来。 于是开始排查是什么导致了CPU的飙高。 问题排查与解决 在压测期间,登录到机器,开始排查问题。 本案例的排查过程使用的阿里开源的Arthas工具进行的,不使用arthas,使用JDK自带的命令也是可以。 在开始排查之前,可以先看一下CPU的使用情况,最简单的就是使用top命令直接查看: 1 2 3 4 5 6 7 8 9 10 11 12 13 top - 10:32:38 up 11 days, 17:56, 0 users, load average: 0.84, 0.33, 0.18 Tasks: 23 total, 1 running, 21 sleeping, 0 stopped, 1 zombie %Cpu(s): 95.5 us, 2.2 sy, 0.0 ni, 76.3 id, 0.0 wa, 0.0 hi, 0.0 si, 6.1 st KiB Mem : 8388608 total, 4378768 free, 3605932 used, 403908 buff/cache KiB Swap: 0 total, 0 free, 0 used. 4378768 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 3480 admin 20 0 7565624 2.9g 8976 S 241.2 35.8 649:07.23 java 1502 root 20 0 401768 40228 9084 S 1.0 0.5 39:21.65 ilogtail 181964 root 20 0 3756408 104392 8464 S 0.7 1.2 0:39.38 java 496 root 20 0 2344224 14108 4396 S 0.3 0.2 52:22.25 staragentd 1400 admin 20 0 2176952 229156 5940 S 0.3 2.7 31:13.13 java 235514 root 39 19 2204632 15704 6844 S 0.3 0.2 55:34.43 argusagent 236226 root 20 0 55836 9304 6888 S 0.3 0.1 12:01.91 systemd-journ 可以看到,进程ID为3480的Java进程占用的CPU比较高,基本可以断定是应用代码执行过程中消耗了大量CPU,接下来开始排查具体是哪个线程,哪段代码比较耗CPU。 ...

March 22, 2026 · 2 min · santu

Load飙高问题排查过程

✅什么是Load(负载)? 问题发现 我们有一个应用,平常都好好的,运行的都比较平稳,但是每次应用在发布过程中,刚刚重启好的机器经常会有cpu利用率和load飙高的现象,进而导致我们应用的RT变高,很多调用方反馈有大量超时。 应用刚刚发布后的几分钟内,CPU飙高到70%,Load飙高到11,并持续几分钟后就好了。 问题定位 这个问题的排查过程挺复杂的,刚开始怀疑是应用代码的问题,但是经过多方检查,打印各种堆、栈的dump,分析各种火焰图,都没有看到有什么异常。 后来又怀疑虚拟机、容器、宿主机是不是有问题,前后换了机器配置、docker镜像都没有什么变化。 后来有怀疑JDK的版本、堆内存设置、垃圾收集器等可能有关?但是排查下来也都发现并没有什么特别的问题。 最后,经过多方讨论,定位到和JIT优化有关。 我们知道, Java最开始是一种解释型语言,他的代码需要先通过javac编译成class文件,然后再通过解释器将字节码将其翻译成对应的机器指令, 逐条读入, 逐条解释翻译。 但是这个过程太慢了,于是hotspot提出了JIT优化,JIT优化器会基于热点代码检测,把热点代码直接翻译成机器语言,方便后续直接执行。大大提升了效率。 ✅简单介绍一下JIT优化技术? 理解了JIT编译的原理之后,其实可以知道,JIT优化是在运行期进行的,并且也不是Java进程刚一启动就能优化的,是需要先执行一段时间的,因为他需要先知道哪些是热点代码。 所以,在JIT优化开始之前,我们的所有请求,都是要经过解释执行的,这个过程就会相对慢一些。 而且,如果你们的应用的请求量比较大的的话,这种问题就会更加明显,在应用启动过程中,会有大量的请求过来,这就会导致解释器持续的在努力工作。 一旦解释器对CPU资源占用比较大的话,就会间接的导致CPU、LOAD等飙高,导致应用的性能进一步下降。 这也是为什么很多应用在发布过程中,会出现刚刚重启好的应用会发生大量的超时问题了。 而随着请求的不断增多,JIT优化就会被触发,这就是使得后续的热点请求的执行可能就不需要在通过解释执行了,直接运行JIT优化后缓存的机器码就行了。 问题解决 解决这个问题主要有两种思路: 1、提升JIT优化的效率 2、降低瞬时请求量 在提升JIT优化效率的设计上,大家可以了解一下阿里研发的JDK——Dragonwell。 这个相比OpenJDK提供了一些专有特性,其中一项叫做JwarmUp的技术就是解决JIT优化效率的问题的。 这个技术主要是通过记录Java应用上一次运行时候的编译信息到文件中,在下次应用启动时,读取该文件,从而在流量进来之前,提前完成类的加载、初始化和方法编译,从而跳过解释阶段,直接执行编译好的机器码。 除了针对JDK做优化之外,还可以采用另外一种方式来解决这个问题,那就是做预热。 很多人都听说过缓存预热,其实思想是类似的。 就是说在应用刚刚启动的时候,通过调节负载均衡,不要很快的把大流量分发给他,而是先分给他一小部分流量,通过这部分流量来触发JIT优化,等优化好了之后,再把流量调大。 我们内部有一个配置开关,可以帮助我们开启JwarmUp,配置上之后,就发现问题基本解决了。

March 22, 2026 · 1 min · santu

OOM问题排查过程

✅OutOfMemory和StackOverflow的区别是什么 问题发现 某次双十一之前,线上突然出现大量报警。服务的调用方很多人也反馈请求超时的情况很多: 后来发现同时线上存在GC耗时特别长,GC也特别频繁的情况 问题定位 出现了这个现象之后,我们第一时间取拉了一下线上的堆DUMP。然后用我们的分析工具进行内存泄漏分析。 如果没有工具,可以用jmap和jhat分析。注意在GC前拉dump 可以看到,我们的一个LinkedList占用了73%的内存,很容易识别出来是发生了内存泄漏的问题。 该代码于问题发生前几日上线,目的是实现批量支付透支多笔算法,根据代码定位是这个算法存在问题,为了止血,第一时间通过配置中心将算法全部切回单笔算法,保证额度中心系统稳定后,继续进行分析。 根据崩溃线程可以看到程序在不断递归,递归的过程中内存不足了。 然后顺腾摸瓜,找到了具体的case, 然后定位到出现问题的代码,该算法输入一组订单input,以及该批订单最高可用额度maxQuota,返回最接近maxQuota的一组订单,实现用户可用额度应用尽用。 本质是一个nums与goal的问题,开发这个代码的同学认为这个问题在leetcode上是一道真题,https://leetcode.cn/problems/closest-subsequence-sum/solution/by-mountain-ocean-1s0v/,我们解决这个问题的思路是分治加回溯,预计空间复杂度是o(n*(2^(2/n))), 但是跟leetcode的需求不同的在于需求记录最接近目标值的一组订单,因为最后要返回的是订单序列,这也是为什么空间复杂度打爆的原因。 出问题的代码就不贴了,主要是开发者只考虑了时间复杂度,没考虑空间复杂度,导致OOM 问题解决 定位到问题之后,就做了一把简单的优化,把"算法"干掉了,通过简单的方式其实就能实现。 这个问题告诫我们,不要炫技!

March 22, 2026 · 1 min · santu

POI导致内存溢出排查

问题发现 在一个内部平台中,系统提供了一个功能,允许管理员导出报表数据为Excel文件。随着平台用户量的增长和数据量的积累,管理员开始报告导出操作时系统频繁崩溃。 监控系统显示,在执行导出操作期间,应用服务器的内存使用量急剧上升,最终导致Java虚拟机抛出 OutOfMemoryError 异常。 问题排查 首先,我们需要确认是哪部分代码或对象导致了内存溢出。这可以通过分析堆转储(Heap Dump)来实现。当 OutOfMemoryError 发生时,可以配置JVM自动生成堆Dump文件,或者使用诸如 jmap 工具手动生成堆Dump。 1 2 3 -XX:+HeapDumpOnOutOfMemoryError 配置OOM时自动生成Dump文件 1 jmap -dump:live,format=b,file=heapdump.hprof <pid> 这里面建议在GC前和GC后分别获取Dump,如果只获取一次,一定要知道是GC前的还是GC后的。不然很容易分析错误。 接下来使用堆分析工具MAT加载和分析 heapdump.hprof 文件。通过分析,我们发现存在大量的 org.apache.poi.xssf.usermodel.XSSFWorkbook 实例占用了大部分堆内存。 (当时的分析截图未保存,这个截图是我从网上找的,几乎和我们当时现象一模一样) 定位到使用 Apache POI 的代码部分,发现系统在处理大量数据时,是将所有数据一次性加载到 XSSFWorkbook 对象中的。 我们的业务代码中确实有使用XSSF的部分代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public ByteArrayOutputStream exportData(List<Data> dataList) { XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("Data"); int rownum = 0; for (Data data : dataList) { Row row = sheet.createRow(rownum++); // ... 填充数据行 ... } ByteArrayOutputStream out = new ByteArrayOutputStream(); workbook.write(out); return out; } 问题解决 问题定位到了,解决起来就简单了。根据下文: ...

March 22, 2026 · 1 min · santu

RocketMQ消费堆积问题排查

问题现象 负责的业务中有一个应用因为特殊原因,需要修改消息配置(将Spring Cloud Stream 改为 RocketMQ native),修改前和修改后的配置项如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring.cloud.stream.bindings.consumerA.group=CID_CONSUMER_A spring.cloud.stream.bindings.consumerA.contentType=text/plain spring.cloud.stream.bindings.consumerA.destination=CONSUMER_A_TOPIC spring.cloud.stream.rocketmq.bindings.consumerA.consumer.tags=CONSUMER_A_TOPIC_TAG spring.cloud.stream.bindings.consumerB.group=CID_CONSUMER_A spring.cloud.stream.bindings.consumerB.contentType=text/plain spring.cloud.stream.bindings.consumerB.destination=CONSUMER_B_TOPIC spring.cloud.stream.rocketmq.bindings.consumerB.consumer.tags=CONSUMER_B_TOPIC_TAG spring.cloud.stream.bindings.consumerC.group=CID_CONSUMER_A spring.cloud.stream.bindings.consumerC.contentType=text/plain spring.cloud.stream.bindings.consumerC.destination=CONSUMER_C_TOPIC spring.cloud.stream.rocketmq.bindings.consumerC.consumer.tags=CONSUMER_C_TOPIC_TAG 1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring.rocketmq.consumers[0].consumer-group=CID_CONSUMER_A spring.rocketmq.consumers[0].topic=CONSUMER_A_TOPIC spring.rocketmq.consumers[0].sub-expression=CONSUMER_A_TOPIC_TAG spring.rocketmq.consumers[0].message-listener-ref=consumerAListener spring.cloud.stream.bindings.consumerB.group=CID_CONSUMER_A spring.cloud.stream.bindings.consumerB.contentType=text/plain spring.cloud.stream.bindings.consumerB.destination=CONSUMER_B_TOPIC spring.cloud.stream.rocketmq.bindings.consumerB.consumer.tags=CONSUMER_B_TOPIC_TAG spring.cloud.stream.bindings.consumerC.group=CID_CONSUMER_A spring.cloud.stream.bindings.consumerC.contentType=text/plain spring.cloud.stream.bindings.consumerC.destination=CONSUMER_C_TOPIC spring.cloud.stream.rocketmq.bindings.consumerC.consumer.tags=CONSUMER_C_TOPIC_TAG 但是当机器发布一半后开始灰度观察的时候,出现了消息堆积问题: ...

March 22, 2026 · 1 min · santu

RT飙高问题排查过程

问题发现 本次是针对某个产品做风控定价的回流导入,相当于把风控策略基于风险测算出的价格通过数据同步的方式回流到我们的定价配置表中。 本次总计回流的数据大概有16万条,在此之前,我们的定价配置表中有不到2万条数据,所以,在回流之前的方案设计中,我们就考虑到可能因为数据量增大带来的一些问题了。所以,在回流过程中,我们在第一批回流了2万左右条数据之后,没有继续操作 ,开始观察线上的监控报警情况。 经过观察发现,我们的报价接口的 RT 有明显的增加,时间点和我们做数据回流的时间点刚好吻合: 问题排查 所以第一时间开始排查接口的链路情况了,很明显这次 RT 升高就是因为我们的数据回流导致的,于是首先登录服务器通过 Arthas (使用手册:https://arthas.aliyun.com/doc/index.html )看了下接口耗时的主要操作,验证下是不是在查询定价配置表的地方。 先下载 arthas: 1 curl -L http://start.alibaba-inc.com/install.sh | sh ...

March 22, 2026 · 1 min · santu

回表导致慢 SQL 问题排查

问题发现 线上突然疯狂报警,某个接口的调用成功率暴跌,失败数突增。 经过一番排查,发现底层数据库CPU已经被拉到了100%,在进行了紧急扩容后,开始进行根因分析。 问题排查 通过数据库的诊断工具,发现有一个慢SQL,执行了很多次,并且耗时比较长,找到的SQL具体如下: 这个用户确实是一个大买家、表中一共100多万条数据,他自己就有几十万单(新的业务模式,之前也没见过) 并且在分析过程中,发现这个 SQL 有的时候走索引,有的时候不走索引,并且走不走索引都很慢。于是看一下成本消耗到底在哪里。 ✅MySQL的优化器的索引成本是怎么算出来的? 1 2 3 4 5 6 7 EXPLAIN FORMAT=json SELECT ifnull(SUM(sum_payment), 0) AS order_actl_pay_fee_sum , COUNT(1) AS order_num FROM xxxx_payment_message WHERE byr_id = 221xxxx478 AND biz_date >= '1719391999351' AND biz_date <= '1719478399351'; 得到的结果如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 { "query_block": { "select_id": 1, "cost_info": { "query_cost": "139147.15" # 总成本 }, "table": { "table_name": "xxxx_payment_message", "access_type": "ALL", "possible_keys": [ "byr_id_date_index", "biz_date_index" ], "rows_examined_per_scan": 1364869, "rows_produced_per_join": 448231, "filtered": "32.84", "cost_info": { "read_cost": "94323.95", # IO Cost(Engine Cost) "eval_cost": "44823.20", # CPU Cost(Server Cost) "prefix_cost": "139147.15", # 总成本 "data_read_per_join": "458M" }, "used_columns": [ "byr_id", "biz_date", "sum_payment" ] } } } 这条 SQL 不走索引,实际耗时675ms,预估扫描行数1374150行 ,cbo为139147.15。 ...

March 22, 2026 · 2 min · santu

如何使用jstack分析死锁

✅什么是死锁,如何解决? 先来写一段死锁的程序: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package javaCommand; /** * @author hollis */ public class JStackDemo { public static void main(String[] args) { Thread t1 = new Thread(new DeadLockclass(true));//建立一个线程 Thread t2 = new Thread(new DeadLockclass(false));//建立另一个线程 t1.start();//启动一个线程 t2.start();//启动另一个线程 } } class DeadLockclass implements Runnable { public boolean falg;// 控制线程 DeadLockclass(boolean falg) { this.falg = falg; } public void run() { /** * 如果falg的值为true则调用t1线程 */ if (falg) { while (true) { synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); } } } } /** * 如果falg的值为false则调用t2线程 */ else { while (true) { synchronized (Suo.o2) { System.out.println("o2 " + Thread.currentThread().getName()); synchronized (Suo.o1) { System.out.println("o1 " + Thread.currentThread().getName()); } } } } } } class Suo { static Object o1 = new Object(); static Object o2 = new Object(); } 当我启动该程序时,我们看一下控制台: ...

March 22, 2026 · 2 min · santu

如何排查网络问题?

典型回答 网络问题是指在典型的C/S或B/S通信中,客户端或浏览器向服务端发送数或接受数据的时候,因为网络不稳定或接收方异常导致的数据包丢失、无法连接等问题。通常情况下TCP会通过重试的机制确保数据包在一段时间后到达接收方。如果是UDP需要应用自身实现重传机制确保在出现丢包后可重新发送。 注意本文讨论的内容基于Linux操作系统 我们可以使用一些基本的命令检测是否出现丢包、目标端口异常: ping 检测本机到目标主机是否通畅 telnet 检测本机到目标主机端口是否通畅 traceroute 检测本机到目标主机经过的路由是否通畅 ping ping命令会发出ICMP类型的数据包,使用方法: 1 ping 目标ip/域名 # 如果不使用-c参数会一直ping,按ctrl+c停止 注意:ping的时候是不需要加"http"或者"https"的,直接ping域名。 ping存在的问题: 由于ping使用ICMP协议,一些主机防火墙会丢弃ICMP报文,所以会导致ping不同 ping只是探测本机到目标主机的通路,并不探测目标端口是否有效 ✅ping的原理是什么? telnet telnet 是一个网络协议,同时也是一个使用该协议的命令行工具。它最初被设计用于远程登录到服务器,但由于其安全性问题(主要是信息传输不加密),在现代网络环境中已经较少用于远程登录。然而,telnet 命令仍被广泛用作一种简单的工具来测试网络连接,特别是用于检查指定的IP地址和端口是否可达。 1 telnet 目标ip/域名 端口 telnet存在的问题: 无法感知到达目标主机的响应耗时 交互式的体验,不太适合写在shell脚本中 telnet 不支持加密,发送的数据(包括登录凭据)都是明文。因此,它不适合用于任何涉及敏感信息的场景。 traceroute traceroute 是一个网络诊断工具,用于显示数据包从源主机到目标主机之间经过的路由(路径)。它可以帮助诊断数据包传输中的问题,例如确定网络延迟的位置或查看数据包的路径。 1 2 3 4 5 6 7 traceroute 目标ip/域名 #traceroute常用参数: 1. -w 秒数 等待远端主机响应的时间,高于这个时间算为超时 2. -i 网卡 使用指定网卡发出数据包 3. -g 网关ip 设置经过哪个网关发出数据包 4. -m 跳数 设置经过路由的最大跳数 traceroute主要用于跟踪路由,排查数据包到达目标主机之前产生的问题。 ...

March 22, 2026 · 1 min · santu

慢SQL问题排查

问题发现 线上有一个反欺诈相关的定时任务执行连续多次失败: 于是紧急排查日志,发现在任务执行的时间段,有大量报错: 1 Cause: ERR-CODE: [TDDL-4202][ERR_SQL_QUERY_TIMEOUT] Slow query leads to a timeout exception, please contact DBA to check slow sql. SocketTimout:12000 ms, 在这个日志的上下文不远处就定位到这条慢SQL: 1 select distinct buyer_id as buyerId, seller_id as sellerId from fraud_risk_case WHERE subject_id_enum = 'BUYER_SELLER_BOTH' and (buyer_id = ? or seller_id = ? ) and product_type_enum = ? order by id desc limit 100 这条SQL的主要目的是找到是否有买卖家之间存在关联关系的数据。 通过执行explain,我们看了一下执行计划: id 1 select_type SIMPLE table fraud_risk_case partitions NULL type index possible_keys idx_byr_slr_product key idx_byr_slr_product key_len 1546 ref NULL rows 4133627 filtered 0.19 Extra Using where; Using index 通过这个执行计划可以发现,type = index ,extra = Using where; Using index ,表示SQL因为不符合最左前缀匹配,而扫描了整颗索引树,故而很慢。 ...

March 22, 2026 · 1 min · santu

留言给博主