☰
线上CPU100%排查实战:从load到jstack火焰图全链路指南
2026/10/6 9:26:15 网站建设 项目流程

凌晨两点手机狂震,聊天群里甩来一张监控截图:线上两台应用服务器的CPU已经连续五分钟在100%附近徘徊,接口超时率开始往上爬。这是我过去几年做线上系统运维时最熟悉的夜班开场——CPU跑满这个问题,表象永远一样,但背后原因五花八门,可能是业务飚量、可能是定时任务撞车、也可能是一段没什么人注意的代码在某个输入下死循环。

很多人的第一反应是直接重启。我这里必须拦一下:没有保留现场证据的重启,等于把唯一的线索毁了,下次再爆你依然两眼一抹黑。这篇文章把线上CPU100%的排查思路完整梳理一遍,从最基础的load和top读数,到线程级的堆栈抓取,再结合几个实际踩过的案例,告诉你遇到这种情况应该按什么顺序看、哪些命令绝对别乱跑、以及如何把一次偶发的CPU跑满变成可防范的日常监控项。适合刚接手线上系统的后端开发、运维和SRE,也适合想补全自己排查方法的进阶开发者。

1. 线上CPU100%的第一反应:先稳住现场,别急着抢救

1.1 CPU跑满时应该先看哪些基础指标

CPU跑满这件事,首先要判断的是“病”还是“忙”。在动手排错之前,我习惯先花几分钟把机器的整体状态看明白,而不是直接扎进进程堆栈里捞针。最基础的几个指标就五个:load、CPU各态占比、磁盘I/O、内存和网络。一台机器CPU高,后面可能跟着磁盘问题、网络问题,单纯盯着CPU本身很容易看走眼。

先看load。uptime输出一行,像这样:load average: 12.08, 10.90, 9.30。这三个数字分别代表过去1分钟、5分钟、15分钟的平均运行队列长度,包含正在运行的线程和排队等待CPU的线程,而不是CPU使用率本身。8核机器load跑到12,已经明显超载;但你用top看到CPU百分比可能只有80%,因为有些线程在等锁、等I/O,不在跑。我见过不少新手把load当成百分比,一看1.5就以为CPU占用150%,其实在单核机器上1.5确实是过载,在8核机器上不过是两层冗余而已,完全不算事。

读取load要有参照系,这个参照系就是机器的逻辑核心数,通过nproc能看到。load除以核心数小于0.7,基本算流畅;超过1.0,表示平均每个核有一个线程在奔跑,系统开始出现排队;超过2.0甚至更高,说明已经在明显拥堵。真正短期冲高的场景,看1分钟和15分钟的差值更有意义。如果1分钟是15、15分钟只有8,那是最近几分钟才爆的,适合立刻追查;如果三个值都高且相对接近,说明已经持续了一段时间,多半不是突发流量而是缓慢恶化或者泄漏式消耗。

然后就要看CPU各态的分配,top输出的%Cpu(s)行是核心中的核心。us高,用户态程序消耗了CPU,比如应用代码在做计算、循环、序列化、正则匹配等;sy高,内核态消耗大,系统调用频繁、锁竞争、线程切换、中断处理,甚至内核模块异常都可能表现为sy特别高;wa高,CPU在等待磁盘或I/O设备完成操作,这时CPU看着忙,其实是周边设备不给力;hi/si高,硬件中断、软件中断,尤其是网络包收发和处理比较多的服务容易在这里翻车。

方向判断是我最看重的一步。如果us和sy一起高,大概率是应用的多线程在犯病;如果wa高但CPU摸不着头脑,你就该去查磁盘iostat而不是在应用代码里找死循环;如果si高,就要考虑网络软中断问题。一次在Java服务里排查CPU跑满,折腾了大半天,后来仔细一看top里si占了40%,根本没走应用层,根因是旧版本驱动在收发数据包时反复触发软中断,升级驱动后立刻恢复正常。这个教训告诉我:永远不要跳过sy/si这些基础字段就直接抓线程栈。

1.2 现场信息采集清单:别在重启后后悔

