接到线上告警的那一刻我通常不会先打开监控大盘而是直接登服务器敲命令top看一眼整体vmstat盯两秒钟再用pidstat定位到具体线程。这套“三连”动作对于全栈工程师来说应该像吃饭喝水一样自然。Linux 系统管理指令里和 CPU 相关的部分正是这个系列第二章的核心内容。这篇内容适合所有需要自己排查服务性能问题的人——不管你是后端、运维还是“被迫全栈”的开发者把 CPU 相关的指令练熟了遇到机器卡顿、进程飙高、负载异常的时候才不会像无头苍蝇一样乱抓。我会从系统级观测讲起逐步深入到 CPU 硬件识别、负载与上下文切换、进程级定位与优先级管理最后用一次完整的 CPU 飙高排查过程收尾。全程都会带上真实可用的命令、参数解释和踩坑经验尽量做到你看完就能直接照着用。1. 先把系统级 CPU 状态一眼看清1.1 uptime三分钟看懂 Load Average很多人以为 Load Average 就是 CPU 使用率这是个被误解了很多年的概念。先看命令本身uptime输出大概是这样的14:22:33 up 20 days, 3:12, 2 users, load average: 1.20, 0.80, 0.60重点是最后三个数字它们分别代表最近 1 分钟、5 分钟、15 分钟内的平均活跃任务数。“活跃任务”不仅包含正在跑在 CPU 上的进程还包括不可中断睡眠状态D state的进程。D 状态通常和磁盘 I/O、网络文件系统等待绑定在一起所以负载高并不必然等于 CPU 跑满了。判断负载是否过高的标准要和 CPU 核数挂钩。单核 CPU 的 load 到 1.0 就接近饱和4 核机器 load 到 4.0 才是同等程度。一个常见的解读套路是1 分钟值远大于 15 分钟值说明系统近期有突发压力三个值持续走高说明压力是逐步累积的持续高于核数就要往下查是 CPU 排队还是 D 状态进程堆积。我习惯把 load average 类比成高速公路收费站的排队长度。CPU 核数是收费窗口排队多不一定窗口不够也可能是某个窗口前堵了一辆抛锚的车——这个“抛锚的车”往往就是 D 状态进程。1.2 topCPU 监控的“瑞士军刀”top是最常用的实时监控命令大多数人只是用它看一眼就退出其实里面的信息量很大。常用启动方式是top -d 2-d 2表示每 2 秒刷新一次。进入交互界面后几个按键一定要记住P按 CPU 使用率降序排列默认就是这个1展开或折叠每个逻辑核心的独立使用率H切换线程视图模式能看到进程内的线程c显示完整命令行路径z开启颜色高亮。最值得仔细看的是%Cpu(s)那一行里面藏着系统运行状态的核心信息%Cpu(s): 8.3 us, 2.1 sy, 0.0 ni, 89.3 id, 0.2 wa, 0.0 hi, 0.1 si, 0.0 st字段含义分别是用户态占用、内核态占用、优先级调整过的进程占用、空闲、I/O 等待、硬中断、软中断、被虚拟机管理程序偷走的时间。这行数据是判断性能瓶颈方向的起点us高大概率是业务代码问题sy高要怀疑系统调用或锁竞争wa高就要看磁盘 I/O。还有一个细节容易被忽略top的%CPU列显示的是相对单个逻辑核的比例。也就是说一个多线程进程如果占满 4 个核这里可能显示 400%。看到超过 100% 的数值不要慌这不是 bug后面我会专门讲。脚本或者自动化采集场景下top要用批处理模式top -bn1 | head -20-b是批处理模式-n1表示只输出一次。head -20截取前 20 行可以避免刷新动画把输出搞乱。1.3 htop比 top 更像任务管理器htop不是系统自带的但绝大多数发行版都能装# CentOS/RHEL 系 yum install -y htop # Debian/Ubuntu 系 apt install -y htophtop的优势是交互体验CPU 占用率用彩色条形图展示按F5切换到树状视图能直接看到父子进程关系按F6可以选择排序字段选中进程后按F9可以直接发送信号省得另开一个终端敲kill。不过我必须提醒一句生产环境没有图形界面的情况下htop可能没装而top一定在。所以top的按键用法必须是肌肉记忆htop只是锦上添花。2. 认识机器的 CPU“底细”2.1 lscpu一条命令看全家福排查性能问题之前先搞清楚这台机器到底是什么配置。lscpu是最直观的方式lscpu重点看这几个字段Architecture: x86_64 CPU(s): 8 Thread(s) per core: 2 Core(s) per socket: 4 Socket(s): 1 Model name: Intel(R) Xeon(R) CPU E5-... NUMA node0 CPU(s): 0-7CPU(s)是逻辑核数也就是操作系统看到的 CPU 数量。要算物理核心数逻辑核数 / Thread(s) per core 物理核数。像上面这个例子8 个逻辑核除以每核心 2 线程实际是 4 个物理核开了超线程。flags字段里也藏着信息包含vmx或svm说明 CPU 支持硬件虚拟化包含avx2、avx512说明支持高级向量指令对某些计算密集型程序性能影响很大。判断机器是不是“超线程虚胖”一个简单办法是看Thread(s) per core。超线程对大多数业务来说是加分项但对部分极端计算场景可能反而引发资源争抢这个时候就需要配合绑核操作了。2.2 /proc/cpuinfo 和 /proc/stat数据源头lscpu的输出本质上来自/proc/cpuinfo但后者能提供一些更原始的信息。几个常用命令# 逻辑核数 grep -c processor /proc/cpuinfo # 物理 CPU 数量 grep physical id /proc/cpuinfo | sort -u | wc -l # 每颗物理 CPU 的核数 grep cpu cores /proc/cpuinfo | sort -u真正做 CPU 监控时绕不开/proc/stat。它的第一行是总 CPU 时间片cpu 12345678 2345 345678 90123456 12345 6789 23456 0这 8 个数字依次对应用户态时间user、低优先级用户态时间nice、内核态时间system、空闲时间idle、I/O 等待时间iowait、硬中断时间irq、软中断时间softirq、Steal 时间。注意top里展示的百分比就是从这些累计值里采样计算出来的。CPU 使用率的计算公式不复杂关键在于“两次采样取差值”第一次采样 total1 user1 nice1 system1 idle1 iowait1 irq1 softirq1 steal1 第二次采样 total2 user2 nice2 system2 idle2 iowait2 irq2 softirq2 steal2 CPU 使用率 ( (total2 - total1) - (idle2 - idle1) ) / (total2 - total1) * 100这里用idle的差值作为闲散时间剩下的部分都算“忙”。实际脚本里很多人会把iowait单拎出来看因为 I/O 等待占用高但 CPU 还在发指令不能简单当成纯忙碌。2.3 为什么全栈工程师必须知道“几核几线程”部署服务时这个信息直接决定配置参数的合理性。比如一个 Java 服务默认线程池大小、Nginx 的worker_processes、数据库的连接池配置都和逻辑核数相关。盲目把线程池配成“越大越好”往往会在 CPU 上下文切换上吃大亏。容器环境下尤其明显。假设一台物理机 16 核给某个容器--cpus1容器内lscpu可能仍然显示 16 核但实际能用的 CPU 配额只有 1 核。这时候你在容器内部跑top看到load average高达 10其实不等于整机负载高只说明容器被限制得厉害。所以排查容器 CPU 问题一定要跳出容器看宿主机。物理机上做绑核操作也经常用到# 将进程绑定到逻辑核 0 和 1 上 taskset -pc 0,1 PID # 查看进程当前绑定关系 taskset -pc PIDNUMA 架构下还可以用numastat查看内存访问是否跨节点。绑核这种操作一般不在业务代码里做而是运维层和容量规划层的活但全栈工程师至少要知道有这些命令存在遇到延迟敏感型服务才不至于束手无策。3. 负载、上下文切换与历史数据3.1 vmstat两秒钟判断瓶颈方向vmstat是我最依赖的指令之一它用极少的输出覆盖了 CPU、内存、I/O 三大维度vmstat 1 5每 1 秒采样一次共输出 5 次。关注这几列procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa 3 0 128 20480 1024 65536 0 0 12 48 120 2400 8 2 89 1r运行队列长度等待 CPU 调度的任务数。持续大于 CPU 核数说明 CPU 排队严重。b不可中断睡眠进程数。这个值异常升高通常意味着 I/O 出问题了。cs每秒上下文切换次数。这个指标极其重要后面单独展开。us/sy用户态 / 内核态 CPU 占比。id空闲占比。waI/O 等待占比。判断思路可以整理成套路CPU 瓶颈r高、us高、id低不用犹豫直接去挖用户态代码I/O 瓶颈wa高、b高、bi/bo有持续读写这时盯磁盘iostat上下文切换异常cs飙到几十万级别同时sy也高就要查线程数量和锁竞争。3.2 mpstat把视角切到每个核心vmstat看的是全局平均值有时候会掩盖“单核打满”的真实情况。比如 8 核机器整体空闲 80%但某个核已经被干到 99%这通常意味着应用是单线程或者被绑核了。这时候要用mpstatmpstat -P ALL 1 3输出会列出每个逻辑核的独立占用率12:00:01 AM CPU %usr %nice %sys %iowait %irq %soft %steal %idle 12:00:02 AM all 15.0 0.0 3.0 1.0 0.0 0.5 0.0 80.5 12:00:02 AM 0 99.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 12:00:02 AM 1 2.0 0.0 1.0 0.0 0.0 0.0 0.0 97.0all行是平均值后面的0、1是逻辑核编号。看到某个核独占 99%下一步就去确认这个进程是不是绑核了还是 JVM 的 GC 线程集中在某个核上。3.3 sar把历史翻出来对比线上问题最麻烦的一点是你赶到现场的时候现象可能已经消失了。这时候sar能帮你把过去某段时间的性能数据翻出来。用法也不复杂sar -u 1 3 # 当前 CPU 使用率采样 sar -q 1 3 # 负载和运行队列长度 sar -P ALL 1 1 # 每个核的使用情况sysstat工具包提供了数据采集能力一般通过 cron 每分钟落盘一次归档文件在/var/log/sysstat/目录下。我第一次用sar排查问题时最直观的感受是你能精确地说出“CPU 是从下午 2 点 10 分开始飙升的持续了 25 分钟”这比“下午业务高峰 CPU 比较高”这种模糊表述有用得多。排查方向可以这样用把问题时段和正常时段的sar -q输出对比如果负载均值突然翻倍再结合sar -u看看是用户态还是内核态驱动定位效率会高很多。3.4 上下文切换为什么线程太多反而更慢我们来深入讲讲vmstat里的cs列。上下文切换可以简单理解成 CPU 从一个任务切到另一个任务的“换挡”过程。换挡本身有开销寄存器状态要保存、缓存要失效、调度器要做决策。任务切换得越频繁真正干活的算力被打折越多。生活化类比一个咖啡师同时给 100 个顾客做咖啡如果每个顾客做一口就换下一个大部分时间都浪费在切换上。合理的做法是让每个人至少喝完一杯再换。操作系统也一样线程数量远超核数时每个线程分到的时间片变短切换次数暴增系统整体吞吐反而下降。排查这个问题的命令组合是vmstat 1 10 # 看 cs 列的变化 pidstat -w 1 5 # 看每个进程的主动/被动切换数量pidstat -w会输出cswch/s主动切换和nvcswch/s被动切换。主动切换通常是线程自己让出 CPU比如等锁、等 I/O被动切换是被更高优先级任务抢占。两类数值都高时重点检查线程池是不是开得太大了。4. 进程级 CPU 管理与定位4.1 ps快速揪出 CPU 榜首全局看完了下一步就是找“谁在吃 CPU”。ps命令配合排序输出非常高效ps -eo pid,ppid,%cpu,%mem,user,stat,cmd --sort-%cpu | head -20这条命令把系统中所有进程按 CPU 使用率降序排列只取前 20 条。字段里%cpu和stat值得多说两句。%cpu在ps中是一个很有意思的指标它表示进程从启动到当前时刻累计消耗的 CPU 时间按逻辑核数做了折算。所以一个长期存在的多线程进程%cpu显示 800% 都不奇怪。这个值的弊端是不能反映“此刻”的占用看瞬时一定要用top或pidstat。stat是进程状态常见的有R运行中、S睡眠、D不可中断睡眠、Z僵尸进程。如果你在一堆进程里看到大量D状态CPU 使用率可能不高但 load 已经爆了这时候排查方向应该是 I/O 而不是 CPU。4.2 pidstat线程级 CPU 视图定位到高 CPU 进程之后90% 的场景还需要定位到“哪个线程”在捣乱尤其是 Java、Go 这类多线程运行时。pidstat是最直接的工具pidstat -t -p 12345 1 5-t表示显示线程维度-p指定进程号输出里会多出TID列。假设看到TID 4567的%CPU长期 100%这个线程就是绕不开的对象了。如果排查的是 Java 应用还需要把线程号转成十六进制去线程栈里找对应关系printf %x\n 4567输出的十六进制值比如11d7然后配合线程 dump 文件查找jstack 12345 | grep -A 20 0x11d7这一步能把“CPU 高的线程”和“业务代码里哪一行”关联起来。虽然这个过程听起来繁琐但它在生产环境救过我太多次。非 Java 应用也可以用gdb附加进程查看线程栈不过生产环境附加gdb需要谨慎评估性能影响不小。4.3 nice / renice给进程排优先级Linux 的调度器给每个进程分配了一个优先级指标——nice 值范围从 -20 到 19。数值越小优先级越高默认是 0。普通用户只能调高 nice 值让进程更谦让只有 root 能调低。启动时指定优先级# 以 nice10较低优先级启动一个批量任务 nice -n 10 ./batch_task.sh对已经在跑的进程做调整renice -n 5 -p 12345典型场景是业务高峰期你想跑一个不那么重要的日志压缩任务又不想它抢占在线服务的 CPU。把任务 nice 到 10 以上再启动调度器会自动让它在系统空闲时获得更多时间片。nice不是万能的它只能影响同优先级竞争下的调度比例。如果机器 CPU 本身已经满载nice 值的调节效果有限。极端情况下要限制进程 CPU 占用比例可以借助cpulimit这类工具它会通过暂停/恢复进程的方式把 CPU 占用压到目标值以下。这类工具通常用于“没法改代码又要立刻止血”的场景。5. 一次 CPU 飙高问题的完整排查实录5.1 从告警到结论的六个步骤我把自己在实际项目里沉淀出的一套排查流程固定成六步遇到 CPU 异常基本照做登服务器先跑top找到 CPU 占用 TOP 的进程记录 PID用top -H -p PID或pidstat -t -p PID 1 5定位到具体线程 TID确认这个线程的 CPU 占用是持续还是瞬时至少采样 3 次以上根据应用类型打开线程栈Java 用jstackC/C 用gdb或perf回到vmstat看us/sy比例判断问题出在用户态还是内核态汇总现象和结论确定修复方向并验证。这套流程的顺序是有讲究的先现象、后细节、再根因每一步的输入来自上一步的输出。很多人一上来就strace、perf只会把现场越搞越乱。5.2 用户态高还是内核态高处理方向完全不同在vmstat或者top的 CPU 行里us和sy的比例决定了排查路径。us高最常见的原因有三类业务代码死循环、算法复杂度退化、GC 频繁或内存分配压力大。这类问题基本要在代码层解决压测复现后拿 profiler 找热点函数。sy高则指向内核态工作系统调用频繁比如大量网络读写、文件读写、锁竞争激烈、软中断风暴。我曾经遇到过一个案例某个服务每秒发生几十万次系统调用sy占比超过 70%最后通过批量处理请求和连接复用解决了问题。两者同时高也很常见不能机械地只盯一个方向。我的经验是先把strace -c -p PID统计一下进程的系统调用分布再决定往哪边深挖。5.3 一个模拟案例某服务的 CPU 飙高定位为了把流程讲清楚我模拟一次完整的排查过程。假设有个应用叫“模拟项目 X”是 Java 写的在线服务某天监控报警CPU 使用率 85% 持续 5 分钟。第一步登服务器top -bn1 | head -20看到进程PID 32145Java 进程%CPU接近 700%说明至少占用了 7 个逻辑核状态R。第二步top -H -p 32145 -bn1线程视图里看到TID 32189占 99%。为了确认它是持续高再跑pidstat -t -p 32145 1 5五次采样中TID 32189的%CPU都接近 100%基本可以判断这个线程在疯狂干活。第三步printf %x\n 32189输出7dbd。接着拿线程栈jstack 32145 | grep -A 30 0x7dbd栈信息里能看到这个线程卡在一个 JSON 解析逻辑的循环里最终定位到是配置下发后某个字段的解析出现了无限循环。修复代码并发布后CPU 使用率回落到 3%。这个过程中有一个很重要的小技巧jstack只抓某一瞬间的栈可能抓到的是无关紧要的瞬态。一定要多抓几次至少间隔 3 秒抓 3 份栈交叉对比后共同的调用点才值得深入。6. 常见问题与避坑速查6.1 为什么 top 和监控平台的数值对不上这是最常被问到的问题。监控平台的数据一般通过 agent 定期采集/proc/stat并计算差值采样周期通常是 15 秒到 1 分钟而top默认是 3 秒刷新。两个工具的采样窗口不同算出来的“平均 CPU 使用率”自然有偏差。所以不要在告警时纠结“平台显示 90%top 显示 60%”先确认两边采样周期是否一致再以短周期采样为准。6.2 多线程进程 CPU 超过 100% 正常吗正常。top和ps显示的 CPU 百分比是相对单个逻辑核的。一个进程开了 8 个线程每个线程满负荷跑%CPU就可能显示 800%。判断是否异常不是看这个数字是否超过 100%而是看整体占用是否超出了这台机器的逻辑核数总额。6.3 iowait 高但磁盘看起来不忙是怎么回事wa高说明 CPU 在等待 I/O 完成但这个 I/O 不一定来自本地磁盘。可能是 NFS 网络文件系统卡顿也可能是内存大量 swap 导致页面换入换出。排查时把iostat -x 1和sar -W一起看如果磁盘%util不高但await很大就优先查网络存储和 swap。6.4 nice 值调整后没效果遇到过几次原因是 cgroup 的 CPU 限制优先级高于 nice 值。也就是说如果一个进程已经被容器或 systemd 的CPUQuota限制住了你再怎么调 nice 也没用配额墙比调度优先级更硬。确认进程是否在容器或受限 cgroup 里cat /proc/PID/cgroup如果路径里有明显的一切限制标志那 CPU 问题要从配额角度解决而不是 nice。6.5 排查时别急着 kill -9CPU 飙高确实让人着急但kill -9只能让进程消失问题很快会以另一种形式回来。我见过有人把 GC 线程满血的 Java 服务kill -9之后进程由守护脚本再次拉起又被打满循环往复一整天。正确做法是先抓现场拿到线程栈之后再决定是重启止损还是修复上线。除非服务完全无响应且严重影响用户否则“先取证再动手”始终是更稳妥的选择。我把这类典型问题整理成一张速查表方便你直接对着排查现象可能原因优先排查命令top 与监控平台数值不一致采样周期不同对比两边刷新间隔load 很高但 CPU 空闲D 状态进程、I/O 阻塞vmstat 1、ps -eo status 高业务代码热点、死循环、GCjstack、perf topsy 高系统调用过多、锁竞争strace -c、pidstat -w单核打满其他核空闲单线程应用、绑核mpstat -P ALL 1wa 高但磁盘空闲NFS 卡顿、swap 频繁iostat -x 1、sar -W调 nice 无效cgroup CPU 配额限制cat /proc/PID/cgroup个人体会写在后头CPU 相关的指令真的不难难的是知道什么时候看哪个指标、看到指标之后怎么往下走。我的体会是把uptime、top、vmstat、pidstat这四兄弟用熟能解决 80% 的 CPU 排查问题lscpu、sar、mpstat属于进阶补充遇到硬件识别和历史回溯时再拿出来也不迟。实操中第一次定位到具体线程、找到代码根因的成就感比看十篇教程都来得扎实。这个系列后面还会继续写内存、磁盘和网络先把 CPU 这关过了后面很多思路都是相通的。