☰
Linux服务器CPU飙升故障排查实战:从告警到根因定位的完整流程
2026/10/18 17:47:27 网站建设 项目流程

半夜两点,手机在床头柜上震得嗡嗡响。困得要命,意识还停留在刚才的梦里,但身体已经坐起来了——职业本能,告警就是命令。屏幕上显示某台云主机负载告警,load average一路飙到12以上,昨晚更新过代码,这个时间点出问题,十有八九是新版本惹的祸。披上衣服坐到电脑前,深呼吸,开始按多年踩坑总结出来的流程一步一步排查。这个场景,相信做过运维和SRE的同学都不陌生。今天不聊虚的,直接把这份在无数个深夜里救过我的Linux故障排查“作战地图”完整拆开,从告警触发到根因定位,再到止血恢复和复盘改进,每一步都附上实测有效的命令、参数和判断逻辑,希望能帮你在下一次告警响起时,少走几段弯路。

1. 告警刚响起时,先做对这三件事

1.1 第一件事:确认告警真实有效,别被误报带偏节奏

深夜的告警,首先不要急着登录服务器。用半分钟看一眼监控面板:是单机单指标告警,还是集群多指标同时飘红?如果是单机load高,其他机器指标正常,那大概率是这台机器自身的问题;如果是整个集群的请求失败率、响应时间、load一起涨,就要考虑是不是上游服务、数据库或网络链路出了问题。我见过不少人一听到告警就冲上去重启服务,结果重启完才发现是另一台核心数据库出了状况,白白扩大了故障面。

确认告警有效之后,登录服务器的第一件事,不是跑什么花哨的诊断工具,而是把基础信息三件套拉出来:当前时间、系统负载、运行时长。用uptime可以看到load的1分钟、5分钟、15分钟三个值,这一步非常关键——如果1分钟值远高于15分钟值,说明负载是在最近几分钟内突然上涨的,属于突发型故障;如果三个值都高且接近,说明已经持续了相当一段时间,可能是慢慢累积出来的问题。两种场景的排查方向完全不同,前者优先看最近变更,后者优先看进程和资源泄漏。

1.2 第二件事:看一眼全局,先摸清机器现在“喘不喘”

登录之后,我习惯先跑vmstat 1 5,连续输出5次,每秒一次。vmstat输出的列很直白:r表示运行队列中的进程数,b表示不可中断睡眠的进程数,si和so反映交换分区使用情况,us和sy分别代表用户态和内核态的CPU占用。判断思路很简单——如果us很高,说明是应用在做计算;如果sy很高,说明系统调用频繁,可能是锁竞争或内核态开销大;如果wa居高不下,说明磁盘IO在拖后腿;如果r远大于CPU核心数,那就是典型的负载过高,排队严重。

这一步的目标是给故障定性:CPU瓶颈、内存瓶颈、磁盘瓶颈、还是网络瓶颈。定性准确,后面才不会走弯路。我自己的习惯是,把vmstat、free -h、iostat -x 1 3三件套按顺序快速过一遍,基本能在两分钟内把大方向圈定。紧接着再用top看一眼整体排序,确认哪些进程占资源最凶。注意,top刚启动时的数据是累计值,要按“P”键按CPU排序,或者按“M”键按内存排序、按“T”键按时间排序,等一两秒让它刷新出实时状态再判断。

1.3 第三件事:打开终端日志记录,一切操作留痕

这个习惯很多人会忽略,但深夜故障排查最容易手忙脚乱,命令敲了一堆,回头复盘时根本想不起当时执行过什么。登录服务器后,我第一件事就是执行script -f /tmp/debug_$(date +%s).log,把所有操作录制下来。故障结束后,这份记录就是事故报告的第一手素材。说句实话,我做故障复盘时,最头痛的就是“当时执行了什么命令、看到了什么输出”完全没留痕,光靠脑子回忆全是模棱两可,有了script日志,整个排查过程的推进逻辑清清楚楚。

做完这三件事,才算是真正进入排查状态。下面用一个我经历过的真实场景——某网关服务在某日凌晨突发CPU飙升、超时告警——把整张作战地图完整走一遍。

2. CPU飙升型故障的定位链路

2.1 从top开始:找出“最不像话”的进程