我把现场采集当成处理CPU故障的第一步,不是第二步。原因很简单:很多信息只有问题正在发生时才能拿到,CPU一旦恢复或者进程重启,这些线索就永久消失了。所以无论问题多紧急,先花两分钟采集现场,再判断处置方案。

第一项是时间线。记录问题发生的大概时间点、告警时间、你观察到的时间。时间线要跟发布、变更、流量调整、定时任务对齐。我平时排查前先翻一遍最近几小时的变更记录,很多CPU问题其实跟代码提交没有关系,是配置或者资源调整触发的。把变更时间一标注,根因方向基本能砍掉一半。

第二项是系统基础信息快照。用几条命令一起执行,输出保存到文件中:

date uptime top -b -n 2 -d 3 | tail -n 100 free -m vmstat 1 5 iostat -x 1 3 2>/dev/null dmesg | tail -n 50

每一栏都是排查线索:free看内存是不是也吃紧;iostat看是不是磁盘瓶颈;dmesg看有没有OOM、硬件报错、CPU微码警告。有时候CPU跑满和内存回收是联动的,内存不足触发频繁swap,swap又放大CPU和磁盘开销,光看CPU根本看不全。

第三项是进程信息。先抓住CPU较高的前几个进程,记录PID、PPID、启动时间、启动命令。ps -eo pid,ppid,lstart,cmd --sort=-%cpu | head -20这一条就能拿到。启动时间尤其重要,如果进程启动时间和问题出现时间吻合,那基本可以确认是这个进程引入的。

第四项是业务和日志信息。应用层面的错误日志、慢日志、GC日志都要留。如果是Java服务,无论如何先顺手拿一份jstat -gcutil PID 1000 5的GC曲线,GC异常导致CPU跑满的比例实在太高,早期发现能省下一堆时间。

完成这四项再判断要不要重启。如果是业务完全不可用,确实需要快速恢复,重启之前也至少把前两项留下来。容器环境下还要补充一个动作:确认应用容器的CPU限制。默认容器看到的CPU是宿主机全量核数还是cgroup分配的配额,会直接影响你的判断;如果只分配2核,内部跑到100%对宿主机整体影响很小,但对外表现已经是不可用。这类问题在宿主机top里看百分比基本发现不了,得从容器监控层面看。

2. 从进程到线程的定位:逐层剥开CPU的真正消耗者

2.1 top命令的正确读法

现场信息采完,接下来就是定位环节。我的习惯是一层层往下剥:先知道机器层面哪个区域消耗最高,再找到进程,再定位线程,最后落到代码位置。每一层都有对应的工具,但全部基于同一个思路:用最小的成本把范围缩小到可以下手处理的粒度。

top默认按CPU排序,第一次输出可能受到当前刷新周期影响,所以我在线上一般用top -b -n 2 -d 3拿两次采样。第一次先铺垫整个概况,第二次再看数据。在top的进程列表里,第一优先级是找出CPU占比最高的那个PID,以及它属于谁。如果发现PID是内核线程(比如kworker、ksoftirqd),问题性质就完全变了,不是业务代码问题,而是内核相关的软中断或内核线程工作异常。

字段含义常见原因
us用户态CPU占比业务代码计算密集、死循环、序列化、正则匹配
sy内核态CPU占比系统调用频繁、上下文切换、锁竞争
wa等待I/O完成占比磁盘瓶颈、网络设备落后、文件系统性能差
hi/si硬件中断/软件中断占比网卡收发包、网络软中断堆积、驱动异常

读top的%Cpu(s)行,us、sy、wa、hi、si这些字段我前面已经说过,这里再补充一个细节:sy占比超过30%就要特别警惕。正常情况下绝大多数CPU时间应该在us里,因为业务代码在用户态运行。sy过高通常意味着系统调用和上下文切换异常频繁,或者锁竞争严重。这类问题即使你抓到进程,也可能是个无辜线程在反复尝试加锁失败,反复进入内核态,把CPU烧掉了。我一直强调,在用户态高和内核态高这两种情况下,采用的排查路径完全不同。

