开篇:为什么全栈工程师必须搞定CPU指令最近在带几个转行的同学做项目,发现一个很有意思的现象:很多人写代码挺溜,一旦说"服务器卡了,你去看看"就懵了。不是不会敲命令,是不知道敲什么命令、看了输出又不知道什么意思。尤其CPU相关的指令,表面上是"看占用率",实际上背后涉及的是整个系统的负载模型、调度逻辑、进程状态流转这些硬核知识。这篇文章想聊的就是Linux下CPU指令这一整块。我梳理的这套内容,适合两类人:一类是刚走上全栈这条路、还没系统看过系统调优的开发者;另一类是写业务代码写腻了,想往性能调优、稳定性保障方向钻的同行。我会从最常用的几个命令讲起,把参数拆开讲,结合实际排查案例,最后再说说那些"手册里查得到但没人告诉你怎么用"的经验。我这十几年前后端摸爬滚打下来,最深的体会是:CPU指令谁都会打几条,但真正值钱的不是命令本身,而是你看到输出之后能不能快速建立"现象到原因"的映射。下面从最基本的设计思路开始聊。1. CPU监控的整体设计思路与选型考量1.1 为什么先看CPU,而不是先看内存或磁盘很多人排查服务器问题有个习惯,上来就free -h看内存。我的习惯是先看CPU,原因很简单:CPU是整个系统的调度中心。你打开一个网站慢,可能是数据库查询慢,也可能是应用线程在等锁,但所有这些等待,最终都会以某种形式体现在CPU的调度状态里——要么是用户态占用升高,要么是等待I/O的进程数暴涨,要么是上下文切换频率异常。更重要的是,CPU指标是所有性能问题的"前哨"。内存泄漏有滞后性,磁盘撑满是慢性的,但CPU如果是瞬时打满,往往意味着请求流量有突发、代码里有死循环、或者某个GC配置出了问题。这类问题如果不及时发现,会在几秒内拖垮整个服务。所以我的监控策略是:CPU作为第一优先级的预警指标,内存、磁盘、网络作为辅助定位手段。这个思路也体现在我写的监控脚本里——CPU超过阈值先告警,再看其他指标组合定位。1.2 指令选型:实时、统计、采样三个维度缺一不可Linux下CPU相关的命令非常多,top、htop、vmstat、mpstat、sar、pidstat、ps、lscpu,新手很容易陷入"背命令"的误区。我把这些工具按用途分成了三个维度:实时维度:top、htop,适合正在出问题时快速看一眼,秒级刷新,能看到全局概览。统计维度:mpstat、sar,适合抓一段时间内的趋势,能确认问题是持续性的还是瞬时的,还能回溯历史。进程维度:pidstat、ps、top里的进程视图,适合锁定到底是哪个具体进程在消耗CPU,是用户态还是内核态。这三个维度相辅相成。比如线上告警说CPU 100%,你先top确认不是误报,再mpstat看是所有核心都满还是单核满,再用pidstat锁定进程,整个链路下来,定位时间一般不超过两分钟。这套方法我称之为"三段式排查",后面实操部分会详细走一遍流程。1.3 工具背后的关键数据源:/proc文件系统不管你用哪个命令,底层数据基本都来自/proc目录。这块理解透了,很多命令的输出就不用死记了。/proc/stat是CPU总使用情况的大本营,top的第一行、mpstat的所有汇总数据,本质上都是解析这个文件。文件里每一行对应一个CPU核心的累计时间戳,包括用户态(user)、低优先级用户态(nice)、系统态(system)、空闲(idle)、等待I/O(iowait)等字段。/proc/cpuinfo则是CPU硬件信息的源头,型号、主频、核心数、缓存大小,lscpu读的就是它。/proc/PID/stat对应每个进程的CPU消耗细节,pidstat的数据就从这里来。理解了这个底层逻辑,你就明白为什么有的命令输出偶尔会"跳变"——因为它是取两次采样的差值计算的,第一次采样不准或者系统刚好在切换,百分比会短暂失真。这不是命令坏了,是采样原理决定的。后面排查问题的时候,遇到这种"假跳变"就不会慌了。2. 核心指令逐个拆解:从输出到参数2.1 lscpu:先搞清楚你手上有什么硬件很多教程把lscpu一笔带过,我觉得这命令被低估了。规划容器资源、评估服务部署密度、判断Java应用该配几个GC线程,全都依赖准确的CPU拓扑信息。我刚入行时犯过一个错:在一台物理机上起了4个Java服务,每个都按"以为有16核"去配置线程池,结果那台机器实际只有8核16线程(超线程),四个服务抢得死去活来。后来学乖了,部署前必跑一条lscpu。lscpu输出里几个字段要注意:CPU(s):逻辑CPU总数。我见过有人把这个直接当物理核心数,实际上它包含了超线程。Core(s) per socket和Socket(s):两者相乘才是真正的物理核心数。Thread(s) per core:如果是2,说明开了超线程,逻辑CPU是物理核心的两倍。NUMA node:现在的服务器基本都是多路架构,内存访问有远近之分。跨NUMA节点访问内存比本地访问慢很多,所以大厂做性能优化时都会用numactl绑核绑内存,让线程和它访问的数据待在同一个NUMA节点。知道这些之后,你再看Java的-XX:ActiveProcessorCount、Go的GOMAXPROCS,就明白为什么容器环境下不能直接拿runtime.NumCPU()的返回值去开线程池了——它拿到的可能是宿主机的核数,而不是容器限定的核数。这属于容器化场景里的经典坑,后面结合场景再细讲。2.2 top:实时监控的万金油,但要看懂每一行top是出现频率最高的命令,但说实话,大部分人对它的理解停留在"看%CPU那一列"。其实top的输出信息量非常大,我按从上到下解读:第一行负载均值,三个数字分别代表过去1分钟、5分钟、15分钟的平均负载。这里必须区分两个概念:负载和CPU占用率不是一回事。负载高不一定CPU满,可能是D状态(不可中断睡眠)进程太多,比如进程在等磁盘I/O。这时候CPU可能很闲,但系统负载很高,表现出来就是"机器卡死了,但top看CPU才20%"。判断负载是否健康有个经验值:负载均值除以逻辑核心数,如果长期大于0.7,就说明系统偏忙了;如果接近1甚至超过,基本可以判定存在瓶颈。当然这个经验值对IO密集型场景要单独看。第二行和第三行的进程总览,重点看running和sleeping的数量比例。正常情况下sleeping占绝大多数,如果running持续超过核数,说明CPU调度队列一直在积压。第三行CPU状态行,us(用户态)、sy(内核态)、ni(低优先级用户态)、id(空闲)、wa(等待I/O)、hi(硬中断)、si(软中断)、st(被虚拟机偷走的时间)。这里面st很多新手忽略,在虚拟化环境里它至关重要——如果st长期偏高,说明宿主机资源超卖严重,你花钱买的CPU被邻居抢走了,这时候你优化自己应用效果有限,该考虑换实例规格或者迁移了。进程列表,默认按CPU占用排序。看进程的CPU列有个细节:它表示的是该进程占单个核心的百分比,比如200%意味着占满了两个逻辑核心。对于多线程应用,%CPU可以超过100%,这是正常的,别看到超过100%就以为命令出错了。常用交互按键:按P按CPU排序,按M按内存排序,按1展开每个核心的使用情况,按c显示完整命令路径。排查问题时我常先用top -c进入,因为默认不显示完整命令行,很多进程只看进程名根本猜不到它是干嘛的。2.3 mpstat:按核心查看使用率,定位"单核打满"神器top的默认视图是所有核心的加权平均,这在排查某些问题时不够用。比如应用是单线程的,总CPU才12%(假设8核),看起来一点不紧张,但其中一个核心实际已经100%了。这时候用mpstat按核心看,问题立刻暴露。mpstat是sysstat工具包里的,用法不难:# 每2秒采样一次,输出所有核心的情况 mpstat -P ALL 2 # 只看2号核心 mpstat -P 2 2输出里比较关键的是%usr、%sys、%iowait、%irq、%soft这几列。我实际排查中遇到最多的情况是%soft偏高,也就是软中断占比大。软中断是什么?简单说就是网卡收包、定时器等内核任务在软件层面触发的中断处理。如果某个核心的%soft一直30%以上,多半是有大量网络小包在涌入,可以进一步用cat /proc/softirqs确认是NET_RX还是NET_TX导致的。mpstat -P ALL还有一个用途:判断NUMA架构下的调度是否均衡。如果发现负载全压在前几个核心上,后几个核心闲得发慌,要么是中断绑定没有均匀分布,要么是应用线程没有充分利用多核。这时候结合taskset或sched_setaffinity做绑核,效果立竿见影。2.4 vmstat和sar:看趋势、抓历史vmstat是一个被严重低估的命令,它虽然名字带"virtual memory",但第二行的CPU字段非常有用。用法是vmstat 2 5,意思是每2秒采样一次,总共采样5次。我一般盯着这几列:r(运行队列长度,超过CPU核数说明CPU饱和)、b(不可中断睡眠进程数,持续大于0说明有I/O阻塞)、cs(上下文切换次数)、si/so(换入换出)。组合起来看很有意思:r很高但us不高,说明系统资源分配有问题;cs一直几万甚至几十万,说明线程之间切换太频繁,系统时间都耗在调度上了。sar则是真正的"历史回顾"工具,同样来自sysstat包。日常排查中我用的最多的是sar -u(CPU使用率历史)和sar -q(负载历史)。它从/var/log/sa/sa??这个历史文件中读数据,所以能看过去几天甚至几周的状态。这个回溯能力特别适合回答"昨晚三点是不是CPU飙过"这类问题。我接过不少夜间告警的case,人不可能24小时盯着,全靠第二天上班sar -u -f /var/log/sa/sa$(date -d yesterday +%d)翻记录定位。生产环境我建议一定开着sysstat的历史采集,这几乎是零成本的保险。2.5 pidstat和ps:锁定元凶进程前面几个命令告诉你"系统出问题了",pidstat告诉你"谁干的"。# 每2秒输出一次所有进程的CPU统计,持续10次 pidstat -u 2 10 # 只看某个PID pidstat -p 12345 -u 2pidstat相比top的优势是输出格式稳定,方便脚本采集和分析。它能区分用户态和内核态的消耗时间,当一个进程%usr高,说明它在做计算;%sys高,说明它在频繁调用系统调用,比如大量文件读写、网络收发、锁竞争。ps大家都熟,但ps aux --sort=-%cpu这个组合很多新手不知道,它直接把CPU占用最高的进程排在最前面,配合head可以快速看Top 10。排查的时候我习惯先用这条命令做一次快照,再上pidstat持续跟踪。3. 实操过程:一次CPU飙升的完整排查实录3.1 场景还原与初步判断前一阵我们的一个模拟项目X(一个内部接口服务)出现线上告警,接口平均响应时间从30ms涨到3秒,监控面板显示CPU已经100%持续了五分钟。我当时的处理顺序是这样的:第一件事不是看代码,而是登录服务器先跑uptime,看负载均值。结果三个数字是12.5、10.2、8.3,而机器是8核,负载已经超过核数,确实是CPU饱和引起的响应变慢,不是单纯的网络抖动。接着执行top -c,几秒后看到两个Java进程的CPU列分别是380%和215%,加起来快600%了,基本确定就是这两个进程在打架。3.2 利用mpstat判断是单核还是整体为了确认是不是某个NUMA节点或某几个核心过热,我跑了:mpstat -P ALL 2输出显示8个核心的使用率比较均匀,都在60%-80%之间浮动,没有出现"一个核心100%、其余空闲"的极端情况。这说明负载是整体性的,不是线程绑核导致的倾斜,更可能是并发量上来或者业务逻辑有什么变化。同时观察top里的us和sy:us大约70%,sy大约25%。sy这么高不太正常,正常的Java业务进程sy一般不会超过10%。这里出现25%说明进程在内核态花费了大量时间,常见原因包括大量系统调用、线程频繁切换、锁竞争。3.3 pidstat锁定可疑线程下一步是把两个Java进程的内部情况挖出来。我执行:pidstat -u -p <PID> 2 5结果发现其中有一个进程的%usr持续在90%以上,另一个进程的%sys非常高。这就有意思了,一个像在疯狂计算,一个像在疯狂做系统调用。同时我用top -H -p <PID>看了线程级别的消耗,记下CPU最高的几个线程号,然后用jstack把这些线程的堆栈导出来,对应线程号找到具体代码位置。这一步非常关键,进程级别的分析只能看到"谁",线程级别的堆栈才能看到"在干什么"。3.4 根因与处理最后定位到的问题是:某段新增的重试逻辑在外部依赖超时时,采用了"忙等待"方式重试,没有加退避策略,相当于每次失败都立刻再试,形成了高CPU消耗。而另一个进程sys高是因为处理超时异常时的日志打印量激增,大量同步写日志的操作压在内核态。处理方案分两步:紧急处理是先把出问题的节点从负载均衡摘掉,让流量不再进来,服务器负载立刻下降,接口恢复;根因修复是给重试逻辑加上指数退避,同时把日志改成异步批量写入。复盘来看,整个排查过程从告警到定位根因大约25分钟,主要时间花在jstack分析线程栈上。指令层面的判断其实五分钟内就完成了。这验证了我前面说的:命令本身不难,难的是把top、mpstat、pidstat组合起来每一步都问"为什么"。3.5 应急脚本:一条命令快速出报告除了交互式排查,我也习惯把常用的采集组合写成一段脚本,出问题时直接跑,把所有关键信息一次性抓下来。这里分享一个我实战中用的简化版:#!/bin/bash # 快速采集CPU排查所需信息 DATE=$(date +%Y%m%d_%H%M%S) echo "=== 负载均值 ===" uptime echo "=== CPU整体使用率(采样5次) ===" mpstat -P ALL 2 5 echo "=== 每个核心的中断分布 ===" cat /proc/softirqs echo "=== CPU占用Top15进程 ===" ps aux --sort=-%cpu | head -16 echo "=== 上下文切换统计 ===" vmstat 1 5 | awk '{print "r="$1, "cs="$12}'这条脚本采集的信息基本覆盖了CPU排查最核心的几项。打完包发给运维同事或者存档备查,比口头说"当时CPU满了"有说服力得多。注意vmstat在有些发行版上没有,需要先安装sysstat工具集。4. 常见问题与排查技巧实录4.1 "CPU满了但不知道是哪个进程"怎么办top排序都拍不清楚的时候,直接用ps aux --sort=-%cpu | head打印前10个。但有些场景下进程名是乱的(比如几十个相同名字的worker进程),这时候需要用top -H -p按线程看,或者用pidstat -t按线程维度统计。另外一个小技巧:Linux的/proc目录可以直接看到每个进程的详细信息,cat /proc/<pid>/status能看进程状态、线程数,cat /proc/<pid>/wchan能看它阻塞在内核的哪个函数上。在内核态排查时比top信息量大得多。4.2 为什么top看到的CPU和云监控面板对不上这个问题几乎每个月都有人问。原因有三层:第一,云监控的采样周期一般是1分钟取平均,而top是秒级瞬时值,两者本来就不在同一时间粒度上。CPU在1分钟内可能飙升了三次又降下来了,监控面板上平均下来可能只有40%,top抓到的瞬间却是90%。第二,容器环境里top看到的是宿主机视角还是容器视角,取决于内核版本和top的实现。新版top(比如procps-ng 3.3.10以上)对容器cgroup的CPU视图做了适配,但也不是所有发行版都默认开启。这就是为什么有时候容器里top看到的CPU是100%,而外面看宿主才30%。第三,st字段(steal time)的存在意味着你看到的CPU时间并不是全都在为你工作。虚拟化环境下宿主机超卖,你的虚拟机时间片被抢占,top会把这部分算到st里,但监控面板可能没展示这个指标。遇到CPU相关告警但自己进程不忙,先看一眼st。4.3 负载高但CPU使用率低,问题出在哪这个经典场景我遇到过好多次:uptime显示负载8以上,但top里CPU空闲率还有60%。新手通常会困惑,实际上是下面几种可能:I/O等待:进程在做大量磁盘读写,卡在D状态,wa这一项会明显偏高。用iostat或者iotop进一步确认。互斥锁等待:进程没有真正使用CPU,而是阻塞在锁上。pidstat -w能看到线程在等待锁的时间,或者用cat /proc/<pid>/status的voluntary_ctxt_switches字段,这个数字如果飙升,锁竞争嫌疑很大。大量短生命周期进程:进程像潮水一样起来又消失,每次都分到一点CPU时间片,但调度器忙得不可开交,负载被推高。我处理过一个典型case:一个定时任务每分钟启动20个子进程去批量处理消息,子进程虽然只跑一两秒,但瞬间并发太高,导致负载平均值虚高,看起来像CPU不够用,实际上系统空闲得很。后来改成信号量限流,限制同时运行的子进程数,负载立刻降到1以下。4.4 软中断占CPU居高不下前面提过mpstat的%soft,这里展开说一下。%soft高一般和网络收包强相关,可以用watch -n 1 "cat /proc/softirqs"观察NET_RX字段的变化速率。如果网卡中断全打在同一个核心上,可以用smp_affinity或者irqbalance做中断分散。我踩过一个坑:某台机器网卡队列数目设置得比CPU核心数少,导致大量中断堆积在少数核心上,%soft常年30%以上,而这些核心上还跑着业务线程。后来调整网卡多队列(ethtool -L)配合irqbalance动态分配,%soft降到了5%以下,业务延迟也跟着降了。这块排查起来有点绕,很多人会误判成应用代码问题,其实根源在中断分发。4.5 CPU频率与能耗:tc和cpupower的隐藏作用提到CPU指令,很少有人提cpupower这个工具,但它在排查性能问题时偶尔能救命。现在的服务器CPU都有动态调频机制(比如intel_pstate驱动),空闲时频率降得很低,突发流量来时频率提升需要几百毫秒。如果业务对延迟极其敏感,可以临时把CPU调到性能模式:cpupower frequency-set -g performance这条命令把所有核心的调频策略切到最高性能,代价就是功耗和发热上升。我有一次在压测环境里发现某台机器吞吐忽高忽低,排查半天发现是频率调度的延迟,切到performance模式后测试数据立刻稳定了。生产环境我不建议长期这么做,但压测、大促前评估容量时,把环境统一到performance模式能让数据更干净。4.6 绑核:taskset和numactl的适用场景最后补一个实用技巧。taskset -c 0,1 命令可以把进程绑定到指定核心运行,numactl --cpunodebind=0则绑定NUMA节点。适用场景主要有三个:一是延迟极其敏感的中间件(比如某些数据库引擎)希望避免上下文切换带来的抖动;二是NUMA架构下让关键线程和数据本地化,避免跨节点访问内存;三是验证某段代码是否因为频繁迁移CPU核心导致cache miss严重。但我要泼一盆冷水:对大部分业务应用,不建议随便绑核。绑核会降低调度器的灵活性,在多租户、混合负载的服务器上反而容易造成核心空转或者过载。我曾经为了"优化"把某服务的核心线程绑定到两个核上,结果流量突发时这两个核很快被打满,其他核心闲着,服务照样被拖垮。绑核前一定评估好业务流量模型,否则是画蛇添足。5. 监控体系的搭建思路与指令的自动化组合5.1 从手动指令到半自动巡检命令敲多了,自然会想能不能自动化。我不建议一上来就上重型监控系统(比如整套Prometheus+Grafana),对于中小团队,先手工把指令组合成脚本,加上定时任务,已经能覆盖大部分场景。我自己的习惯是保留一个简单脚本,每5分钟采集一次CPU核心指标,写到本地日志:#!/bin/bash # /etc/cron.d/cpu_monitor */5 * * * * root /usr/bin/mpstat -P ALL 1 3 >> /var/log/cpu_mpstat.log 2>&1配合日志rotate,可以保留90天的历史,关键时刻翻出来对比。相比部署一整套监控系统,成本几乎为零,但价值很大。5.2 告警阈值怎么设告警阈值设置这件事,经验比理论重要。CPU使用率平均超过80%才告警,这是我见过最多的默认配置。但实际业务场景里,80%的CPU使用率对不同的应用含义完全不同:对计算密集型任务,CPU到80%已经很危险,再做一点流量就可能打满;对IO密集型的Web服务,CPU使用率80%其实还有余量,因为CPU在做计算的同时还有大量时间在等你I/O。更科学的做法是结合请求量和响应时间一起看,单纯看CPU使用率容易误报漏报。我常用的方案是双阈值:CPU超过85%持续5分钟触发普通告警;超过95%持续2分钟触发严重告警。同时配上负载均值的阈值(负载/核数超过1持续3分钟告警)。这套组合在多个项目里验证下来,误报率比单看CPU好很多。5.3 压测场景下的CPU观测方法全栈工程师迟早要接触压测,压测时观测CPU的方法和平常排查故障还不一样。压测追求的是"找到系统的极限拐点",我一般会盯着三个指标:CPU使用率是否接近但不超过100%:如果压测已经把CPU打到100%,响应时间还在缓慢上涨,说明系统容量已经到顶,不是配置问题。上下文切换是否随并发线性增长:如果并发从100涨到200,cs数量翻了十倍,说明线程模型有瓶颈。负载均值是否越过核数线:这是容量规划最直观的参考线。压测结束后的性能报告里,CPU相关的数据我会附上mpstat -P ALL的采样记录,这比只贴一张平均数的图表更有说服力,因为能看出每个核心的负载分布是否均衡,是否某个核心提前成为瓶颈。5.4 趋势数据的简易可视化不想搭Grafana的话,用sar -u的历史数据加上一个简单脚本,也能画出CPU使用率的趋势图。Python里读一下sar输出转成CSV,跑一段matplotlib就能出图。虽然简陋,但日常给团队同步性能状况完全够用。有一次某服务连续三天在凌晨2点CPU有规律地飙升到60%,白天稳定在10%。通过sar -u -f翻历史发现这个规律,最终定位是某个定时任务在凌晨2点全量扫描数据,由于数据量越来越大,任务执行时间越来越长,才导致CPU峰值缓慢爬升。如果没有历史趋势数据,这种慢性问题很难被发现。写在最后:几个我用经验换来的提醒CPU指令这一章的内容到这里就全部讲完了,最后说几句掏心窝的话。第一句,不要在业务高峰期去研究命令。CPU相关的工具链应该提前熟悉,把常用组合记在笔记里,出问题时争取的是"肌肉记忆"而不是现场翻手册。我发现团队里成长最快的同学,往往不是在出了大事那天最拼命的人,而是平时愿意花半小时把top的每一行字段都搞懂的人。第二句,别迷信单条命令的绝对值。CPU数据是典型的"横看成岭侧成峰",top看到的瞬时值、sar看到的平均值、监控面板看到的分钟级采样,经常对不上。经验多了之后你会发现,真正可靠的是趋势和组合,不是孤立的数值。第三句,CPU指令只是入口,不是终点。当你真正熟练地用这些命令定位到一次线上故障时,你可能会第一次感受到"操作系统不是黑盒"。这种对系统运行机制的理解,会反过来影响你写代码——你会开始思考自己的线程池配置是否合理,锁的粒度是否过大,系统调用是否频繁。这才是学习CPU指令真正的收获。下一篇笔记我打算写内存管理相关的指令,从free到vmstat到/proc/meminfo,把虚拟内存、页缓存、Swap这些概念一次讲透。如果你在这些内容里找到一点共鸣,或者有自己的经验想补充,欢迎交流。
开篇:为什么全栈工程师必须搞定CPU指令最近在带几个转行的同学做项目,发现一个很有意思的现象:很多人写代码挺溜,一旦说"服务器卡了,你去看看"就懵了。不是不会敲命令,是不知道敲什么命令、看了输出又不知道什么意思。尤其CPU相关的指令,表面上是"看占用率",实际上背后涉及的是整个系统的负载模型、调度逻辑、进程状态流转这些硬核知识。这篇文章想聊的就是Linux下CPU指令这一整块。我梳理的这套内容,适合两类人:一类是刚走上全栈这条路、还没系统看过系统调优的开发者;另一类是写业务代码写腻了,想往性能调优、稳定性保障方向钻的同行。我会从最常用的几个命令讲起,把参数拆开讲,结合实际排查案例,最后再说说那些"手册里查得到但没人告诉你怎么用"的经验。我这十几年前后端摸爬滚打下来,最深的体会是:CPU指令谁都会打几条,但真正值钱的不是命令本身,而是你看到输出之后能不能快速建立"现象到原因"的映射。下面从最基本的设计思路开始聊。1. CPU监控的整体设计思路与选型考量1.1 为什么先看CPU,而不是先看内存或磁盘很多人排查服务器问题有个习惯,上来就free -h看内存。我的习惯是先看CPU,原因很简单:CPU是整个系统的调度中心。你打开一个网站慢,可能是数据库查询慢,也可能是应用线程在等锁,但所有这些等待,最终都会以某种形式体现在CPU的调度状态里——要么是用户态占用升高,要么是等待I/O的进程数暴涨,要么是上下文切换频率异常。更重要的是,CPU指标是所有性能问题的"前哨"。内存泄漏有滞后性,磁盘撑满是慢性的,但CPU如果是瞬时打满,往往意味着请求流量有突发、代码里有死循环、或者某个GC配置出了问题。这类问题如果不及时发现,会在几秒内拖垮整个服务。所以我的监控策略是:CPU作为第一优先级的预警指标,内存、磁盘、网络作为辅助定位手段。这个思路也体现在我写的监控脚本里——CPU超过阈值先告警,再看其他指标组合定位。1.2 指令选型:实时、统计、采样三个维度缺一不可Linux下CPU相关的命令非常多,top、htop、vmstat、mpstat、sar、pidstat、ps、lscpu,新手很容易陷入"背命令"的误区。我把这些工具按用途分成了三个维度:实时维度:top、htop,适合正在出问题时快速看一眼,秒级刷新,能看到全局概览。统计维度:mpstat、sar,适合抓一段时间内的趋势,能确认问题是持续性的还是瞬时的,还能回溯历史。进程维度:pidstat、ps、top里的进程视图,适合锁定到底是哪个具体进程在消耗CPU,是用户态还是内核态。这三个维度相辅相成。比如线上告警说CPU 100%,你先top确认不是误报,再mpstat看是所有核心都满还是单核满,再用pidstat锁定进程,整个链路下来,定位时间一般不超过两分钟。这套方法我称之为"三段式排查",后面实操部分会详细走一遍流程。1.3 工具背后的关键数据源:/proc文件系统不管你用哪个命令,底层数据基本都来自/proc目录。这块理解透了,很多命令的输出就不用死记了。/proc/stat是CPU总使用情况的大本营,top的第一行、mpstat的所有汇总数据,本质上都是解析这个文件。文件里每一行对应一个CPU核心的累计时间戳,包括用户态(user)、低优先级用户态(nice)、系统态(system)、空闲(idle)、等待I/O(iowait)等字段。/proc/cpuinfo则是CPU硬件信息的源头,型号、主频、核心数、缓存大小,lscpu读的就是它。/proc/PID/stat对应每个进程的CPU消耗细节,pidstat的数据就从这里来。理解了这个底层逻辑,你就明白为什么有的命令输出偶尔会"跳变"——因为它是取两次采样的差值计算的,第一次采样不准或者系统刚好在切换,百分比会短暂失真。这不是命令坏了,是采样原理决定的。后面排查问题的时候,遇到这种"假跳变"就不会慌了。2. 核心指令逐个拆解:从输出到参数2.1 lscpu:先搞清楚你手上有什么硬件很多教程把lscpu一笔带过,我觉得这命令被低估了。规划容器资源、评估服务部署密度、判断Java应用该配几个GC线程,全都依赖准确的CPU拓扑信息。我刚入行时犯过一个错:在一台物理机上起了4个Java服务,每个都按"以为有16核"去配置线程池,结果那台机器实际只有8核16线程(超线程),四个服务抢得死去活来。后来学乖了,部署前必跑一条lscpu。lscpu输出里几个字段要注意:CPU(s):逻辑CPU总数。我见过有人把这个直接当物理核心数,实际上它包含了超线程。Core(s) per socket和Socket(s):两者相乘才是真正的物理核心数。Thread(s) per core:如果是2,说明开了超线程,逻辑CPU是物理核心的两倍。NUMA node:现在的服务器基本都是多路架构,内存访问有远近之分。跨NUMA节点访问内存比本地访问慢很多,所以大厂做性能优化时都会用numactl绑核绑内存,让线程和它访问的数据待在同一个NUMA节点。知道这些之后,你再看Java的-XX:ActiveProcessorCount、Go的GOMAXPROCS,就明白为什么容器环境下不能直接拿runtime.NumCPU()的返回值去开线程池了——它拿到的可能是宿主机的核数,而不是容器限定的核数。这属于容器化场景里的经典坑,后面结合场景再细讲。2.2 top:实时监控的万金油,但要看懂每一行top是出现频率最高的命令,但说实话,大部分人对它的理解停留在"看%CPU那一列"。其实top的输出信息量非常大,我按从上到下解读:第一行负载均值,三个数字分别代表过去1分钟、5分钟、15分钟的平均负载。这里必须区分两个概念:负载和CPU占用率不是一回事。负载高不一定CPU满,可能是D状态(不可中断睡眠)进程太多,比如进程在等磁盘I/O。这时候CPU可能很闲,但系统负载很高,表现出来就是"机器卡死了,但top看CPU才20%"。判断负载是否健康有个经验值:负载均值除以逻辑核心数,如果长期大于0.7,就说明系统偏忙了;如果接近1甚至超过,基本可以判定存在瓶颈。当然这个经验值对IO密集型场景要单独看。第二行和第三行的进程总览,重点看running和sleeping的数量比例。正常情况下sleeping占绝大多数,如果running持续超过核数,说明CPU调度队列一直在积压。第三行CPU状态行,us(用户态)、sy(内核态)、ni(低优先级用户态)、id(空闲)、wa(等待I/O)、hi(硬中断)、si(软中断)、st(被虚拟机偷走的时间)。这里面st很多新手忽略,在虚拟化环境里它至关重要——如果st长期偏高,说明宿主机资源超卖严重,你花钱买的CPU被邻居抢走了,这时候你优化自己应用效果有限,该考虑换实例规格或者迁移了。进程列表,默认按CPU占用排序。看进程的CPU列有个细节:它表示的是该进程占单个核心的百分比,比如200%意味着占满了两个逻辑核心。对于多线程应用,%CPU可以超过100%,这是正常的,别看到超过100%就以为命令出错了。常用交互按键:按P按CPU排序,按M按内存排序,按1展开每个核心的使用情况,按c显示完整命令路径。排查问题时我常先用top -c进入,因为默认不显示完整命令行,很多进程只看进程名根本猜不到它是干嘛的。2.3 mpstat:按核心查看使用率,定位"单核打满"神器top的默认视图是所有核心的加权平均,这在排查某些问题时不够用。比如应用是单线程的,总CPU才12%(假设8核),看起来一点不紧张,但其中一个核心实际已经100%了。这时候用mpstat按核心看,问题立刻暴露。mpstat是sysstat工具包里的,用法不难:# 每2秒采样一次,输出所有核心的情况 mpstat -P ALL 2 # 只看2号核心 mpstat -P 2 2输出里比较关键的是%usr、%sys、%iowait、%irq、%soft这几列。我实际排查中遇到最多的情况是%soft偏高,也就是软中断占比大。软中断是什么?简单说就是网卡收包、定时器等内核任务在软件层面触发的中断处理。如果某个核心的%soft一直30%以上,多半是有大量网络小包在涌入,可以进一步用cat /proc/softirqs确认是NET_RX还是NET_TX导致的。mpstat -P ALL还有一个用途:判断NUMA架构下的调度是否均衡。如果发现负载全压在前几个核心上,后几个核心闲得发慌,要么是中断绑定没有均匀分布,要么是应用线程没有充分利用多核。这时候结合taskset或sched_setaffinity做绑核,效果立竿见影。2.4 vmstat和sar:看趋势、抓历史vmstat是一个被严重低估的命令,它虽然名字带"virtual memory",但第二行的CPU字段非常有用。用法是vmstat 2 5,意思是每2秒采样一次,总共采样5次。我一般盯着这几列:r(运行队列长度,超过CPU核数说明CPU饱和)、b(不可中断睡眠进程数,持续大于0说明有I/O阻塞)、cs(上下文切换次数)、si/so(换入换出)。组合起来看很有意思:r很高但us不高,说明系统资源分配有问题;cs一直几万甚至几十万,说明线程之间切换太频繁,系统时间都耗在调度上了。sar则是真正的"历史回顾"工具,同样来自sysstat包。日常排查中我用的最多的是sar -u(CPU使用率历史)和sar -q(负载历史)。它从/var/log/sa/sa??这个历史文件中读数据,所以能看过去几天甚至几周的状态。这个回溯能力特别适合回答"昨晚三点是不是CPU飙过"这类问题。我接过不少夜间告警的case,人不可能24小时盯着,全靠第二天上班sar -u -f /var/log/sa/sa$(date -d yesterday +%d)翻记录定位。生产环境我建议一定开着sysstat的历史采集,这几乎是零成本的保险。2.5 pidstat和ps:锁定元凶进程前面几个命令告诉你"系统出问题了",pidstat告诉你"谁干的"。# 每2秒输出一次所有进程的CPU统计,持续10次 pidstat -u 2 10 # 只看某个PID pidstat -p 12345 -u 2pidstat相比top的优势是输出格式稳定,方便脚本采集和分析。它能区分用户态和内核态的消耗时间,当一个进程%usr高,说明它在做计算;%sys高,说明它在频繁调用系统调用,比如大量文件读写、网络收发、锁竞争。ps大家都熟,但ps aux --sort=-%cpu这个组合很多新手不知道,它直接把CPU占用最高的进程排在最前面,配合head可以快速看Top 10。排查的时候我习惯先用这条命令做一次快照,再上pidstat持续跟踪。3. 实操过程:一次CPU飙升的完整排查实录3.1 场景还原与初步判断前一阵我们的一个模拟项目X(一个内部接口服务)出现线上告警,接口平均响应时间从30ms涨到3秒,监控面板显示CPU已经100%持续了五分钟。我当时的处理顺序是这样的:第一件事不是看代码,而是登录服务器先跑uptime,看负载均值。结果三个数字是12.5、10.2、8.3,而机器是8核,负载已经超过核数,确实是CPU饱和引起的响应变慢,不是单纯的网络抖动。接着执行top -c,几秒后看到两个Java进程的CPU列分别是380%和215%,加起来快600%了,基本确定就是这两个进程在打架。3.2 利用mpstat判断是单核还是整体为了确认是不是某个NUMA节点或某几个核心过热,我跑了:mpstat -P ALL 2输出显示8个核心的使用率比较均匀,都在60%-80%之间浮动,没有出现"一个核心100%、其余空闲"的极端情况。这说明负载是整体性的,不是线程绑核导致的倾斜,更可能是并发量上来或者业务逻辑有什么变化。同时观察top里的us和sy:us大约70%,sy大约25%。sy这么高不太正常,正常的Java业务进程sy一般不会超过10%。这里出现25%说明进程在内核态花费了大量时间,常见原因包括大量系统调用、线程频繁切换、锁竞争。3.3 pidstat锁定可疑线程下一步是把两个Java进程的内部情况挖出来。我执行:pidstat -u -p <PID> 2 5结果发现其中有一个进程的%usr持续在90%以上,另一个进程的%sys非常高。这就有意思了,一个像在疯狂计算,一个像在疯狂做系统调用。同时我用top -H -p <PID>看了线程级别的消耗,记下CPU最高的几个线程号,然后用jstack把这些线程的堆栈导出来,对应线程号找到具体代码位置。这一步非常关键,进程级别的分析只能看到"谁",线程级别的堆栈才能看到"在干什么"。3.4 根因与处理最后定位到的问题是:某段新增的重试逻辑在外部依赖超时时,采用了"忙等待"方式重试,没有加退避策略,相当于每次失败都立刻再试,形成了高CPU消耗。而另一个进程sys高是因为处理超时异常时的日志打印量激增,大量同步写日志的操作压在内核态。处理方案分两步:紧急处理是先把出问题的节点从负载均衡摘掉,让流量不再进来,服务器负载立刻下降,接口恢复;根因修复是给重试逻辑加上指数退避,同时把日志改成异步批量写入。复盘来看,整个排查过程从告警到定位根因大约25分钟,主要时间花在jstack分析线程栈上。指令层面的判断其实五分钟内就完成了。这验证了我前面说的:命令本身不难,难的是把top、mpstat、pidstat组合起来每一步都问"为什么"。3.5 应急脚本:一条命令快速出报告除了交互式排查,我也习惯把常用的采集组合写成一段脚本,出问题时直接跑,把所有关键信息一次性抓下来。这里分享一个我实战中用的简化版:#!/bin/bash # 快速采集CPU排查所需信息 DATE=$(date +%Y%m%d_%H%M%S) echo "=== 负载均值 ===" uptime echo "=== CPU整体使用率(采样5次) ===" mpstat -P ALL 2 5 echo "=== 每个核心的中断分布 ===" cat /proc/softirqs echo "=== CPU占用Top15进程 ===" ps aux --sort=-%cpu | head -16 echo "=== 上下文切换统计 ===" vmstat 1 5 | awk '{print "r="$1, "cs="$12}'这条脚本采集的信息基本覆盖了CPU排查最核心的几项。打完包发给运维同事或者存档备查,比口头说"当时CPU满了"有说服力得多。注意vmstat在有些发行版上没有,需要先安装sysstat工具集。4. 常见问题与排查技巧实录4.1 "CPU满了但不知道是哪个进程"怎么办top排序都拍不清楚的时候,直接用ps aux --sort=-%cpu | head打印前10个。但有些场景下进程名是乱的(比如几十个相同名字的worker进程),这时候需要用top -H -p按线程看,或者用pidstat -t按线程维度统计。另外一个小技巧:Linux的/proc目录可以直接看到每个进程的详细信息,cat /proc/<pid>/status能看进程状态、线程数,cat /proc/<pid>/wchan能看它阻塞在内核的哪个函数上。在内核态排查时比top信息量大得多。4.2 为什么top看到的CPU和云监控面板对不上这个问题几乎每个月都有人问。原因有三层:第一,云监控的采样周期一般是1分钟取平均,而top是秒级瞬时值,两者本来就不在同一时间粒度上。CPU在1分钟内可能飙升了三次又降下来了,监控面板上平均下来可能只有40%,top抓到的瞬间却是90%。第二,容器环境里top看到的是宿主机视角还是容器视角,取决于内核版本和top的实现。新版top(比如procps-ng 3.3.10以上)对容器cgroup的CPU视图做了适配,但也不是所有发行版都默认开启。这就是为什么有时候容器里top看到的CPU是100%,而外面看宿主才30%。第三,st字段(steal time)的存在意味着你看到的CPU时间并不是全都在为你工作。虚拟化环境下宿主机超卖,你的虚拟机时间片被抢占,top会把这部分算到st里,但监控面板可能没展示这个指标。遇到CPU相关告警但自己进程不忙,先看一眼st。4.3 负载高但CPU使用率低,问题出在哪这个经典场景我遇到过好多次:uptime显示负载8以上,但top里CPU空闲率还有60%。新手通常会困惑,实际上是下面几种可能:I/O等待:进程在做大量磁盘读写,卡在D状态,wa这一项会明显偏高。用iostat或者iotop进一步确认。互斥锁等待:进程没有真正使用CPU,而是阻塞在锁上。pidstat -w能看到线程在等待锁的时间,或者用cat /proc/<pid>/status的voluntary_ctxt_switches字段,这个数字如果飙升,锁竞争嫌疑很大。大量短生命周期进程:进程像潮水一样起来又消失,每次都分到一点CPU时间片,但调度器忙得不可开交,负载被推高。我处理过一个典型case:一个定时任务每分钟启动20个子进程去批量处理消息,子进程虽然只跑一两秒,但瞬间并发太高,导致负载平均值虚高,看起来像CPU不够用,实际上系统空闲得很。后来改成信号量限流,限制同时运行的子进程数,负载立刻降到1以下。4.4 软中断占CPU居高不下前面提过mpstat的%soft,这里展开说一下。%soft高一般和网络收包强相关,可以用watch -n 1 "cat /proc/softirqs"观察NET_RX字段的变化速率。如果网卡中断全打在同一个核心上,可以用smp_affinity或者irqbalance做中断分散。我踩过一个坑:某台机器网卡队列数目设置得比CPU核心数少,导致大量中断堆积在少数核心上,%soft常年30%以上,而这些核心上还跑着业务线程。后来调整网卡多队列(ethtool -L)配合irqbalance动态分配,%soft降到了5%以下,业务延迟也跟着降了。这块排查起来有点绕,很多人会误判成应用代码问题,其实根源在中断分发。4.5 CPU频率与能耗:tc和cpupower的隐藏作用提到CPU指令,很少有人提cpupower这个工具,但它在排查性能问题时偶尔能救命。现在的服务器CPU都有动态调频机制(比如intel_pstate驱动),空闲时频率降得很低,突发流量来时频率提升需要几百毫秒。如果业务对延迟极其敏感,可以临时把CPU调到性能模式:cpupower frequency-set -g performance这条命令把所有核心的调频策略切到最高性能,代价就是功耗和发热上升。我有一次在压测环境里发现某台机器吞吐忽高忽低,排查半天发现是频率调度的延迟,切到performance模式后测试数据立刻稳定了。生产环境我不建议长期这么做,但压测、大促前评估容量时,把环境统一到performance模式能让数据更干净。4.6 绑核:taskset和numactl的适用场景最后补一个实用技巧。taskset -c 0,1 命令可以把进程绑定到指定核心运行,numactl --cpunodebind=0则绑定NUMA节点。适用场景主要有三个:一是延迟极其敏感的中间件(比如某些数据库引擎)希望避免上下文切换带来的抖动;二是NUMA架构下让关键线程和数据本地化,避免跨节点访问内存;三是验证某段代码是否因为频繁迁移CPU核心导致cache miss严重。但我要泼一盆冷水:对大部分业务应用,不建议随便绑核。绑核会降低调度器的灵活性,在多租户、混合负载的服务器上反而容易造成核心空转或者过载。我曾经为了"优化"把某服务的核心线程绑定到两个核上,结果流量突发时这两个核很快被打满,其他核心闲着,服务照样被拖垮。绑核前一定评估好业务流量模型,否则是画蛇添足。5. 监控体系的搭建思路与指令的自动化组合5.1 从手动指令到半自动巡检命令敲多了,自然会想能不能自动化。我不建议一上来就上重型监控系统(比如整套Prometheus+Grafana),对于中小团队,先手工把指令组合成脚本,加上定时任务,已经能覆盖大部分场景。我自己的习惯是保留一个简单脚本,每5分钟采集一次CPU核心指标,写到本地日志:#!/bin/bash # /etc/cron.d/cpu_monitor */5 * * * * root /usr/bin/mpstat -P ALL 1 3 >> /var/log/cpu_mpstat.log 2>&1配合日志rotate,可以保留90天的历史,关键时刻翻出来对比。相比部署一整套监控系统,成本几乎为零,但价值很大。5.2 告警阈值怎么设告警阈值设置这件事,经验比理论重要。CPU使用率平均超过80%才告警,这是我见过最多的默认配置。但实际业务场景里,80%的CPU使用率对不同的应用含义完全不同:对计算密集型任务,CPU到80%已经很危险,再做一点流量就可能打满;对IO密集型的Web服务,CPU使用率80%其实还有余量,因为CPU在做计算的同时还有大量时间在等你I/O。更科学的做法是结合请求量和响应时间一起看,单纯看CPU使用率容易误报漏报。我常用的方案是双阈值:CPU超过85%持续5分钟触发普通告警;超过95%持续2分钟触发严重告警。同时配上负载均值的阈值(负载/核数超过1持续3分钟告警)。这套组合在多个项目里验证下来,误报率比单看CPU好很多。5.3 压测场景下的CPU观测方法全栈工程师迟早要接触压测,压测时观测CPU的方法和平常排查故障还不一样。压测追求的是"找到系统的极限拐点",我一般会盯着三个指标:CPU使用率是否接近但不超过100%:如果压测已经把CPU打到100%,响应时间还在缓慢上涨,说明系统容量已经到顶,不是配置问题。上下文切换是否随并发线性增长:如果并发从100涨到200,cs数量翻了十倍,说明线程模型有瓶颈。负载均值是否越过核数线:这是容量规划最直观的参考线。压测结束后的性能报告里,CPU相关的数据我会附上mpstat -P ALL的采样记录,这比只贴一张平均数的图表更有说服力,因为能看出每个核心的负载分布是否均衡,是否某个核心提前成为瓶颈。5.4 趋势数据的简易可视化不想搭Grafana的话,用sar -u的历史数据加上一个简单脚本,也能画出CPU使用率的趋势图。Python里读一下sar输出转成CSV,跑一段matplotlib就能出图。虽然简陋,但日常给团队同步性能状况完全够用。有一次某服务连续三天在凌晨2点CPU有规律地飙升到60%,白天稳定在10%。通过sar -u -f翻历史发现这个规律,最终定位是某个定时任务在凌晨2点全量扫描数据,由于数据量越来越大,任务执行时间越来越长,才导致CPU峰值缓慢爬升。如果没有历史趋势数据,这种慢性问题很难被发现。写在最后:几个我用经验换来的提醒CPU指令这一章的内容到这里就全部讲完了,最后说几句掏心窝的话。第一句,不要在业务高峰期去研究命令。CPU相关的工具链应该提前熟悉,把常用组合记在笔记里,出问题时争取的是"肌肉记忆"而不是现场翻手册。我发现团队里成长最快的同学,往往不是在出了大事那天最拼命的人,而是平时愿意花半小时把top的每一行字段都搞懂的人。第二句,别迷信单条命令的绝对值。CPU数据是典型的"横看成岭侧成峰",top看到的瞬时值、sar看到的平均值、监控面板看到的分钟级采样,经常对不上。经验多了之后你会发现,真正可靠的是趋势和组合,不是孤立的数值。第三句,CPU指令只是入口,不是终点。当你真正熟练地用这些命令定位到一次线上故障时,你可能会第一次感受到"操作系统不是黑盒"。这种对系统运行机制的理解,会反过来影响你写代码——你会开始思考自己的线程池配置是否合理,锁的粒度是否过大,系统调用是否频繁。这才是学习CPU指令真正的收获。下一篇笔记我打算写内存管理相关的指令,从free到vmstat到/proc/meminfo,把虚拟内存、页缓存、Swap这些概念一次讲透。如果你在这些内容里找到一点共鸣,或者有自己的经验想补充,欢迎交流。