我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

Linux CPU指令排查与性能调优实战

Linux CPU指令排查与性能调优实战 开篇为什么全栈工程师必须搞定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/Oiowait等字段。/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 -uCPU使用率历史和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和syus大约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 --cpunodebind0则绑定NUMA节点。适用场景主要有三个一是延迟极其敏感的中间件比如某些数据库引擎希望避免上下文切换带来的抖动二是NUMA架构下让关键线程和数据本地化避免跨节点访问内存三是验证某段代码是否因为频繁迁移CPU核心导致cache miss严重。但我要泼一盆冷水对大部分业务应用不建议随便绑核。绑核会降低调度器的灵活性在多租户、混合负载的服务器上反而容易造成核心空转或者过载。我曾经为了优化把某服务的核心线程绑定到两个核上结果流量突发时这两个核很快被打满其他核心闲着服务照样被拖垮。绑核前一定评估好业务流量模型否则是画蛇添足。5. 监控体系的搭建思路与指令的自动化组合5.1 从手动指令到半自动巡检命令敲多了自然会想能不能自动化。我不建议一上来就上重型监控系统比如整套PrometheusGrafana对于中小团队先手工把指令组合成脚本加上定时任务已经能覆盖大部分场景。我自己的习惯是保留一个简单脚本每5分钟采集一次CPU核心指标写到本地日志#!/bin/bash # /etc/cron.d/cpu_monitor */5 * * * * root /usr/bin/mpstat -P ALL 1 3 /var/log/cpu_mpstat.log 21配合日志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涨到200cs数量翻了十倍说明线程模型有瓶颈。负载均值是否越过核数线这是容量规划最直观的参考线。压测结束后的性能报告里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这些概念一次讲透。如果你在这些内容里找到一点共鸣或者有自己的经验想补充欢迎交流。
返回列表