us高的常规原因是某种计算密集的脚本或程序,比如一个while(true)循环、日志里反复做正则匹配、JSON序列化。sy高时你不能止步于用户态程序,还要关注系统调用和锁。wa高时第一反应应该是磁盘I/O,不要一上来就追杀进程。把这些方向分清了,之后抓线程堆栈才有意义。

2.2 用pidstat和mpstat区分多核与单核问题

进程粒度用一个pidstat -u 1 5就够。它会输出每个进程在每个采样周期的CPU使用率,并且拆开用户态和内核态,两列分别对应%usr和%system,我能一眼看出这个进程是业务算法消耗大还是系统调用消耗大。

多核机器上有个非常容易忽略的坑:top显示某个进程CPU%只有12%,其实它已经把单个核心跑满了。原因在于top默认的百分比是把所有核加在一起平均的,8核机器单核打满显示12.5%。所以如果看到某个进程CPU%来回在10%-20%之间波动,但业务线程又确实卡得不正常,别急着排除它,先执行mpstat -P ALL 1 3看看每个核的分摊情况。

mpstat -P ALL的输出会列出每个CPU核心的使用率。如果某个核心一直接近100%,其他核很空闲,那基本是单线程热点问题,可能是绑核配置把线程锁死在一个核上,也可能是一个重型算法只开了一个线程。处理思路自然是拆线程、绑核重配、优化算法。如果所有核同时涨,则更像是线程池被打满,大量线程并发执行,或者锁竞争把所有线程都拖进等待队列。

这两种情况在top视角里几乎是一样的整机CPU百分比,但根因和处理方式截然不同。所以遇到CPU告警,第一件不该做的事就是只看整机数字做判断。我在过去做过的压测复盘里反复看到这种场景:一个40线程的Java线程池全部进入阻塞状态,但整机CPU可能只有百分之十几,接口响应时间却明显炸了。没有mpstat的单核视角,这种问题很可能被误判成网络或磁盘故障。

pidstat还有一个很好用的功能:带-t参数可以输出指定进程内各线程的数据。这一步可以衔接下一节的线程级定位,不用切换工具就能从进程平滑过渡到线程。

2.3 线程级定位:top -H 与 ps -Lf 的实战用法

定位到进程之后,进一步使用top -H -p PID按线程排序,一般能直接看到哪几个TID的CPU使用率最高。-H是线程级显示,-p限定某个进程。我用top -H -b -n 1 -p PID抓一次快照,记下高耗时的线程ID和线程名,然后立刻用ps -Lf -p PID进行交叉验证。

线程名不是可有可无的信息。在Java里,一个叫http-nio-8080-exec-5的线程CPU高,基本可以确认是某个HTTP长任务在处理;一个叫GC task thread#0的线程CPU高,说明垃圾回收器异常;一个叫main的线程CPU高,说明主线程在忙碌;如果出现VM Thread高,多半是JVM内部操作比如JIT编译或GC触发频繁。这些名字在抓堆栈前就能给你一个强烈的方向暗示。

为什么要同时用top和ps两个工具?因为top -H输出的TID,之后在jstack里是按十六进制表示的;有时候还需要确认线程的nice值、优先级之类的额外信息,ps -Lf会更直观。两个工具输出相互映证,能避免单次采样误差。我在实际排障时还会开第三个窗口持续观察:

while true; do top -H -n 1 -b -p PID | head -25; sleep 2; done

连续看一段时间,确认哪个线程是持续高而不是瞬时波动。持续观测能筛掉大量假阳性,尤其是业务本来就繁忙时的CPU抖动。

线程级别的TID拿到后,下一步就分语言而行了。Java的做法是把TID转成十六进制再去找nid;原生程序则可能去gdb/pstack里翻。先做转换,这里给个命令:

printf '%x\n' 12345

得到的十六进制数就是后面在jstack堆栈里搜索的关键字。这个细节虽然小,但每次帮同事定位问题都要提醒一遍:在jstack输出里直接搜十进制TID,大概率一无所获,然后又耽误十分钟。

3. 线程堆栈与热点采样:把消耗点从“谁”挖到“在哪”

3.1 Java服务用jstack看线程到底在干什么