再拉回那次故障的时间线。告警触发后,我登录机器,uptime显示load 12.6,而机器是8核,运行队列已经严重超出处理能力。vmstat里us列高达91.2,iowait只占0.8,内存也没到瓶颈——定性结果非常明确:纯CPU消耗型故障,不是磁盘或内存的问题。

接下来用top定位进程。那一瞬间,屏幕上PID为18763的java进程CPU占用直接拉满,以197%的数值遥遥领先。这里插一句,top显示的CPU占用是“占单个核心的百分比”,所以一个多线程进程显示197%,意味着它大致吃满了两个核心。正常情况下这个网关服务也就占20%到30%,突然飙到接近200%,绝对不正常。

但我要提醒一句:看到某个进程CPU高,先别急着kill。你要想想这个进程为什么高。如果是业务高峰期自然上涨,kill就是事故二次放大;如果是代码死循环或GC异常,终止进程只能临时止血,等重启后问题还会回来。正确的姿势是继续往下钻,找到CPU到底消耗在哪些线程、哪些方法上。

2.2 线程级定位:jstack连抓三次,找到那个“钉子户”

定位到进程之后,我先把进程号记下来,然后执行top -H -p 18763,按CPU排序查看进程内部的线程消耗。这一步能清楚看到是哪些线程在疯狂消耗CPU。在输出里,PID为00279的线程稳坐第一,CPU占用长期徘徊在95%以上。为了确认这个线程不是碰巧瞬间飙高,我在30秒内连续执行三次jstack 18763 > /tmp/jstack_$(date +%s).txt,把堆栈快照保存下来。

这里有个非常关键的小技巧:jstack的线程号是十六进制表示的,而top -H输出的是十进制PID,两者要换算。我平时都在命令行直接跑printf '%x\n' 00279,得到十六进制值,再去jstack文件里搜索nid。三次堆栈中,00279线程都反复出现在同一个调用位置——一个RPC调用Hessian序列化的方法上,而且堆栈底部能看到GC日志相关的线程也在活动。这个“每次都出现在同一个方法”的特征,基本锁定了嫌疑范围。

然后,顺手把GC日志也拉出来看一眼:grep "Full GC" 一下,确认没有Full GC频繁触发的问题,排除内存因素干扰。到这里,疑点已经很集中了:某个业务方法在做大量序列化,CPU开销异常。

2.3 CPU问题的常见分类:死循环、GC抖动、线程爆炸

为了让这套定位思路能复用,我把CPU飙升型故障的常见类型整理成一张速查表,你在现场照着判断就行:

故障类型典型特征快速确认手段处理方向
业务死循环/热点逻辑某线程CPU持续高位,堆栈固定在同一方法jstack多次抓取,观察堆栈是否每次一致修复代码、紧急回滚
GC频繁/内存泄漏CPU高但堆内存曲线反复上涨,GC日志密集jstat -gcutil、GC日志分析dump堆内存分析,扩容或修复
线程数爆炸线程数暴增,上下文切换剧烈观察top -H线程数量,cat /proc/PID/status查线程数检查线程池配置、连接池泄漏
锁竞争/内核态高sy列偏高,线程堆栈大量blocked/waitingjstack搜索BLOCKED、vmstat sy列优化锁粒度、检查死锁
外部依赖超时重试线程堆栈集中在网络IO等待,CPU不高但load高检查RPC/DB调用耗时,日志搜索timeout熔断、限流、退避重试

第5类特别容易误判,因为它表现成load高,但CPU的us并不高,很多人绕一大圈才发现是下游接口超时拖垮了线程池。判断这类问题有个巧办法:如果top里CPU总和不算高,但load很高,先怀疑IO等待和锁等待,再怀疑不可中断睡眠。

3. 不要把视角锁死在单机上:负载过高,可能源头在下游

3.1 确认“是不是只有这台机器受害”:从单点走向全局

定位到进程内部之后,很多人的第一反应是“找到问题线程了,赶紧处理”。但我不建议马上动手。在下结论之前,还要回答一个问题:这台机器为什么会在这个时间点承受这么大的压力?是这台机器的流量被调度过来了,还是用户请求总量突然暴涨,又或者上游在疯狂重试这台机器?

