
凌晨两点手机狂震聊天群里甩来一张监控截图线上两台应用服务器的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跑满和内存回收是联动的内存不足触发频繁swapswap又放大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去文件里搜nid0x...。搜到之后往下看七八行基本能看到这个线程正在执行的类和方法。但我想强调单份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 21 | tee gdb_stack.txt-batch模式执行完thread apply all bt后自动退出不会让gdb停在交互提示符。后面多一个full关键词thread apply all bt full还能带上局部变量。但是必须提醒gdb附加到正在运行的进程时这个进程会短暂暂停线上高负载环境下这种暂停可能引发流量重试风暴让问题快速恶化。所以要谨慎使用非到万不得已不要上gdbpstack真抓不出问题再说。真实场景中我遇到过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_paranoid3可能连perf record都不让你用。临时调低为sysctl -w kernel.perf_event_paranoid1或者直接对目标进程的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跑高时能在同一个时间轴上看清楚上下游的连带变化。告警配置别搞得太简单。只设置“CPU90%”这种固定阈值在高负载系统里很容易误报在动态伸缩的容器环境里又可能漏报。我的经验是组合条件比如CPU持续超过85%达到5分钟并且RT同时上升才触发告警或者load超过核心数并且持续10分钟才提醒。趋势类的告警也很值钱比如CPU从20%突然跳到70%并继续攀升即使还没到阈值也要及时感知。另一个非常实用的部署是“现场快照脚本”。我写了一个snapshot.sh放在每台机器上逻辑很简单当CPU使用率连续3分钟超过80%自动执行前面第一节里说的那套采集命令把top、vmstat、iostat、dmesg、Java相关的栈信息全部存到固定目录并以时间区分文件。这个脚本平时不占用资源到关键时刻自动保留证据分类排查的效率会高很多。更高阶的玩法是半自动自愈。对于场景相对成熟的值班团队可以配置CPU持续过高自动保存快照并通知恢复策略比如切断异常定时任务、清理失控线程可以做成有限的自动化操作能极大减少凌晨抢修的时间。但自动重启、自动扩容这些动作一定要有审批和开关脚本出错比人手犯错的扩散面大得多。我个人更推荐分两步走先把现场快照和告警做好让每一个事故都有据可查等流程跑顺了再逐步引入自动化处置别一步跨太大。最后再分享一点经验接手线上系统之后我会先花一个下午把故障处理SOP写出来并把所有抓现场的命令封装成脚本。你可能会觉得这些偏防守、没意思但真正遇到CPU100%时它帮你节约的时间是以小时计的。CPU问题从来不怕它难怕的是在慌乱中做出一系列不可逆的操作。把流程固定下来之后我处理这类告警的心态基本就是先按清单拿证据再按链路定位最后按根因处置。这套顺序跑习惯了凌晨被叫醒也没那么心虚了。