确定高耗时的TID之后,Java服务最直接的下一步是抓线程堆栈。执行jstack PID > jstack_$(date +%s).txt,然后用前面转换出来的十六进制TID去文件里搜nid=0x...。搜到之后往下看七八行,基本能看到这个线程正在执行的类和方法。

但我想强调:单份jstack的价值有限,它只是那个瞬间的切片。线程可能一秒内换了上百个代码帧,刚好某一帧出现在dump里,并不代表它就是消耗热点。可靠的判断方式是连续抓多份,间隔一到两秒。我自己至少抓三份,然后比较同一个TID在多份dump里是否反复停留在同一段栈。如果是,那这段栈十有八九就是消耗热点;如果每次栈都不一样,那线程只是在正常运行但工作量很大,性能问题可能要换个维度去解决,比如流量、数据量。

另外要留意线程状态。jstack里常见的有RUNNABLE、BLOCKED、WAITING、TIMED_WAITING。高CPU通常对应RUNNABLE,但也有反直觉的情况:一个看似WAITING的线程,如果它不断被唤醒又睡眠,也消耗大量CPU。特别是使用LockSupport.parkNanos做轮询的代码,dump里会显示停在Unsafe.park附近,看起来像是等待,实际上在高频自旋。这时候判断依据还是前面说的:多份dump是否重复出现同一栈帧。

JVM权限问题也建议提前解决。应用以非root用户运行时,jstack可能卡在attach阶段,报类似Unable to open socket file或者permission denied的错误。别在故障发生时才去研究用户切换或jattach机制,早点在测试环境把用户配置和权限验证好。再给一个更省事的方案:给每个Java容器默认挂一个调试脚本,内部提前封装好jstack、jstat、jmap命令,故障时直接执行脚本输出文件,避免在线敲命令时手忙脚乱。

3.2 C/C++服务用gdb/pstack抓线程栈

如果你排查的服务是nginx、Redis、自研C++网关,或者任何用C/C++写的守护进程,那这套方法要稍微换一下。

最简单的工具是pstack,直接执行pstack PID,会把进程中所有线程当前的调用栈打印到标准输出。它本质上就是gdb attach后抓线程栈的快速版本。我在线上先执行pstack PID > stack1.txt,隔两秒再抓一份stack2.txt,然后对比热点线程的栈是否相同。和jstack的对比逻辑一模一样。

如果pstack输出太简略,或者你需要查看线程的局部变量、当前函数参数,需要上gdb。用法上我推荐非交互式的一行命令:

gdb -p PID -batch -ex "thread apply all bt" 2>&1 | tee gdb_stack.txt

-batch模式执行完thread apply all bt后自动退出,不会让gdb停在交互提示符。后面多一个full关键词,thread apply all bt full还能带上局部变量。但是必须提醒:gdb附加到正在运行的进程时,这个进程会短暂暂停,线上高负载环境下这种暂停可能引发流量重试风暴,让问题快速恶化。所以要谨慎使用,非到万不得已不要上gdb,pstack真抓不出问题再说。

真实场景中,我遇到过CPU跑到100%但pstack两份栈完全没有共性的情况。看起来像是不同线程都在随机位置做普通操作,完全看不出某个执行路径特别异常。这时就得靠更底层的采样工具perf top,它能从内核采样的角度告诉你CPU时间都花在哪条路径上,而不依赖恰好的栈帧快照。有一次排一个自定义网关的CPU问题,pstack抓了两个小时一无所获,后来perf top一开,直接看到热点集中在某个库的幂运算函数上,原来是业务把签名操作做成了循环调用,一个请求算几十次签名。这个例子让我彻底认识到栈抓取和统计采样的价值差异。

3.3 用perf采样生成热点火焰图

perf是Linux下最强大的CPU分析工具之一。它的原理是定期打断CPU执行,采集当前正在执行的函数地址,再结合符号表翻译成函数名,最后统计每个函数被采样到的次数,等同于函数消耗CPU时间的占比。

我常用来生成火焰图的完整链路是这样的:

perf record -F 99 -a -g -- sleep 30 perf script > out.perf git clone https://github.com/brendangregg/FlameGraph cd FlameGraph ./stackcollapse-perf.pl ../out.perf > out.folded ./flamegraph.pl out.folded > cpu.svg