带着这个疑问,我去翻了一下监控面板的流量曲线,发现请求量比同时段高出将近三倍。紧接着又检查了网关的实时日志,发现大量请求的响应时间从平时的50ms直线飙到2000ms以上,部分请求直接超时。也就是说,不只是CPU高,整个请求链路的耗时都在恶化。到这里,问题性质变了,从“单机CPU冒烟”变成了“网关服务的请求链路劣化”。

这种视角转化特别重要。CPU飙高是果,得找到因。如果只盯着单机CPU,你很可能在错误的方向上做无用功。我见过有同事花了一个小时去分析代码热点,结果最后发现是上游系统在凌晨做批处理,把请求压力全打到了这台网关,方向上就错了。

3.2 常见根因场景:上游重试、流量突增、定时任务

在日常运维里,类似“单机load高,但根因在别处”的情况,通常集中在三类场景:

一是上游服务的重试风暴。某个核心服务在这段时间内报错率上升,它的客户端开启了失败重试,所有重试请求就会像洪水一样涌向后端网关。这种场景下,网关的CPU往往先体现在网络IO和序列化处理上。

二是流量突增引起的不均衡。比如前端做了活动推广,或某个接口被异常调用方疯狂刷量,流量集中打到了某一台或某几台机器。这种情况要看入口的负载均衡策略,是不是会话保持把流量钉死在了特定机器上。

三是定时任务和大促任务扎堆。每天凌晨固定跑批处理任务,或者数据同步任务恰好在这个时段启动,几个小时的全量数据同步会抢占大量CPU和带宽。

针对这三种情况,排查的时候要额外关注两个数据源:入口网关的access log和下游依赖的调用链监控。看日志里同一种请求的占比是否异常,看是否突然多出了某个调用来源,再用awk对小口径做快速统计。后面我会专门讲怎么在命令行里快速分析日志。

3.3 快速确认“是代码问题还是流量问题”的简单公式

有人会问,怎么样最快区分“代码写烂了”和“流量真的大”?我分享一个简单实用的估算方法:先看当前QPS相对平时翻了几倍,再看平均CPU占用翻了几倍,两个倍数做对比。假设平时QPS是2000,现在5000,翻了2.5倍;平时CPU 30%,现在91%,翻了3倍多。这个“CPU翻倍速度显著快于QPS翻倍速度”的差异,说明处理单个请求的开销也在变大,代码路径大概率出了问题——要么是某个分支被频繁走到,要么是参数导致序列化暴涨。

反过来,如果QPS翻倍和CPU翻倍基本同步,那更接近纯流量增长。这个公式在深夜现场很实用,三分钟就能算出来,能把排查方向从“大海捞针”收敛到“验证代码变更”。

4. 日志分析三板斧:不用专业平台也能快速定位

那个夜晚,我在确认请求量暴增之后,赶紧打开网关的access log,开始用命令行的方式做现场分析。

4.1 第一板斧:用tail盯实时变化,用awk做分钟级统计

现场分析的第一步,是确认流量上涨的节奏。我执行了tail -n 2000 /data/logs/gateway/access.log,先看最近一段时间请求的形态。然后关键的一步来了,我用了一个统计脚本思路:对日志里的时间字段做分钟截断,用awk累加请求数,输出随时间变化的QPS曲线。

假设日志每行时间格式类似2026-05-11 00:10:23,那就可以用下面这段命令做统计:

awk '{print substr($4,1,16)}' /data/logs/gateway/access.log | sort | uniq -c | tail -n 30

这条命令把日志按“YYYY-MM-DD HH:MM”的分钟维度分组,再统计每个分钟内的请求量。执行后我清楚地看到,请求量在00:10之前还是600每分钟的平稳状态,00:11直接跳到1800,00:12开始破万,之后一路攀升。流量不是缓慢涨上来的,而是断崖式突增,这种形态基本排除了自然增长,更像有调用方在暴力刷接口。

4.2 第二板斧:从日志中筛出“异常占比”和“嫌疑参数”

