先讲个我自己的经历。凌晨两点四十,手机告警响得跟fire drill似的,某台线上虚拟机load average直接飙到38。登录上去一看,CPU使用率不到15%,内存余量充足,top里根本看不到哪个进程在疯狂吃CPU。这种“负载高但CPU很闲”的诡异场面,很多运维同行第一次遇到都会发懵。我后来花了两个多小时才定位到真正的元凶——一块云硬盘的I/O被打到接近上限,一堆D状态进程卡在不可中断睡眠里排队。这趟折腾之后,我彻底摸清了系统负载这套指标的脾气,也把常见的负载高场景整理成了一套排查流程。这篇就把这些经验完整写出来,从原理到实操再到踩坑,希望能帮你少走点弯路。
1. 先搞清楚负载到底是什么
排查任何问题之前,先把概念吃透。不然连问题本身都描述不清楚,后面全是在瞎试。
1.1 load average的三个数字怎么看
运行uptime、top,或者查看/proc/loadavg,都会看到类似这样一行:
load average: 1.82, 1.14, 0.68三个数分别是1分钟、5分钟、15分钟的平均负载。很多人只看第一个数,这是不对的。真正有价值的组合是看三个数的趋势关系:
- 三个数都高,说明系统已经持续高负载一段时间了,不是瞬时抖动。
- 1分钟远高于15分钟,说明负载是刚刚拉起来的,正处于飙升阶段。
- 1分钟低于15分钟,说明负载正在下降,最坏的阶段可能已经过去。
这是最基础的读法。但load average这个数字本身,其实包含了两类完全不同的东西,这是最容易误解的地方。
1.2 负载高不代表CPU繁忙
Linux内核里,负载统计的是处于TASK_RUNNING和TASK_UNINTERRUPTIBLE两种状态的进程数之和。TASK_RUNNING比较好理解,就是正在CPU上跑的、或者排着队等CPU的进程。但TASK_UNINTERRUPTIBLE是很多人忽略的,它指的是处在不可中断睡眠状态的进程,这种进程通常是在等I/O——比如从磁盘读数据、网络请求阻塞,并且这个等待不能被打断。
这就是为什么你可能会看到这样的场景:load average已经飙到20了,但top里CPU那栏全部加起来才10%出头。因为有一大堆进程卡在I/O等待上,根本没在消耗CPU,但它们依然被计入了负载。
打个比方,把操作系统想象成一个餐厅后厨。CPU是灶台,负载是后厨里人数——不仅包括正在炒菜的厨师,还包括在边上等配菜、等洗碗机的人。如果洗碗机堵了,一群厨师蹲在边上干等着,后厨看起来人满为患(负载高),但灶台其实是闲置的(CPU低)。这时候你拼命加灶台没用,得去修洗碗机。
1.3 要结合CPU核数来判断高低
判断负载高不高,必须知道机器有多少个CPU核。一般经验是:
- 负载长期低于核数,系统资源有富余,处于健康状态。
- 负载约等于核数,资源利用基本饱和,但还勉强稳定。
- 负载持续高于核数,任务排队严重,响应时间会明显劣化。
比如一台2核的机器,负载1.8看起来不高,但已经接近饱和了;一台16核的机器,负载3.0看起来数字很大,实际上还有大量CPU空闲,问题未必严重。拿到负载数字之后第一步,永远先去查核数。
nproc或者:
grep -c processor /proc/cpuinfo2. 排查工具组合拳
光会看数字没用,得有一套完整的工具链和排查顺序。我习惯的套路是:先看概况,再锁进程,最后查底层资源。每一步都用不同的工具,各司其职。
2.1 第一板斧:uptime和top看全局
登录服务器,先敲uptime,确认当前负载以及1分钟、5分钟、15分钟的趋势。然后别急着看具体进程,先按经验判断:CPU型还是I/O型?
接着用top,注意几个关键细节:
- 按1展开每个CPU核的使用情况,看看是单核打满还是均匀分布。
- 看us(用户态)、sy(内核态)、wa(I/O等待)、hi(硬中断)、si(软中断)这几项的占比。wa高基本就是I/O瓶颈,si高则可能网卡软中断有问题。
- 看进程列表,如果某个进程CPU那列明显偏高,基本可以锁定嫌疑。
top默认按CPU使用率排序,这个场景下够用。但如果怀疑I/O瓶颈,建议按CPU排序后按x高亮当前排序列,再按shift加v切换森林视图,能看到进程树关系。
2.2 第二板斧:vmstat定位CPU还是I/O
top看的是快照,vmstat看的是持续变化。连续采样几次:
vmstat 1 5每行输出里重点看三列:
- r:运行队列长度,也就是正在跑和等着跑的进程数。这个值长期高于核数,CPU肯定吃紧。
- b:阻塞进程数,通常代表卡在不可中断I/O的进程数量。这个值持续大于1甚至更高,I/O问题八九不离十。
- wa:CPU花在等待I/O上的时间占比。wa高,配合b列高,基本能坐实I/O瓶颈。
vmstat还有一个好处,能看到si和so。这两个是swap的换入换出量,如果si/so持续有数值,说明内存可能不够,系统正在把进程往swap里赶,这也会引发负载飙升。
2.3 第三板斧:iostat和mpstat做细分
确认I/O可能是瓶颈之后,上iostat看具体是哪块盘在扛压:
iostat -x 1 5重点看每个设备的util、await、svctm这几列。util接近100%,说明这块盘已经满负荷运转;await接近或者超过几十毫秒,说明每次I/O请求都在排队,延迟很高。
CPU方面用mpstat可以看得更细:
mpstat -P ALL 1 5-P ALL是把每个CPU核单独列出来,方便看是不是某个核被软中断或者单线程进程打爆,而其他核空闲。
2.4 补充工具:pidstat定位具体进程
很多时候光知道是I/O瓶颈不够,还得找出到底是哪个进程在疯狂读写磁盘。这时候用pidstat:
pidstat -d 1这个命令按进程维度展示磁盘读写速率。如果某个进程的kB_rd/s或者kB_wr/s高得离谱,直接从进程反查它访问了哪些文件。更直观的方案是:
pidstat -d 1 -p PID单盯一个进程。要是进程还会动态变化,需要留意排查窗口内进程是否已经被重启,必要时结合审计日志、业务日志一起看。
3. 真实案例完整复盘
理论讲完了,说一次我实际处理的案例。这个案例非常典型:负载高,但CPU和内存看起来都不紧张,极具迷惑性。
3.1 告警出现时的第一手现场
那天凌晨接到某业务集群一台机器负载持续15分钟超过10的告警。机器配置是8核16G,负载长期基线在1左右。第一反应是出了什么问题,登录后先抓现场:
uptime输出:
02:41:55 up 87 days, 3:12, 1 user, load average: 11.02, 8.15, 4.361分钟负载11,5分钟8,15分钟4,典型的正在爬坡的形态。再过几分钟不干预,15分钟负载也会被拉上去。
接着跑top,一看进程列表,CPU占用最高的才17%,整体CPU加总不到20%。us不高、sy不高、wa也不高,这就不对劲了。wa不高说明磁盘可能还没被榨干,但也不能完全排除I/O问题,因为wa有时候会被某些调度和驱动因素掩盖。
3.2 逐层排查,从进程追到底层资源
先用vmstat看运行队列和阻塞进程:
vmstat 1 5输出大致:
r b wa us sy 1 23 4 8 2 2 25 5 7 3 3 24 3 9 3b列非常吓人,24左右,说明有大量进程卡在不可中断睡眠。r列只有1-3,说明CPU这边毫无压力。内存swap的si/so接近0,说明内存层面没有明显问题。先把内存和CPU排除了,剩下的焦点就在磁盘I/O。
然后用iostat确认:
iostat -x 1 5输出里一块数据盘util达到99.8%,await显示220ms。正常SSD的await应该在几毫秒到十几毫秒,220ms说明I/O请求排队排到天荒地老。同时另一块系统盘util只有3%,说明问题定位在这块数据盘上。
到这里还不够,还得找出到底是谁在这块盘上疯狂读写。执行pidstat -d,结果发现一个Java进程的kB_wr/s持续在5万到8万之间。反查它的工作目录和日志,定位到它正在向一个临时目录疯狂刷日志,并且在做批量写操作。
3.3 收敛元凶,处理与恢复
找到方向后,进一步排查为什么这个Java进程刷盘这么严重。看日志发现业务侧一个批量任务跑了异常数据,导致处理逻辑进入了死循环式的重试,每次重试都产生大量日志和临时文件。玩命写盘,然后日志组件又在密集刷盘,最后把磁盘I/O彻底压死,一堆进程排队等I/O,负载就爆了。
处理动作分几步:
- 先停掉那个Java进程的异常任务,通过管理平台终止批量任务,让写盘压力先降下来。
- 清理临时目录里已经堆积的大量临时文件。
- 观察负载趋势,确认是否回到基线。
从开始干预到负载回落到2以下,大概花了十分钟。后续跟踪半小时,没有再反弹。
3.4 案例复盘总结
这个案例最典型的地方在于:所有症状都指向I/O阻塞,但最开始时wa并不高。因为当I/O队列已经堵到一定程度时,新来的进程不进入CPU的I/O等待状态,而是直接在进程调度队列里排队,导致wa低、b高这种组合。如果只看wa会误判。所以我后来给自己定了一条规矩:负载高的排查,永远先看vmstat的b列,再做判断。
另外还有一个细节,负载飙到11的时候,系统还没有表现出特别卡顿,因为8核机器即便有20个进程在阻塞,CPU资源剩余还很多,表面功能不受影响。这也是I/O型负载高比CPU型负载高更阴险的地方,用户有时候感知不到异常,但服务质量已经在悄悄劣化。
4. 常见负载高场景与应对速查
我整理了四类最常见的负载高场景,每一类的表现、定位思路和应对方法都不一样,放在一起对比着看会更清晰。
4.1 CPU密集型:运行队列排队,进程争抢计算资源
最直观、最容易被理解的一类。典型表现是top里us或sy很高,vmstat里r列接近或超过核数,负载高、CPU使用率也高。常见原因包括:业务流量暴涨、某段代码出现死循环、定期任务撞在同一个时间窗口、挖矿木马等恶意程序。
定位方式是top排序找高CPU进程,必要时按线程维度看,Java应用可能还要结合jstack抓线程栈。处理上一般就是优化代码、限流、扩容,或者紧急情况下直接杀掉制造压力的进程先止损。
需要注意一种特殊情况:sy高。这代表CPU大量消耗在内核态,要看一下是不是上下文切换过于频繁。配合命令:
vmstat 1 3看cs列,如果每秒几十万的上下文切换,多半是线程数或者进程数太多了,也不排除某个组件在疯狂fork。这时候即使us不高,负载也会很高。
4.2 I/O阻塞型:进程等磁盘,等待队列堆积
之前案例就是这一类。典型表现是vmstat的b列高,负载高但CPU空闲,iostat能看到某块磁盘util高、await长。常见原因包括:日志刷盘过猛、数据库全表扫描或大量脏页刷盘、磁盘本身故障降级、文件系统老化和碎片化。
定位时pidstat -d找具体高IO进程,然后lsof或者ls -l /proc/PID/fd看它在访问什么文件。处理办法:如果是日志问题就降日志量、改异步写;如果是数据库问题就优化SQL或者扩大缓冲池;如果是磁盘故障就换盘。
再补充一个容易被忽视的:当磁盘出现坏道或到寿命末期,I/O延迟会明显波动,但util不一定接近100%,await却很高。这时候iostat的%util失去参考价值,要重点看await的变化趋势。
4.3 内存不足引发swap抖动
内存长期偏高,系统开始使用swap,swappiness配置又比较激进的时候,很容易出现这种场景。典型表现:si/so有持续数值,负载高,CPU使用率不算特别高但系统整体响应慢。常见原因:应用内存泄漏、JVM堆配得太大、并发太高导致内存膨胀。
定位方式:free -h看内存和swap占用,top里按内存排序找吃内存大户,ps aux --sort=-%mem排序看进程。处理办法:重启吃内存的进程,调低堆内存配置,加内存,或者把swappiness调低:
sysctl vm.swappiness=10但这只是缓解,根本解法还是找到内存泄漏点。swap抖动的危害比普通负载高更大,因为进程从swap换入换出是有极高延迟的,I/O和CPU的压力会同时叠加。
4.4 反直觉场景:CPU空闲、I/O正常,但负载依旧高
还有一类更少见的情况:CPU和磁盘看起来都正常,vmstat的r和b都不高,但负载就是维持在比较高的位置。这种时候要往内核态深挖,看是不是软中断(softirq)、锁竞争、或者某个内核线程在搞事。
排查手段:
mpstat -P ALL 1 5 cat /proc/softirqs cat /proc/sched_debug如果si(软中断)占比高,可能网卡流量异常或CPU亲和性设置不当。如果sched_debug里能看到大量调度延迟,可能就是某些实时线程或者高优先级任务在抢占调度资源。还有一种可能是僵尸进程积累过多,虽然不占CPU,但会影响调度器的统计口径,让负载虚高。查一下:
ps aux | awk '$8 ~ /Z/ {print}'有大量Z状态进程就去处理父进程,一般要从根源上修父进程不回收子进程的bug,临时措施是杀掉父进程让init收养清理。
5. 建立负载基线,搭建日常监控
处理完一次紧急故障之后,真正的考验是:你以后能不能在问题发生前就发现苗头。负载高这个问题,如果只是被动等告警再介入,每次都搞得像救火。更合理的思路是建立机器负载的基线数据,设定合理的告警阈值,把“追着负载跑”变成“看着趋势走”。
5.1 基线怎么打
新机器上线后不要急着设告警,先裸奔观察一两个星期。用crontab定时或监控系统按5分钟粒度采集load、CPU、内存、磁盘I/O等指标,存下来作为基线。常用crontab采集:
*/5 * * * * echo "$(date '+%F %T') $(cat /proc/loadavg)" >> /var/log/monitor/load_history.log两周之后看数据分布,算一下负载的平均值和P95值。比如90%的时间负载都在1.5以下,那2.5就可以作为一个相对合理的告警起点。P95这个数很关键,比平均值更能反映压力波动。
5.2 告警阈值怎么配
阈值不要只设一个“负载大于多少”的硬指标,要结合趋势和持续时间一起看。我给团队的告警配置逻辑是这样的:
- 单次负载超过核数2倍,持续5分钟,触发P2告警。
- 负载超过核数2倍,持续15分钟,升级为P1。
- 负载从低到高突变,比如5分钟内翻倍,不管绝对值超不超,都触发事件提醒。
为什么要看突变?因为有些机器平时负载极低,两个核的负载常年0.1,某天突然飙到1.5。1.5绝对值不高,但对这台机器来说是15倍的增幅,背后一定发生了什么。绝对值会骗人,趋势不会。
告警渠道上,主流监控系统都可以配置。模板可以这样设计:
主机:web-prod-03 指标:load1 = 6.8(基线P95 = 1.5) 持续时间:10分钟 当前CPU使用率:12% 疑似方向:I/O等待或进程阻塞,建议先看vmstat b列
告警内容里附带初步判断方向和排查建议,能让值班的人省下大量时间。
5.3 监控数据留存与故障复盘
监控数据至少要保留30天以上,给故障复盘提供依据。很多时候故障不是当下发生的,而是几天前某个改动的延迟爆发。没有历史数据,复盘全靠拍脑袋。
我建议每次负载故障处理完毕之后,都顺手导出一份当时的监控图表,附在工单里,标注出负载攀升的起点、峰值、干预时间和恢复时间。这些素材积累多了以后还能沉淀成一台“负载异常识别器”——看到某个负载波形,你就能判断大概是哪一类问题。比如陡升陡降、水平方向持续高位、锯齿形波动,对应的问题方向完全不同。
这些都是一个运维老手长期积攒的经验,写出来供大家参考,希望能给大家提供一个排查负载问题的基本盘。最后再额外分享一个避坑技巧:排查负载高时,一定要看系统日志,比如dmesg和/var/log/messages,是否有OOM记录、硬件错误、文件系统只读等提示。这些底层错误往往是表面负载异常的“根因”,不看日志只看指标,容易被误导。