生成的svg在浏览器里打开,横轴代表采样次数或者说CPU时间占比,纵轴是调用栈。哪个函数在横轴上特别宽,它就是绝对的CPU热点。这个图形工具比直接读文本直观得多,尤其排查多层调用导致的性能问题时,一眼就能看到源头的路径。

-F 99每秒采样99次,是性能分析界的习惯值。选用99而不是100,是为了避免跟某些每100毫秒一次的系统定时器同步,产生周期性的采样偏差。采样时长一般30到60秒,时间太短统计意义不够,太长又占用磁盘。

Java环境使用perf有一些额外的坑。默认情况下perf看到的Java方法名可能只是一堆JIT编译后的符号,根本看不出来是你哪段业务代码。解决方法是Java启动参数里加上-XX:+PreserveFramePointer,让JVM保留帧指针,perf才能拿到Java方法名。这个参数有少量运行时开销,但对排查价值而言一般可以接受;至少我在线上验证过,打开之后火焰图信息量完全不在一个量级。

还要注意perf对权限的要求。很多系统默认kernel.perf_event_paranoid=3,可能连perf record都不让你用。临时调低为sysctl -w kernel.perf_event_paranoid=1,或者直接对目标进程的PID运行perf record -p PID,能绕过一部分权限限制。在K8s容器中运行时还要确认宿主机的权限,通常最稳的是在宿主机上用容器外的方式执行采样。

4. 三个高频根因:从现象到原因的真实排查链路

4.1 定时任务风暴:为什么凌晨的CPU利用率最高

这类问题我排得最多,也最符合“CPU100%”这个标题给人的第一直觉。现象往往出现在固定时间点,比如每天凌晨0点、凌晨2点、整点。top里没有一个进程独占CPU,而是多个进程同时占用,每个占比不算极端,但加起来打死。

遇到这种模式,流量曲线是最快指向标。先看监控历史:CPU是不是从某个整点开始阶梯式上升的,任务结束时间是不是也相对规律。如果是,基本可以确认是定时任务叠加。第二步就是翻crontab、翻分布式调度平台的任务列表,把触发时间落在同一分钟内的任务全部列出来。我遇到过最多的情况是凌晨2点这个时间点,日志切割、数据归档、报表计算、缓存预热、数据库备份,五个以上任务同时触发,每个任务在多个实例上又复制了一份。多个实例同时跑同一套任务,产生的数据库压力、外部API调用量,瞬间就把CPU推到100%。

修复上我始终坚持三条原则。第一,错峰,给每个任务安排独立的执行窗口并强制分散。第二,限制并发,用一个分布式锁或者调度平台的单实例执行配置,保证同一任务同一时刻只有一个实例执行,避免重复劳动。第三,设置超时与失败退避,防止失败后立刻重复执行,把一次偶发故障放大成持续风暴。

错峰是最简单有效又最容易被忽视的一步。很多团队的任务时间习惯性全部对齐到某个整点,只是因为“方便记”。但线上资源是共享的,时间点在凌晨并没有魔法,它只是让这个隐患延迟爆发而已。把任务时间加一个随机偏移量,比如分散在0点到0点30分,虽然简单,但可以显著平滑负载曲线。

4.2 锁竞争与线程空转:CPU高但业务无响应的典型场景

第二种高频根因比较邪门,特点是CPU很高,但业务却几乎没在干活。接口超时、QPS掉到接近0,可机器上CPU依然嗡嗡转。我接到过很多类似的告警,第一反应都是“CPU高应该是处理快”,怎么会没响应呢?后来发现CPU高的原因往往是锁竞争和自旋空转,而不是真正在执行业务逻辑。

那个典型场景我复盘过很多次:某Java服务维护一个共享缓存,更新缓存时用了一把全局锁。锁内部本应很快释放,但因为同步外部状态时依赖一个长时间不可用的下游服务,导致锁长时间被持有。大量请求线程进入阻塞等待,另有一部分请求线程不断尝试获取锁,反复从用户态进入内核态。最终CPU消耗在锁竞争和上下文切换上,真正干活的线程反而没有。