QPS暴涨只是开始,我还要回答“这些请求在打哪个接口”。因为日志里记录了请求路径、响应状态码、耗时时长,我直接用grep加awk组合做多维分析。先看状态码分布:近14分钟里,200正常响应只占30%,504网关超时占了55%,剩下的是499客户端断开。这个状态码分布说明,网关不仅接到大量请求,而且其中过半请求自身已经处理不过来,触发了超时。

接着按请求路径聚合,看看流量集中在哪个接口上。日志解析字段如果按空格分隔,路径一般在第7个字段附近,我用命令按路径统计请求数:

awk '{print $7}' /data/logs/gateway/access.log | sort | uniq -c | sort -rn | head -n 20

结果非常有意思:一个内部健康检查接口占了40%以上的请求量,而它的响应时间是全站最慢的。再看一眼完整的请求参数,发现其中大量请求都在带一个risk=1的参数,平时这个参数几乎不会出现。也就是说,有人或某个系统在调用这个本应轻量的健康检查接口时,附带了一个危险系数标记,导致服务端对每个请求都去做了一次高消耗的风控计算。现场基本锁定。

4.3 第三板斧:日志里挖不出答案时,转战系统资源和内核日志

虽然这次的日志分析直接破了案,但作为流程,我还是要强调:如果日志里什么都看不出来,不要硬看。日志是应用的“口供”,有时候它只会撒谎或不说话,这时就要切换到系统资源视角,看看CPU到底在忙什么、网络的连接状态是否异常、系统有没有报错。

关键的几条现场命令如下:

# 查看系统负载的历史曲线,确认是突发还是渐进 sar -q -f /var/log/sa/$(date -d yesterday +%d) # 查看TCP连接状态,确认TIME_WAIT和ESTABLISHED是否异常 ss -s # 查看打开文件数是否到了上限 cat /proc/sys/fs/file-nr

TCP连接状态尤其是重点。如果TIME_WAIT数量异常巨大,说明连接被频繁建立和关闭,往往伴随大量短请求;如果SYN_RECV堆积,说明连接队列已满,服务可能已经过载。结合ss的输出,可以判断“请求方在疯狂拨号”还是“服务端无法及时accept”,这直接决定了你应该堵源头还是扩容量。

5. 恢复动作的优先级排序:止血、降级、定位、根治

5.1 先止损再深挖:理性决定“重启还是保留现场”

当故障原因清晰之后,接下来才是决策环节:是先重启服务恢复,还是先保留现场继续深挖?我个人的原则是:如果现场信息已经采集得差不多——线程堆栈抓到了、日志统计出来了、内存快照也dump了——那就可以果断重启进程,让业务先恢复。如果现场信息还没采集完,那就再花两分钟抓全,不要等重启之后才后悔“刚才忘了看某个指标”。

当时我没有立刻重启网关,是因为在日志分析前,现场证据还不足。等到确认是某接口携带特定参数触发高消耗逻辑后,我先做了两个快速动作:一是把这个健康检查接口的调用方的异常流量来源找出来,在接入层把带有那个特定参数的请求做临时拦截;二是联系调用方,确认是否有异常任务在跑。这两个动作比盲目重启有效得多。

5.2 多副本场景下,如何做到“最小化影响”

如果服务是多副本部署,我建议在操作前先把该实例从负载均衡的流量分发列表里摘掉,或者直接停掉健康检查,让它不再接收新请求。确保摘流成功后再针对这台机器做重启、排查、修补,避免一边排查一边又有新流量涌进来,导致你看到的指标永远是混合信号。

摘流之后,由于这台机器不再接收流量,load会逐渐降下来,CPU曲线也会回落。但注意,摘流解决的只是“不再加重”,已经堆积的请求和线程仍然可能让进程卡死,所以下一步依然是重启进程或滚动发布,这一步才是真正的止血。

5.3 回滚判断:不要赌新代码,优先回到上一稳定版本

经过代码比对,这次故障的元凶很快浮出水面:当天上线的新版本里,给那个健康检查接口增加了一个“全量风险扫描”的逻辑,每次调用都会做一次非常耗时的规则遍历,导致CPU被瞬间吃满。这个接口的调用频率原本很低,但架不住某个上游客户端在凌晨的重试机制把它当成了高频健康检查来调用,两个问题叠加,直接引爆。

