
接手过线上服务的人都有体会CPU 飙到 90% 多监控大屏一片红第一反应就是扩容、重启、加限制。但很多时候等你急急忙忙把进程重启了、并发限了业务却好端端的用户压根没感知甚至性能更差了。反过来CPU 才用到 30%服务端却时不时超时、客户端疯狂重试、告警一条接一条。这事儿特别反直觉CPU 使用率高真不等于服务器卡。这篇文章我就想把这层窗户纸捅破结合我自己排查过的问题聊聊 CPU 使用率到底在衡量什么什么情况下 CPU 高是虚惊什么情况下 CPU 低反而是大麻烦以及一套能直接照着操作的排查流程。适合干运维、做后端开发、以及被监控告警吓得天天焦虑的朋友看。1. 先搞懂一个核心概念CPU 使用率到底在衡量什么很多争议其实源自对监控指标本身的误解。CPU 使用率看着是个简单百分比但它的含义比你想象的微妙得多。1.1 CPU 使用率不是平均负载两个指标是两回事先说一个最常见的混淆CPU 使用率%CPU和平均负载Load Average经常被当成一回事这是最大的误区。CPU 使用率衡量的是 CPU 在一段时间内忙的时间占比忙包括执行用户程序指令、处理系统调用、响应中断。而平均负载衡量的是系统中处于可运行状态以及不可中断睡眠状态的进程平均数量。简单理解负载是一个系统中有多少活等着干的长度CPU 使用率是干活的工具被占用了多少。举例子说明一个单核 CPU如果只有一个线程在疯狂计算它的 CPU 使用率是 100%但平均负载可能只有 1.0——这活儿虽然满负荷但没有堆积。如果十个线程同时在跑单核 CPU 使用率顶多 100%但平均负载可能到了 10——说明大量进程在排队系统已经严重超载。所以你会看到两种极端情况。CPU 100%负载却只有 1 左右系统响应通常是不卡的只是这个核很忙CPU 才 50%负载却到了 8 以上那你多半遇到了进程阻塞问题大量任务在那儿排队等着表面看 CPU 有空闲实际已经堵死了。监控上把这两列放在一起看才有意义。1.2 单核与多核的差异还有不那么显式的容量概念很多人看 CPU 使用率只看总数值忽略了核数。同样是 80%八核机器和四核机器含义完全不同一台机器上跑的是单线程密集型任务CPU 总量 12.5%1 核跑满 / 8 核总数它也只是一个核在工作。所以看 CPU 指标时第一件事就是把总使用率对应到核数带宽上去。拓扑上有个很实用的换算方式判断 CPU 是否成为瓶颈别盯着百分比要盯每个核上有多少活儿。单核跑满是事实总占用 12.5% 也是事实两者之间没有矛盾但处理方案完全不一样——前者要考虑能不能让任务并发后者要考虑是不是本来就该串行。我还遇到过一种坑物理机和云主机在核的呈现上不一样某些云主机用的是超线程逻辑核两个逻辑核共享一个物理核的运算单元你以为有 8 核实际物理上就 4 个完整执行核心。这时候跑高算力任务总 CPU 可能到不了 100% 就出现性能下降因为共享单元产生了竞争。这类问题需要配合实测去验证别只看平台标注。1.3 用户态、内核态与等待时间这几分几秒都算在百分比里再往下拆一层。Linux 的 CPU 使用率一般被分解成几个部分ususer用户态 CPU 时间程序自身逻辑在跑。sysystem内核态 CPU 时间系统调用、内核线程、驱动处理占用的时间。waiowait等待 IO 完成。严格说 iowait 时 CPU 是空闲的但它算在CPU 状态里展示。ststeal被虚拟化平台偷走的时间。同一台宿主机上邻居虚机抢资源你的 CPU 时间会被扣掉。ididle真正空闲。这里要特别强调的是用户态与内核态的比值非常有诊断价值。正常业务程序us 占比通常远大于 sy。如果发现 sy 高得异常比如 us 30%、sy 60%那你的程序很可能在做大量的系统调用、文件读写、网络收发或者内核里发生了锁竞争这不是 CPU 不够的问题而是程序在内核里绕圈子。我曾经处理过一个诡异的 CPU 偏高问题程序逻辑很简单但 iostat 显示磁盘才 20% 利用率CPU sy 却占了 40%最后发现是频繁创建和关闭临时文件每次都要做磁盘元数据操作把这些临时文件逻辑优化掉之后sy 直接掉到 5%。还有一个容易忽略的ststeal。云主机时代真的会存在物理 CPU 被邻居挤占的情况表现为你的业务负载不高但 CPU 总使用率里 st 占了很大一块响应变慢。遇到这类现象稳定性排查基本没用因为不是你的程序问题是宿主机资源争抢。2. 为什么 CPU 高不一定卡四个真实场景说清楚指标本身之后我们来打那些CPU 高但系统良好和CPU 低但系统崩溃的经典案例。这些场景我基本都真实遇到过。2.1 场景一单核占满多核空闲这类问题在 Java 应用里尤其常见。代码里用了类似并行流、synchronized、单线程调度器的地方把任务全压到了一个核心上。你去看监控大盘总 CPU 可能只有 12%、25%某一核却烫得不行。服务卡不卡取决于这个单核是否是热点路径。如果用户请求都要经过这里那确实是瓶颈但表现形式不是 CPU 被占满那一天才开始的而是从一开始这个核就是满的负载一点点堆积。如果业务处理并没有明显变慢可能只是这个单核在执行某项后台任务比如指标聚合、日志轮转、对象序列化虽然占满了核心但整体吞吐没有受影响。诊断方法很简单命令行敲 top然后按数字键 1看每个核的使用率。再按大写 P按 CPU 占用排序看看热点进程是不是集中在一个 PID 上。如果单核 100% 而整个系统负载低就得进进程看线程用 top 加 -H 参数或者用 pidstat -t定位到具体的线程 TID。2.2 场景二高 CPU 但任务队列很短这是最经典的假性繁忙。CPU 高说明确实有任务在密集算但如果平均负载不高说明任务来了就能被处理没有排队没有堆积网络栈和应用层都没受影响那用户感知到的延迟就不会变差。举个我实际调过的例子一个画像服务在每天凌晨跑批量特征计算CPU 保持 95% 以上持续两个多小时。因为业务是离线批量任务任务队列一直很稳定在线请求又有独立的进程池高峰期结束时任务刚好算完整个过程用户体验零影响。如果被CPU 95%的裸监控告警吓到贸然限流扩容反而会打断这个批量计算造成数据发布时间延后。所以判断卡不卡核心不是 CPU 高不高而是有没有任务因为 CPU 而排队等待。2.3 场景三CPU 高反而是好事这听起来反直觉但你想想编译打包、数据预处理、缓存预热、加密解密这些场景。CPU 在拼命干活恰恰说明程序在高效利用硬件资源。一个令所有开发都羡慕的运维画面其实是大规模并行编译的时候 CPU 全部飙满编译时间大大缩短。反而不健康的状态是什么CPU 没满负载也很低磁盘 IO 也没跑网络也没什么流量程序却响应慢。那大概率是代码在空转、自旋等待、或者陷入了各种因为配置错误导致的空闲占用。系统太闲也未必是好事。类似的还有游戏服务器、交易系统里的低延迟优化有时候为了省几微秒引入 CPU 忙轮询宁可让核转着等也不让线程睡下去这种系统 CPU 使用率永远不低但响应就是快因为它用 CPU 时间换延迟。你要是按CPU 太高就扩容的思路处理反而拉长了线程调度链路延迟劣化。2.4 场景四IO 等待被算进 CPU 使用率的坑必须单独拿出来讲因为这是新手最容易摔的地方。刚才说过 iowait 在系统监控里以 wa 的形式出现它表示 CPU 处于空闲、正在等待磁盘或网络 IO 完成的状态。很多同学看到 top 输出里 %CPU 列 99%以为 CPU 很忙但用 vmstat 看wa 那列可能高达 80%。这个高 CPU其实不是算术高是CPU 在等待 IO 事件它并没有做有效计算。这种场景下真正的瓶颈是磁盘而不是 CPU。处理方式截然不同。有一次我排查一个数据库节点的 CPU 告警top 显示 mysqld 进程 CPU 用了 200%四核机器但看着 iostat 就明白了%util 98%await 100ms 以上磁盘已经饱和了。mysqld 的 CPU 占用里绝大多数都是等待用户态的内核事件调度消耗解决方式是换更高性能的磁盘、优化冷热数据分离、加大缓冲池而不是给数据库 AIO 配置加并发数后者只会加剧排队。3. 真正让系统卡的是这些指标CPU 高只是表象卡是用户体验概念对应到系统指标通常逃不出下面这些维度。3.1 平均负载Load Averages的正确读法前面提过平均负载这里再深入说一下。Linux 的平均负载有三个数值1 分钟、5 分钟、15 分钟。注意具体读哪一行取决于你想看短期冲击还是长期趋势。判断要诀如果 1 分钟值 5 分钟值 15 分钟值说明负载在上升是涌入或堆积正在发生如果反过来说明高峰正在消退系统在恢复。至于负载多高算高有一个粗略基准按核数算平均负载持续超过核数的 70%就该引起警惕超过核数一般意味着有任务长时间排队体验会有劣化。四核机器 load 长期 6、7说明系统已经过载单核机器 load 到 1已经顶到天花板了。但平均负载高不代表一定是 CPU 忙。D 状态不可中断睡眠也会计入负载常见于大量的 IO 等待。所以用 vmstat 看一眼 r运行队列长度和 b阻塞进程数才能知道负载主要是 CPU 队列造成的还是 IO 阻塞造成的。r 值长期大于核数才是 CPU 资源不足的实锤。3.2 上下文切换与锁竞争这是高端排查里绕不开的指标。现代操作系统按时间片调度线程切换是要花成本的每一次切换都会带来一次 CPU 缓存失效和调度开销。如果上下文切换cscontext switch数量非常高比如每秒几十万次那 CPU 的大量执行时间会耗在切换这件事本身而不是真正做事。情况分两种。**自愿切换voluntary**增多通常是线程等待锁、等待 IO 事件主动让出 CPU这其实是一种正常的阻塞行为**非自愿切换involuntary**猛涨多半是时间片耗尽被硬抢说明线程数过多或调度异常。锁竞争的场景也很典型多个线程同时抢一把锁抢不到的就挂起抢到一瞬间又有一堆线程被唤醒系统出现惊群上下文切换和 sys 占用同时升高。现象是 CPU 整体不高但负载很大业务时快时慢。排查手段是 pidstat -w 看两个进程的 cswch 和 nvcswch再用 strace 或 perf 结合堆栈确认锁的位置。注意在生产环境用 strace 需要格外谨慎它对目标进程有高额性能损耗建议用短时采样或先小流量验证。3.3 内存换页与 SWAP内存不够时操作系统会把不活跃内存页换到磁盘上等到需要时再换回来这个过程会产生缺页中断和换页 IO。问题在于SWAP 的读写速度相对内存慢了几个数量级一旦程序开始频繁和磁盘交换内存页整体性能会断崖式下降。这种场景下CPU 使用率可能反而变低甚至空闲因为程序都在等内存页从磁盘换入换出。你会看到 vmstat 里 siswap in和 soswap out两列非零以及 free -h 显示可用内存告急。解决方向是确认内存带宽是否被正确分配应用是不是存在内存泄漏或者干脆扩容内存。注意观察问题不能盯着 CPU否则会发现 CPU 一直上不去却始终找不到瓶颈。3.4 磁盘/网络 IO 等待时间IO 等待不仅作用于数据库系统对普通业务也一样。比如文件服务、图片处理、视频转码服务很多时候瓶颈在磁盘带宽或网络传输延迟。CPU 在运算上花的时间很少大量时间花在等待数据从磁盘陀螺中转出来。这种场景用 iotop、iostat、sar -d、sar -n 来看重点看四个参数%util设备繁忙度、await平均 IO 等待时间、svctm服务时间、以及网络等待。%util 长期 100% 而 await 很高说明设备队列在排队svctm 很低但 await 很高可以推断是请求特别多系统在并发层面排队而不是单个请求慢。处理方向可能是调整 IO 调度算法、合并小 IO或者让应用走内存缓存。4. 一套可落地的排查流程监控告警响了不可能每次靠猜。我一般按下面四步走十分钟内就能把CPU 高放大到具体是哪个层面的问题。4.1 第一步用 top 快速定性关键词全局视角。登录服务器后的第一个动作一定是 top但要看的东西不是那个硕大的 %Cpu(s) 总和而是按顺序看这四件事按 1 看每个核的使用率确认是均匀占用还是单核热点。看 load average跟核数做对比判断任务队列是否堆积。看 %Cpu(s) 里的 us、sy、wa、st 比例大致定位计算型、系统调用型、IO 等待型、虚拟化抢占用型。按 Shift P 按 CPU 排序找到消耗最大的进程 PID。这一步不需要太精细目的就是确认这台机器上到底谁在吃 CPU以及它的吃法很奇怪还是合理。如果是 Web 服务、缓存服务这种高占用要关注如果是监控采集、日志采集、数据备份这类辅助进程那可能纯属虚惊把采集间隔调大或放到闲时任务里即可。4.2 第二步用 vmstat 和 sar 找瓶颈象限top 是快照vmstat 是趋势两者的组合特别重要。vmstat 一次采样说明不了什么但持续三分钟每秒钟采一次就能看出系统行为的整体走向。vmstat 1 5重点关注r运行队列长度、b阻塞进程数、waIO 等待时间、cs上下文切换。组合判断有几种常见象限r 高 wa 低 us 高典型 CPU 计算瓶颈考虑优化算法或扩容。r 低 wa 高IO 瓶颈CPU 在空等磁盘/网络。加大缓存、优化 IO 模式才有效。r 高 wa 高 b 高CPU 和磁盘可能同时成为瓶颈先看是不是内存换页引起。r 低 cs 很高存在大量线程切换或锁竞争需要往线程模型方向查。sar 则适合看历史数据。很多监控系统只保留 1 分钟粒度的 CPU 总和上线的告警没有提供足够的历史上下文。用 sar -u -r -q -d 回溯一下看问题是在某个时刻突然发生还是长期缓慢劣化。这决定了处理节奏突发性劣化优先看发布变更、流量突增缓慢劣化优先看内存泄漏、连接数堆积、文件句柄耗尽。4.3 第三步用 pidstat、perf 定位到具体线程定性之后要定量定量之后要定位代码。这个环节是纯运维和真排查的分水岭。对于线程级问题pidstat -p 1234 -t 1-t参数可以看到该进程下每个线程的用户态和内核态 CPU 占用。找到热点线程 TID 后再把它转换成十六进制用 jstackJava 场景或者 gdb、pstack 抓线程栈就能直接看到代码在哪个函数里。还有一种更现代的做法是用 perf 采样perf top -p 1234或者perf record一段时间再perf report能直接看到热点函数、热区调用链。这个对 C/C、Go、Rust 等场景非常好用对 JVM 可以说需要配合虚拟机内部符号表效果会打折扣但依然能看出幅度。耗时长的排查里我还经常用strace -c -p 1234统计系统调用的次数分布确认是不是某种系统调用占据了大量 CPU。注意生产环境一定要控制采样时长比如 3 到 5 秒就收别为了查到根因把线上进程拖得更慢。4.4 第四步对照业务曲线确认实际影响排查到最后一步也是最容易漏掉的一步把技术指标和业务指标对齐来看。机器 CPU 高了但线上 QPS每秒请求数、P99 延迟、错误率、活跃会话数都没变化那这个高到底影响了谁影响体现在哪些业务上有一个项目经历很典型某次压测发现订单服务的 CPU 已经 90%但压测人员反复确认 TPS每秒事务数并没有下跌延迟也没上升只是 CPU 一直满。后来才发现这台机器同时跑了定时报表任务定时任务利用了 CPU 的空闲时段但业务高峰期跟定时任务重叠了错开之后 CPU 就掉下来了。所以在处理任何告警之前我强烈建议先拉一条业务链路指标确认现象是否真实影响用户。监控告警的意义在于提前发现隐患不是为了骚扰值班同学。如果业务指标稳如泰山那处理动作就该是分析、观察、优化而不是盲目降级。5. 常见误判案例与避坑经验最后放几个我印象深刻的实战案例每条都是踩过的坑。5.1 案例一业务高峰期 CPU 80% 但体验正常有一次某核心服务在活动大促期间 CPU 常年 80% 以上监控每天告警。评估下来这台服务是四核实例80% 平均相当于 3.2 核在忙负载只有 2.5远没到压垮系统的程度。当时服务本身架构是异步事件驱动大量等待发生在事件循环内部CPU 使用率天然就不会低。大促期间真正的风险点是突增的流量能否被消化掉。我们做了压测验证把流量打高 50% 后CPU 变成 95%但 P99 延迟只从 8ms 涨到 11ms依然满足 SLO。结论非常明确这个服务不用扩容加机器只是浪费成本。但后来的经验告诉我这个结论要加一个前提——一定要关注历史趋势。如果 CPU 过去一个月一直在 60% 到 80% 波动说明这是稳态如果是持续两天从 40% 爬升到 80%那就是老化迹象很可能是代码缺陷累积或数据量变大导致计算复杂度上升这时候就不该坐视不管了。5.2 案例二CPU 30% 但服务超时严重这个坑挺经典的。某网关服务 CPU 使用率只有 30%负载也不高但客户端频繁超时。最初怀疑网络问题排查了半天才发现问题出在线程池配置上。应用配置了 200 个线程但大量请求阻塞在一个第三方依赖的调用上所有线程都被占满了CPU 反而空闲因为线程都在等待依赖系统的响应。这种时候 CPU 低完全不妨碍系统卡死。线程池全部被占请求排队长度暴增新请求根本得不到处理。排查方法是看线程池指标活跃线程数、队列长度、阻塞任务数而不是看 CPU。所以后来处理这类问题时我都会先看连接数、线程数、队列深度这些并发指标CPU 只作为次要参考。还有一种类似情况是数据库连接池耗尽。业务进程 CPU 不高但所有连接都在等待拿到数据库连接表现出来同样是超时。此时系统负载不高但连接池的活跃连接数已经到了最大值。用ss -s和监控里的连接池指标可以发现。5.3 案例三负载明明不高但总是卡一下有个服务负载并不高1 分钟 load 不到 28 核机器但它每隔 20 分钟左右就会出现一次秒级尖刺延迟。一度怀疑是定时任务在捣鬼代码排查后发现是某个全局缓存定期失效导致多个线程同时回源做冷计算瞬间把 CPU 拉高然后又瞬间空下来。这种卡一下的场景CPU 总使用率整体看很健康只有尖峰瞬间会飙上去。处理方法是把缓存的失效时间做散列不要让所有 key 在同一秒内同时过期。还有一个常见来源是 Java 的 GC 了碰上 CMS 或 G1 的大规模回收业务线程会被停顿也就是常说的 STW。这期间 CPU 整体不高但延迟会突变。看 GC 日志是最快的判断方式。这类型问题的高发点很多共同的排查思路是抓拍慢请求的时间点结合当时线程栈、GC 状态、定时任务日志找重叠关系。时间对齐了这个线索维度找根因就剩最后一公里了。6. 我在现场常用的几条判断口诀说了这么多最后总结几条我在实际现场用得最多的判断口诀纯粹是经验不成体系但非常高效。第一条CPU 高先看负载负载低说明没排队基本是虚惊负载高再去分 CPU、IO、锁。第二条us 高是业务逻辑看代码优化sy 高是系统调用频繁看内核行为和 IOwa 高是磁盘慢或 IO 队列深st 高先找云厂商拉扯资源。第三条进程的 %CPU 超过 100 不是异常说明它用了多个核。评估时先除以核数再说话。第四条平均负载不是 CPU 使用率D 状态进程也算负载。看到负载高先查 D 状态进程没准在等磁盘。第五条单核打满不代表系统卡但如果在热点路径上那就是隐藏的定时炸弹。第六条任何告警处理前先看一眼业务指标。业务没影响先不动手业务已经有影响不要纠结为什么 CPU 不高直接查线程和 IO 队列。我个人排查这类问题时最深的体会是监控告警只是提醒你有事发生不代表系统正在遭殃。一个成熟的工程师不会看到 CPU 高就紧张而是会冷静地确认——它是计算密集的正常体现还是资源争抢的预警信号。把 CPU 使用率、平均负载、IO 等待、线程切换这几个指标组合起来看再对一下业务表现十次里有九次都能当场定性。希望这套思路能让你下次面对CPU 飙高告警的时候多几分从容少几分盲目的扩容。