ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

CPU占用率高排查:从top到perf的Linux实战指南

CPU占用率高排查:从top到perf的Linux实战指南 简介面向Linux运维工程师的高CPU占用问题排查实战文档聚焦系统负载飙高时的定位与处理思路。资源系统梳理两种常用排查方法一种通过top排序后结合top -H、线程ID转十六进制、jstack查看线程状态另一种通过ps -mp获取线程耗时并排序再结合jstack打印堆栈二者均能快速锁定异常线程与相关代码。文档还融入真实生产案例演示Java进程CPU占用率达到300%时的完整排查过程从初始top发现PID到最终定位线程堆栈避免盲目重启服务同时介绍Zabbix、Nagios、阿里云监控及“王教授”运维工具的告警机制强调监控前置与主动发现的价值。资源为单个PDF文件大小147KB内容精炼、操作命令明确适合运维人员和后端开发者随时查阅文中方法无需图形界面可直接在纯命令行环境实践。已有4448人学习下载对日常系统维护和故障应急具有实际参考意义。1. CPU占用率高的排查先定位再动手别把玄学当性能分析Linux系统中CPU占用率偏高可能是运维群里出现频率最高的告警。很多人的第一反应是登录服务器敲top看到哪个进程%CPU高就kill -9哪个这套流程在压测环境能交差到了生产环境经常会翻车同一个进程的 CPU 统计跟监控平台对不上或者机器 load 已经快拉满top里 CPU 占用却不到 60%。本文把一套在服务器上反复用的排查思路拆开讲怎么从进程定位到线程、从用户态挖到内核态、再从现场判断该调业务还是调资源适合负责后台服务和容器平台的开发运维也适合准备 linux 面试题的同学在纸上把整套流程推演一遍。2. 用 top、ps、mpstat 把高占用进程和线程揪出来2.1 top 里的三个数%CPU、load average、us/sy 的正确读法top是大家最熟的 linux 常用命令但多数人只看了两个数第一行的 load average 和进程列表里的%CPU。这两个数恰恰最容易误读。load average 后面的三个值分别代表过去 1 分钟、5 分钟、15 分钟的平均活跃进程数。活跃进程包含正在运行的 R 状态进程和等待 IO 的 D 状态进程所以 load 高不等于 CPU 高。一台 4 核机器如果 load 是 4.0大致可以认为跑满但如果进程都在等磁盘load 到 8 也可能%CPU只有 20%。判断是否 CPU 问题要以 CPU 状态行和%CPU为准load 只用来感受整体压力趋势。CPU 状态行里重点看 us(user)、sy(system)、wa(iowait)、si(softirq)、st(steal)。us 高是业务代码消耗sy 高是系统调用或内核态消耗wa 高要转向磁盘排查si 高要怀疑网络包处理st 高则要考虑虚拟化宿主机抢占。进入top后先按数字键1把 CPU 展开成每个物理核的视图如果只有个别核打满说明问题可能出在单线程或中断绑核后续排查方向完全不一样。进程列表默认按 CPU 排序但按P可以强制排序按H可以切换成线程视图按c显示完整命令行。这里花一分钟记住top -H -p PID这个组合它会直接进入某个进程的线程视图是定位单线程热点最快的入口。2.2 ps 的 %CPU 只是平均值线程级视角要交给 pidstat很多新手只靠ps aux看 CPU这是第一个坑ps 报告的是进程从启动到现在的平均 CPU 占用不是当前实时值。一个刚崩溃重启的进程哪怕现在把 CPU 打满ps里也可能只显示个位数百分比。所以ps适合拿来确认进程存在、PID、父子关系不适合判断当前热点。我一般会分两步。第一步用ps做粗筛把所有进程按 CPU 占用排序看一眼到底哪个进程可疑ps -eo pid,ppid,%cpu,%mem,user,comm --sort-%cpu | head -n 20-e表示所有进程-o指定输出列--sort-%cpu按 CPU 占用从高到低排序。这条命令 1 秒出结果适合在 CPU 飙高时先用它定一个嫌疑进程再用下面这条看该进程内部的线程pidstat -u -t -p 12345 1 5pidstat来自 sysstat 包-u表示监控 CPU-t显示线程级别详情-p 12345指定进程 PID最后的1 5表示每秒采样一次、共采样 5 次。输出里每行对应一个线程%usr是用户态占用%system是内核态占用%CPU是线程整体占用TID是线程号。这样定位出来的线程号可以直接对照jstack、perf的调用栈结果把问题从“进程层面”压到“线程层面”。这里有个很重要的换算概念单个线程的%CPU上限就是 100%相当于占满一个逻辑核一个进程显示 300%说明它内部有 3 个线程在并行跑满。后续不管用perf还是看 Java 线程栈都要以 TID 为单位去对而不是用 PID。2.3 mpstat 看核间分布单核打满和全核打满是两种病判断完线程下一步要知道 CPU 压力是不是均匀分布在所有核上。这决定了你要查的是单线程代码问题、绑核问题还是多线程并发资源耗尽。命令是mpstatmpstat -P ALL 1 3-P ALL展示所有 CPU 核1 3表示每秒输出一次、输出三次。返回的表格里每一行是一个 CPU 核的实时占用%usr、%sys、%irq、%soft、%steal分别对应不同来源。看到的结果通常归成两类。一类是某个核长期超过 90%其他核却很悠闲这种大概率是单线程的逻辑写在关键路径上或者内核把中断都塞给了一个核另一类是每个核都高这种要么是多线程死循环要么是锁竞争导致所有线程都在自旋等待要么是宿主机 CPU 超卖。还有一种容易被忽略的情况业务进程每个核都只有 30% 左右但 CPU 总占用加起来很高。这种分布常见于日志写入、压缩、序列化这类影子开销多的服务。不要只盯着最高的核看要按“总 CPU 时间消耗”去算账。观察现象可能原因下一步动作单核打满其他核空闲单线程密集计算、中断绑核查代码热点、查 /proc/interrupts所有核同步打满多线程死循环、锁竞争perf 采样看内核态函数各核 20%~40% 但总量高日志、压缩、序列化等影子开销逐一核对辅助进程与内核线程%steal 明显偏高虚拟机 CPU 被宿主机抢占检查云主机规格与邻居负载3. 从进程态挖到内核态perf、vmstat 与中断风暴的排查路径3.1 perf top 看热点函数数据比直觉靠谱进程和线程定位完了只知道“谁”在烧 CPU还不知道“为什么”。这时候再猜就属于玄学直接把采样工具挂上去看内核和用户态函数分布。最简单的是perf topperf top -p 12345它会按 CPU 采样频率对当前进程的热点函数实时排序默认带调用栈展开功能。看到用户态函数名基本能判断是不是死循环看到内核态函数名也能猜个大概方向。比如native_queued_spin_lock_slowpath出现频率很高多半是自旋锁竞争do_softirq相关函数高要转向网络包处理finish_task_switch高说明线程切换本身消耗了大量 CPU进程内线程数可能太多。如果希望保存下来慢慢分析可以用采样方式perf record -g -p 12345 -- sleep 10 perf report-g记录调用栈-p指定进程-- sleep 10表示采 10 秒就自动结束。perf report进入交互界面后会生成一张热点调用树按回车逐级展开能看到完整调用链。这条链路比任何日志都能说明问题。需要注意两点。第一内核符号可能需要 root 权限才能读全普通用户经常看到一堆[unknown]生产环境可以临时用 root 执行受限命令后退出不要长期挂 root daemon。第二Java、Go 这类 JIT 或运行时语言perf 直接看到的用户态符号往往是运行时自身配合jstack或py-spy把 TID 翻译成业务线程名才算是闭环。3.2 vmstat 的 r 列和 cs 列上下文切换为什么会飙升perf能看函数级但很多机房环境不方便装额外工具此时vmstat是个不需要安装的备选。执行vmstat 1 5关注五列r、cs、us、sy、wa。r 是当前处于可运行状态的进程数如果这个值持续大于 CPU 核数说明有一批进程在排队CPU 确实不够用cs 是每秒上下文切换次数这个值正常情况下每核几百到一千多超过两三万就要警惕进程线程可能像抽风一样在抢时间片us 和 sy 分别是用户态与内核态的 CPU 时间占比wa 是 IO 等待占比。我最常碰到的场景是CPU 占用看着不高us 只有 20%但 sy 占了 40%cs 数值异常高。这种往往是短生命周期线程大量创建销毁或者定时器、信号触发太频繁导致 CPU 大量浪费在“切换”而不是“干活”上。排查时用pidstat -w -t -p PID 1 5看每个线程的 cswch/s(自愿切换) 和 nvcswch/s(非自愿切换)自愿切换过多说明线程在等待锁或 IO非自愿切换过多说明时间片不够、线程数超过核数太多。3.3 把 D 状态进程和 iowait 从 CPU 占用里剥出去很多人把 load 高和 CPU 高混为一谈真实世界里有大量 load 很高但 CPU 闲置的场景。罪魁祸首通常是 D 状态进程也就是不可中断睡眠状态典型情况是进程在等磁盘 IO 或 NFS 响应。D 状态进程会计入 load average但不消耗 CPU 时间片所以top里 CPU 占用看着不高机器却卡得不行。排查命令很简单ps -eo pid,stat,wchan:30,comm | awk $2 ~ /^D/ {print}wchan:30显示进程当前等待的内核函数名能看到它究竟卡在哪个内核函数上。比如卡在wait_on_page_bit说明在等内存页回写卡在nfs相关函数说明远端存储有问题。看到一堆 D 状态进程时不要再调 CPU 配置先去查磁盘iostat -x 1看%util和svctm或者确认 NFS 挂载点是否可达。3.4 中断风暴ksoftirqd 和网卡多队列还有一种 CPU 高是中断带来的表面上找不到用户态进程但top里 si(软中断) 或者 hi(硬中断) 不低同时 ksoftirqd 内核线程占用会飙上去。这个场景在流量型服务上很常见网卡每收一个包就触发一次中断如果小包特别多中断处理本身吃掉的 CPU 可能比业务代码还多。先看中断是否集中在某个核上cat /proc/interrupts左侧是中断号各列对应每个 CPU 核收到的中断次数。如果网卡相关中断号只在 CPU0 上涨说明当前驱动或配置把所有网络中断都绑到了第一个核这就是单核被打满但其他核空闲的原因之一。常见做法是先确认网卡是否支持多队列用网卡工具查看队列数量然后调整队列数与 CPU 核数匹配让多个核分摊收包中断。如果是虚拟化环境且驱动不支持多队列可以尝试开启 RPS/RFS 这类软件分发机制让软中断均匀分散到各核避免单核成为瓶颈。这类问题的后续验证也很直接mpstat -P ALL里各核%soft是否趋于均匀且用户态业务 CPU 是否顺势降下来。4. 对高 CPU 问题的常见处置手段从锁竞争到进程配额4.1 死循环和忙等从热点函数到止损措施代码里出现死循环或忙等是 CPU 高最直接的原因。perf top显示某个用户态函数长期压在顶部基本就能下结论。处理顺序应该是先止损、再定位、后修复。止损手段要看服务形态。能快速回滚的版本直接回滚不能回滚的临时把并发调低比直接 kill 进程更安全因为 kill 会中断所有请求影响面更大。常见做法是先用kill -STOP PID暂停进程确认现场影响范围后再决定是否kill -TERM。kill -9是最后的选择因为它不给进程清理连接和释放内存的机会可能留下半关闭的 socket 或脏数据。止血后再按语言选工具C/C 用perf看调用栈Java 服务用jstack -l PID抓线程栈多抓几次对比是不是同一个方法卡住Python 服务可用py-spy dump --pid PID拿到当前正在执行的 Python 函数名。定位到具体函数后再改代码不要不停重启重启只是把黑匣子又盖回去了。4.2 锁竞争线程数不是越大越好另一个高发原因是锁竞争。现象很典型CPU 占用高业务吞吐却上不去perf top中自旋锁相关函数排在前列火焰图里锁等待区域又宽又矮。很多人第一反应是加线程实际上在高 CPU 密集场景下线程数远超核数就会产生大量上下文切换和锁自旋CPU 全部耗在等锁上活没干多少。这类问题要先看锁的粒度。把大锁拆成细锁、用读写锁代替互斥锁、减少持锁期间的操作都是常用手段。线程池大小也不是简单 2 倍核数就合理CPU 密集场景通常设为核数或核数加一IO 密集场景再放宽。这个问题也是 linux 面试题里反复出现的给你一个四核机器线程池开 100 个线程跑纯计算吞吐能提升吗答案是不能反而可能因为锁竞争和切换开销让性能下降。如果代码一时改不动可以先用taskset把进程绑定到固定核上限制它与其他进程争抢但这只适用于单实例或对延迟不敏感的服务多实例场景慎用。4.3 日志滚轮和压缩让日志进程成了 CPU 大户很多 CPU 高的问题不在业务进程而在日志链路上。新装的 Linux 系统更容易踩这个坑systemd-journald 默认把每个服务的输出都收进 journal如果某个程序疯狂打日志journald 的 CPU 会先飙起来。接着是旧日志压缩归档日志越大压缩进程消耗越高。排查时别只盯业务进程把systemd-journald、rsyslogd、logrotate的 CPU 都看一遍pidstat -u -p $(pidof systemd-journald) 1 5如果 journald 占用显著先看是不是有服务一条条刷屏把日志级别调上去或者把无关输出重定向到 /dev/null。journald 侧可以调整/etc/systemd/journald.conf里的SystemMaxUse限制总日志容量RateLimitIntervalSec和RateLimitBurst控制单位时间最多接收多少条日志修改后重启 journald 生效。rsyslog 同步写盘压力大时可以改成异步队列或者按日志量调整滚动策略避免一个日志文件膨胀到几百兆后再一次性压缩。这个环节经常被忽略属于排查顺序里性价比很高的一步。4.4 用 systemd 配额给失控进程设上限当进程暂时杀不得、代码又改不动可以先把它的 CPU 占用限制住保住同一台机器上其他服务。systemd 管理服务可以直接用运行时命令systemctl set-property your-service.service CPUQuota150%CPUQuota150%表示最多使用 1.5 个核的 CPU 资源底层对应 cgroup 的 cpu.max 配额150% 实际是 150000/100000 的比例关系。设置后立即生效重启也会保留。想临时看一下效果再决定去留可以执行同样的命令把配额改回空值去除限制。对不在 systemd 管理下的进程可以用cpulimit -p PID -l 50这类用户态工具它通过暂停和恢复进程来控制 CPU适合应急但不适合精度要求高的场景。要记住配额只是止损不是修复长时间用配额压着有问题的进程会让延迟劣化且难以察觉还是要留出时间窗处理根因。4.5 虚拟机 CPU steal宿主机抢走了本该属于你的 CPU云服务器和虚拟化环境里还有一种“假高占用”的根因不在你机器内部而在宿主机超卖。mpstat输出里的%steal字段或者top状态行里的st代表虚拟机申请 CPU 时间但被宿主机调度延后的比例。如果%steal持续超过 10%应用的 CPU 占用和延迟都会肉眼可见地恶化但你在虚机里查任何进程都查不出问题。这种场景的处理思路很简单不要调业务参数先调整部署位置。常见做法是迁移到低负载宿主机或者在购买资源时选择限制超卖比例的实例规格。如果服务跨多台机器优先把流量切走如果无法迁移可以尝试把业务线程的调度优先级调低减少被宿主机抢占时的连锁抖动但这个措施效果有限。判断 CPU 高之前先看%steal能省掉后面一整轮业务层排查。5. CPU占用率排查避坑5 个容易翻车的误判场景5.1 top 显示占用正常但机器整体卡成 PPT现象top里各核%CPU只有 20% 左右但敲命令明显卡顿SSH 窗口响应延迟好几十秒。原因CPU 状态行里的 us、sy 都不高时要回看 load average 和 wa。常见根因是磁盘 IO 饱和进程大量进入 D 状态排进 run queueload 被顶高而 CPU 真正花在“等 IO”上而不是“算”上CPU 百分比自然不高。解决用iostat -x 1看磁盘%util用ps -eo pid,stat | grep D数 D 状态进程确认后再决定扩容磁盘、迁移数据还是排查慢存储。不要把突破点放在 CPU 上那条路是死的。5.2 一个进程显示 300% 或 800%以为是 bug 或中毒现象进程列表里某个进程%CPU显示超过 100%甚至 900%第一反应是数值异常或进程被种了矿机。原因top和pidstat的%CPU都是按核归一化的100% 代表一个逻辑核打满。多线程进程的多个线程并行运行占用多个核总和超过 100% 是完全正常的。解决用top -H -p PID切到线程视图确认是多个线程各占一部分还是某个线程单独占满。按 H 切换后如果所有线程加起来和进程总占用对得上说明是并行计算而非异常如果单个线程超过 100%反而要先怀疑进程绑核或者多个线程绑定同一个核再去查业务是否正常。5.3 strace 一挂上去CPU 占用反而降了现象现场怎么看怎么可疑strace 跟踪几秒后CPU 占用以肉眼可见速度下降让人以为是误报。原因strace 会拦截每次系统调用让原本高频的系统调用变慢整个程序的执行节奏被拉低CPU 占用自然降下来。这不代表问题自动消失是干扰工具改变了测量对象。解决遇到这种情况就不要再用strace -p PID做长时跟踪改用短窗口采集比如timeout 5 strace -f -c -p PID只看汇总统计或者换用perf采样它对程序时序的影响小得多。记住排查工具的副作用本身也是排查的一部分。5.4 监控平台显示 CPU 90%登录服务器看却不到 30%现象监控告警 CPU 持续 90%但 SSH 上去执行top和mpstat占用率只有 30% 左右两边数据对不上。原因监控平台通常按 1 分钟或 5 分钟周期聚合会把高峰期和低峰期平均top看的是当前瞬间值两者采样窗口不同本来就有差异。另一个常见原因是监控统计的是 cgroup 视图而top看的是整机视图容器场景尤其明显。解决先把自己采集的周期拉长用pidstat -u 1 60连续记录一分钟看平均 CPU 和监控平台是否接近再确认容器里用top时是否已经进入了对应容器的 PID namespace。数据对不齐时以长时间连续采样为准不要拿一个瞬间值反驳监控平台。5.5 内存回收被误判为 CPU 问题现象us 和 sy 都不高但 kswapd 内核线程 CPU 起高系统整体响应也变慢。原因内存接近上限时内核要反复扫描内存页做回收这个过程消耗 CPU 时间还会触发直接回收进而阻塞进程。CPU 高只是表面现象根因在内存。解决顺手看free -h和vmstat的 si、so 两列si/so 持续不为零说明已经发生换页。用dmesg -T | tail看有没有 oom 相关记录有的话优先处理内存泄漏和调低缓存上限而不是给 CPU 加配。内存问题伪装成 CPU 问题的比例不低CPU 排查的前三个步骤里应该带一眼内存。6. 把排查流程固化成一个采集脚本再对照验证结果第一次排查 CPU 高时凭记忆敲命令容易漏项等现场过去了再补采集就晚了。我现在养成的习惯是提前在机器上备一个采集脚本任何一个环节告警都先让它抓现场。以下这个脚本会在当前目录生成带时间戳的快照文件涵盖 load、CPU 状态、进程排行、线程排行四个维度#!/bin/bash stamp$(date %Y%m%d_%H%M%S) outdir/tmp/cpu_debug mkdir -p $outdir { echo snapshot $stamp uptime echo ----- CPU state ----- top -bn1 | head -n 8 echo ----- process top ----- ps -eo pid,ppid,%cpu,%mem,user,comm --sort-%cpu | head -n 15 echo ----- thread top ----- pidstat -u -t 1 1 | sort -k 9 -r | head -n 15 } $outdir/snapshot_${stamp}.txt echo saved to $outdir/snapshot_${stamp}.txttop -bn1是非交互模式只取一次快照pidstat -u -t 1 1采样一秒钟内的线程占用用sort -k 9 -r按第 9 列%CPU排序。脚本要在疑似 CPU 高的第一时间执行越早越能抓到当时的真实负载而不是事后补测的近似值。如果问题持续而反复还可以用一段时间的趋势记录替代快照pidstat -u -t -p 12345 5 120 /tmp/cpu_debug/pidstat_$stamp.log 每 5 秒采一次、持续 10 分钟后台运行对比不同时段的线程热点变化能看出是持续打满还是周期性脉冲。验证解决效果时不要只比较单个进程的 CPU 百分比要对照三组指标负载状态下降、上下文切换 cs 回落、应用侧延迟或吞吐恢复。CPU 占用降下来但请求延迟更差了说明只是把热点压住、根因没动。我现在每次调整完配置都会先抓十分钟的pidstat记录和修复前的数据并排看确认业务指标也一起好转才算结束。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表