确认是代码变更引入的回归后,最稳妥的恢复策略就是回滚到上一稳定版本。深夜故障排查最忌讳在不清不楚的情况下尝试“修改代码再发布”来修复,因为你没有时间在半夜做充分的验证,新改动很容易引入二次故障。回滚之后,我盯着监控看了15分钟,load回落到2以下,请求耗时也恢复到了50ms以内的水平。这一晚最惊险的35分钟,到这里算是暂时收住了。

6. 故障复盘:不止是写报告,更是把漏洞补成体系

6.1 短期补救:监控盲区补全和告警阈值合理化

告警“炸裂”本身也有问题值得反思。当时这台机器的告警规则只配置了load大于10这一条,缺少CPU、线程数、请求耗时等更细粒度的指标。而且这个告警级别设置成了P1,深夜响铃,但并没有附带任何上下文信息,导致值班人员要先登录机器慢慢查数据,浪费了黄金几分钟。

那次故障之后,我做的第一件事就是完善监控。增加核心服务的CPU使用率、线程数、Full GC次数、线程池活跃度、平均响应时间五个监控指标,并为每个指标设置合理的告警阈值,而不是只用load一把尺子量所有场景。

6.2 中期加固:例行巡检集成“故障演练”

监控补全只是防御的一层。真正让团队在下次故障时不再手忙脚乱的,是两件事:例行巡检和故障演练。巡检不是每天看一眼面板就叫巡检,而是要有具体脚本和判断逻辑。比如写一个巡检脚本,每天凌晨自动检查所有核心服务的线程数、内存增长趋势、错误日志关键词,任何异常直接推送日报摘要,让问题在白天就被看见,而不是等到深夜变成故障。

故障演练则更有价值。我们团队后来做了一次“模拟下游服务超时”的演练,验证了网关的熔断降级是否生效,结果发现熔断阈值配置不生效,因为某个内部客户端没有接入熔断机制,全靠超时硬扛——正是这次演练,提前暴露了隐患,才避免了类似的故障在真实场景中重演。

6.3 长期建设:让“告警定位手册”成为团队资产

复盘的最后一个环节,是把这次的故障前世今生都沉淀成文字,包括:故障时间线、排查决策点、误判方向、关键分析命令与输出、根因说明、改进动作。我会把这些内容追加到团队的“服务排障手册”里,每个核心服务一节,里面写着这个服务在什么场景下会出现什么症状、用什么命令快速确认、上次是怎么修复的。

自此之后,再有类似告警,值班同学按图索骥,定位时间至少缩短一半。

7. 经验收尾:把排障地图变成你自己的肌肉记忆

说说这套“作战地图”里,我自认为最有价值但最容易被忽略的几件事。

第一,告警响起后,控制住“焦急操作”的冲动。深夜的错误决策大多是因为困倦叠加着急,所以每一步都要有一个明确的理由,没有理由的命令不要乱敲。先信息采集,再定性,最后动手,这个顺序不能乱。

第二,现场信息是排障的生命线。确认服务有问题后,先抓线程堆栈、GC日志、系统资源快照、进程文件描述符等现场数据,再谈重启和回滚。没有现场数据,诊断永远是盲人摸象;有了数据,哪怕当时分析不出来,事后也能慢慢复盘。

第三,保持对“变更”的高度敏感。故障往往跟着变更走,上线新版本、调整配置、扩容缩容、切换机房,每一个变更节点都是故障的高发窗口。排查时把变更列表当作第一嫌疑对象,很多时候能大幅缩短定位时间。

第四,不断积累自己的命令“工具箱”。把常用的排查命令和判断逻辑记熟到不用查文档,比如vmstat看哪一列、jstack怎么换算线程号、ss怎么判断连接状态、awk怎么写统计命令,这些在深夜都是救命技能。

最后分享一个小习惯:每次处理完一次故障,我都会把用到的所有命令按顺序归纳保存到自己的命令行速查笔记里,并在每个命令旁边标注“当时用它得出了什么结论”。久而久之,这份笔记越来越厚,也就成了真正属于自己的排障作战地图。希望这篇长文也能帮你把地图的一角画出来,下次夜深人静、手机震动的那一刻,你打开电脑时,手不抖、心不慌、思路清。

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

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

立即咨询