排查这类问题的抓手还是线程堆栈。连续抓几份jstack后,你会发现大量的BLOCKED状态线程都停在同一个锁的获取处,或者大量RUNNABLE线程在近乎相同的栈帧里打转。这种“排队”特征非常明显,几乎不需要复杂工具,只要愿意多抓几份仔细看就不会错过。

处理方向有几个层次。最根本的是缩小锁范围,把与外部依赖的通信挪到锁外;其次是优化锁粒度,比如用分段数据结构替代全局锁;再次是坚决避免在锁内做任何可能阻塞的I/O操作。这个原则通常能总结成两句话:“持锁时间越短,竞争越小”、“锁内不碰网络不碰磁盘”。如果业务场景确实需要复杂的同步状态,优先考虑用无锁的数据结构或引入Event驱动模式。

我还遇到过更隐蔽的情况:代码里用sleep(1)加while(true)自旋轮询某个标志位。这种代码在单线程下看着无害,但多个线程同时轮询,CPU会悄悄涨到峰值。dump里这些线程都显示为WAITING或TIMED_WAITING,如果只看状态不看栈的重复性,很容易把它当成空闲线程忽略掉。所以排查时永远记住:线程状态是会骗人的,多份堆栈对比才是硬依据。

4.3 中断与内核态CPU占用:软中断堆积的问题

最后一类根因走向另一个极端,问题不在应用代码,而在内核和中断处理。判断入口是top里的sy和si两个字段。如果sy或者si长时间超过30%,同时us维持在低位,说明CPU时间大量耗在内核态,应用层的堆栈甚至都不需要抓。

网络类服务在这个场景下最容易踩雷。高并发流量进来后,网卡触发中断,内核需要处理数据包、走协议栈、投递给用户进程。如果网卡多队列没有合理配置,或者中断完全集中在某一个核心上,那个核心就会非常忙,其他核心却只能排队等待。表现到top上就是某个CPU核心爆满,但整机平均不高;放大到容器里,用户看到的只是应用开始周期性抖动。

排查软中断问题,我一般看三个文件:/proc/interrupts看硬件中断分布,/proc/softirqs看软中断增长,/proc/net/softnet_stat看网络软中断的处理质量。三个文件都建议间隔几秒各取两次,观察增量而不是绝对值。如果发现某个网络相关的软中断计数在疯狂增加,且软中断全部落在某一个或某几个CPU编号上,那基本可以坐实“中断堆积”方向。

softnet_stat里重点关注dropped和time_squeeze列。time_squeeze增长说明网络软中断的处理时间不够,数据包队列已经在超时等待;dropped增多说明底层已经开始丢包。这些都是网络协议栈跟不上流量的信号。短期缓解手段包括切走部分流量、在入口层做限流、重启业务服务让连接重新分配、甚至暂时禁用一些冗余的防火墙规则减负。长期方案则是调整网卡队列数量和RPS/RFS配置,让中断平均分摊到多个核上,或者换用支持更多队列的驱动和网卡。

这类问题最坑的是你即使找到了正确的方向,用对工具也需要时间。我有几次直接在应用进程上做perf采样,界面看起来就是一片用户态函数,毫无识别度,时间却已经流逝了半小时。后来看到sys值再来给方向。所以我还是想强调:不要跳过最原始的sy/si判断直接上高级工具。方向是第一位。

5. 高负载操作纪律与长期防线建设

5.1 这几条操作在高负载下千万别乱跑

前面讲的是怎么定位CPU问题,这一节讲怎么不把事情搞砸。CPU已经100%的情况下,系统就像一根绷紧的弦,任何额外操作都可能成为最后一根稻草。我根据自己的踩坑和看到过的线上事故,整理一个“忌口清单”。

第一个是strace。这是我最想提醒的一个坑。CPU高的时候,有人喜欢顺手strace -p PID看看系统调用,但strace会让被跟踪进程每次系统调用都产生大量的额外开销,等于给一个已经透支的进程套上沙袋。我在一次事故中见过strace一挂上去,进程立刻从100%CPU掉到完全hang住,最后只能强制重启,事后想想差点把现场彻底毁掉。如果非要用,先挂strace -c做调用统计,低采样、短时间,并且确保能立刻退出。

第二个是kill -9。很多团队的处理脚本里直接kill -9再拉起服务。我承认有时候这是最快恢复手段,但你应该在完成现场采集之后才考虑它。kill -9没有任何优雅处理机会,线程dump、堆、日志缓冲全部丢失。如果进程还有可用的jstack或者日志,先抓完再杀。

第三个是gdb附加后忘记退出。gdb会让目标进程暂停,在业务高峰期等于人为制造停机窗口。如果你已经attach上去了,执行完bt之后马上quit,别让会话保持挂着。

第四个是盲目扩容。CPU100%未必是资源不够,如果是锁竞争,扩容只会增加更多竞争线程,系统更卡;如果是定时任务风暴,扩容可能让任务重复执行得更欢。扩容前必须回到前面的定位流程,明确是计算资源不足才扩容,否则属于用错误的手段对抗错误的方向。

还有一个很多人会忽略的坑:在容器环境下直接重启Pod或者调整资源限制。如果你没保存现场就动了Pod,调度器一会儿就帮你把它重新拉起来,旧实例的线程转储和系统信息全部灰飞烟灭。至少先执行进入命令把jstack、top、dmesg拿出来再谈重启。

5.2 监控、告警与自愈:提前让CPU问题止于爆发前

处理了那么多CPU问题之后,我发现真正有效率的改进不是更快的定位,而是让问题在影响业务前被监控拦住。CPU100%这类问题往往不是突发而是渐进,只要监控到位、阈值合理,通常能在还来得及补救的阶段被发现。

监控至少覆盖四个层面。第一层是主机指标:CPU使用率、load、各态占比、磁盘I/O、网络流量。第二层是进程指标:重点进程的CPU、内存、线程数。第三层是应用指标:GC耗时、接口RT、错误率、线程池活跃度。第四层是调度与业务指标:定时任务执行数量、队列积压、单量突发。把这四层的时间序列对齐,CPU跑高时能在同一个时间轴上看清楚上下游的连带变化。

告警配置别搞得太简单。只设置“CPU>90%”这种固定阈值,在高负载系统里很容易误报,在动态伸缩的容器环境里又可能漏报。我的经验是组合条件:比如CPU持续超过85%达到5分钟并且RT同时上升,才触发告警;或者load超过核心数并且持续10分钟才提醒。趋势类的告警也很值钱,比如CPU从20%突然跳到70%并继续攀升,即使还没到阈值也要及时感知。

另一个非常实用的部署是“现场快照脚本”。我写了一个snapshot.sh,放在每台机器上,逻辑很简单:当CPU使用率连续3分钟超过80%,自动执行前面第一节里说的那套采集命令,把top、vmstat、iostat、dmesg、Java相关的栈信息全部存到固定目录,并以时间区分文件。这个脚本平时不占用资源,到关键时刻自动保留证据,分类排查的效率会高很多。

更高阶的玩法是半自动自愈。对于场景相对成熟的值班团队可以配置:CPU持续过高自动保存快照并通知;恢复策略比如切断异常定时任务、清理失控线程可以做成有限的自动化操作,能极大减少凌晨抢修的时间。但自动重启、自动扩容这些动作一定要有审批和开关,脚本出错比人手犯错的扩散面大得多。我个人更推荐分两步走:先把现场快照和告警做好,让每一个事故都有据可查;等流程跑顺了再逐步引入自动化处置,别一步跨太大。

最后再分享一点经验:接手线上系统之后,我会先花一个下午把故障处理SOP写出来,并把所有抓现场的命令封装成脚本。你可能会觉得这些偏防守、没意思,但真正遇到CPU100%时,它帮你节约的时间是以小时计的。CPU问题从来不怕它难,怕的是在慌乱中做出一系列不可逆的操作。把流程固定下来之后,我处理这类告警的心态基本就是:先按清单拿证据,再按链路定位,最后按根因处置。这套顺序跑习惯了,凌晨被叫醒也没那么心虚了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询