loadavg高但CPU低?D状态与PSI压力指标排查实战 凌晨 2 点 15 分监控平台弹出告警某业务实例的 1 分钟 loadavg 冲到 180而 5 分钟和 15 分钟平均值还不到 10。切到服务器上看一眼CPU 总体才 30% 出头磁盘 IO 也只有零星几个百分点。第一反应是监控抽风第二反应是——熟悉的味道这又是一次 loadavg 和 CPU 使用率“对不上”的经典现场。这篇文章是这次 loadavg 异常的完整排查记录也顺带整理成一套可以反复套用的排错框架。内容涉及 loadavg 的统计机制、D 状态任务的判别、PSI 压力指标的使用、几类高频异常的复盘以及我在实际操作中踩过的坑。无论你是刚接手服务器运维的新手还是需要应对生产环境告警的开发者这套思路都可以直接抄作业命令也是现成的。1. 先搞懂 loadavg 到底在统计什么1.1 三个数字是“任务数”不是 CPU 百分比先说一个很多人都绕晕过的点/proc/loadavg 里的三个数分别代表最近 1 分钟、5 分钟、15 分钟里处于“可运行”或“不可中断睡眠”状态的任务平均数量。它统计的是任务数量不是 CPU 占比量纲是“个”。只是因为一个 CPU 核同一时刻最多运行一个任务所以当 loadavg 数值等于 CPU 核数时可以近似理解成“每个核都在忙”。具体到内核实现统计的是两类任务的和TASK_RUNNING正在 CPU 上执行、或者排入运行队列等待调度的任务。TASK_UNINTERRUPTIBLE也就是常说的 D 状态不可中断睡眠的任务常见于等待磁盘 IO、等待网络文件系统响应、等待内核态锁。这类任务即使睡眠也不能被信号打断。所以一句话理解loadavg 上涨本质上是“运行队列上排队的任务变多了”或者“阻塞在核心资源上的任务变多了”。前者对应 CPU 过载后者对应 IO、锁或者设备故障。1.2 移动平均是怎么算出来的搞明白计算机制你就能解释为什么告警里三个数字经常“脱节”。内核不是实时算这三个数而是每隔 5 秒采样一次然后对采样结果做指数加权移动平均。简化公式就是新 load 旧 load × 衰减系数 当前活动任务数 ×1 - 衰减系数。衰减系数决定了历史数据对当前值的“拖拽”程度1 分钟平均衰减快、反应灵敏15 分钟平均衰减慢、表现平滑。所以当你看到 1 分钟值瞬间拉得很高、15 分钟值纹丝不动时说明这是一个刚刚发生的突发现象反过来如果三个数一起高说明系统已经持续高压了一段时间。这个机制在排查时非常有用。我习惯把三个数连起来读1 分钟和 5 分钟差距大说明是瞬时冲击5 分钟和 15 分钟都高说明问题已经潜伏很久要尽快扩大排查面不能只盯着刚发生的表象。1.3 为什么 D 状态任务会被计入 loadavg这是 Linux 和很多其他操作系统最大的差异点也是最容易踩坑的地方。Linux 之所以把不可中断睡眠任务也算进 loadavg是因为这类任务占用了宝贵的资源——比如磁盘队列里的 IO 请求、内核锁、文件系统事务——这些资源不释放后面的任务只能一直等所以账必须记在前面的任务头上。结果就是loadavg 高不一定是因为 CPU 跑了太多任务也可能是一块磁盘挂了、某个文件系统卡住、网络存储失联导致大量任务堵在不可中断的等待里。这类问题的典型特征就是 loadavg 很高CPU 却大量空闲。1.4 常见误区loadavg 高不等于 CPU 忙既然 loadavg 统计的是任务排队数量那么“loadavg 高就必须加 CPU 资源”这个惯性思维就是错的。遇到过不少同事看到 loadavg 超过核数就急着扩容结果问题根本不在 CPU 上扩容之后 D 状态任务原封不动负载照样高得离谱。这里有一个判断口诀可以背下来r 队列长、CPU 繁忙 → 计算型问题b 队列长、CPU 空闲 → IO/阻塞型问题两个都高 → 混合型或者次生灾害。后面的内容基本都围绕这个口诀展开。2. 异常表象速判拿到告警后先回答三个问题2.1 第一问r 和 b 的分布如何vmstat 输出的前两列是 rrunning正在运行或等待 CPU 的进程数和 bblocked不可中断睡眠的进程数。这两个数字的对比直接决定排查方向r 远大于 CPU 核数b 几乎为 0CPU 计算资源不足常见于突发计算任务、定时任务集中触发。b 明显大于 0甚至几十上百而 CPU 大多处于 idleIO、锁或者设备层阻塞。r 和 b 都高CPU 和 IO 同时出问题也可能任务已经被 IO 堵住但还在抢占 CPU 反复重试。这里建议多采样几次命令写清楚是 vmstat 1 10持续 10 秒。单次采样可能只是瞬时值我经历过一次典型误判单看一次 vmstat 发现 r200断定是 CPU 问题结果连续采样后发现 r 在持续下降、b 在上升实际是磁盘故障引发的连锁反应方向完全相反。2.2 第二问CPU、IO、内存三个维度谁在承压vmstat 里还有几列关键数字要一起看us sy 接近 100%以计算为主r 队列自然长。wa 持续偏高IO 阻塞为主b 列大概率也有值。si、so 不为 0内存回收和换页压力可能伴随 kswapd 内核线程导致负载波动。除了 vmstat强烈建议看 /proc/pressure 下的 PSI 指标。PSI 是内核提供的压力数据源能直接告诉你 CPU、内存、IO 分别对任务造成了多长时间的阻塞。比如 /proc/pressure/io 的 some 列表示至少有一个任务被 IO 阻塞的时间比例full 列表示所有任务都被 IO 阻塞的时间比例。当 full 长期不为 0 时IO 已经是系统性瓶颈这时候排查方向几乎可以拍板。PSI 比“loadavg 高 CPU 低”这种间接推断要直接得多后面我会专门讲监控怎么用。2.3 第三问谁是嫌疑进程阻塞在哪方向定下来就要锁定具体进程。最常用的一条命令ps -eo pid,ppid,stat,wchan:28,comm | awk $3 ~ /D/只列出 D 状态进程。wchan 这一列是进程当前阻塞在内核的哪个函数上它基本能告诉你“卡在哪类资源里”。举几个实际例子如果 wchan 大量出现在文件系统日志提交相关的函数上大概率是磁盘 IO 故障或者日志写盘卡住如果出现在网络文件系统的 RPC 等待函数上那就是远端存储挂起如果出现在内存回收相关路径上就得查内存水位和 page allocation。有了进程列表和 wchan接下来的动作就是顺着这条线索去查对应资源的状态。下面那节我把它整理成一套从告警到定位的完整流程。3. 一份可直接照做的排查流程3.1 现场信息采集先把证据固定下来不要在系统缓过来之后才回忆“当时发生了什么”。我的习惯是任何人接到 loadavg 告警第一时间把下面这几条命令的结果存成文件uptime cat /proc/loadavg vmstat 1 5 ps -eo pid,ppid,stat,wchan:28,comm --sort-pcpu | head -n 30如果内核支持再加抓三个压力指标cat /proc/pressure/cpu cat /proc/pressure/io cat /proc/pressure/memory这些加起来 30 秒内能拿到却能在事后复盘时完整还原现场。尤其是那些几分钟后自动恢复的“幽灵故障”没有现场快照后面讨论起来全是猜。这算是我做运维这么久最想强调的一个习惯告警处理第一步永远是固定证据而不是急着动刀。3.2 D 状态任务定位ps 的两条关键命令第一步永远先分清 r 和 b。如果 b 有值跑ps -eo pid,stat,wchan:28,comm | grep \D\然后重点观察这些 D 状态进程的数量是涨还是跌。数量持续上涨说明阻塞点在快速积压这是故障现场处置优先级要拉高数量上下波动可能是周期性的短时抖动可以先观察。数量趋势比数量本身更能反映问题的紧迫程度。如果 r 高而 b 几乎没有跑top -H -b -n 1 | head -n 50注意按线程视角看而不是只看进程。多线程应用的负载往往藏在某个线程池里进程维度的 CPU 使用率会把问题抹平必须切到线程维度才能看到谁在真正消耗 CPU。3.3 顺着 wchan 追根因一条典型的追查链wchan 是线索但怎么从线索走到根因这个环节比较考验经验。我拆解一条我在实践中走通过的链路现象loadavg 从 5 涨到 60CPU 使用率只有 20%15 分钟平均值同步上涨。vmstat 显示 b40wa 达到 60% 以上判断 IO 阻塞。ps 发现十几个进程处于 D 状态wchan 指向块设备相关的等待函数。iostat -x 1 查看磁盘发现某块盘的 %util 到 99% 以上。这里不能马上断定磁盘坏了——util 高可能是因为请求大量堆积也可能是磁盘本身响应延迟高。继续查 /proc/diskstats比较 await 和 avgqu-sz。当 await 从零点几毫秒涨到几百毫秒avgqu-sz 涨到几十基本可以断定磁盘服务能力出了问题。再把范围缩小到访问这块盘的进程跟业务侧确认是否有批量扫描、备份任务同时触发。排除业务大动作后就需要考虑硬件层面的故障联系设备侧介入。这个链条的核心思路是“顺着 wchan 找资源顺着资源找进程顺着进程找业务动作”。每一步都有对应的命令和证据支撑不做没有根据的猜测。3.4 内核与系统日志里找佐证很多 D 状态阻塞并不是永久性的而是“卡到超时才报错”。内核的 hung task 检测机制会在任务进入不可中断睡眠超过 120 秒默认阈值时把这个任务打印到内核日志里内容包括任务名称、阻塞的内核栈以及当时的任务列表快照。dmesg -T | grep -i hung task如果看到类似 “blocked for more than 120 seconds” 的条目它后面跟着的内核栈非常关键基本把根因写在了脸上。我遇到过的案例里这条日志一出来排查方向就锁定到对应的驱动层或者文件系统层了省去大量试错。另外如果系统日志里夹杂着文件系统或设备层的报错比如 IO 错误、请求超时重试之类的记录优先级最高的动作是先确认物理设备状态因为设备几乎已经不可用用户态做什么尝试意义都不大。3.5 处置策略与恢复验证处置要分场景不是所有 D 状态都值得立刻动手短时抖动比如批量任务导致的 IO 排队业务容忍度允许的情况下可以先观察同时拉长监控粒度。IO 设备故障优先切路径、摘流量而不是反复重试。切忌对 D 状态进程反复发 kill后面我会解释为什么完全没用。问题可复现尽量先保留现场把快照完整抓下来再处置为的是事后能复盘。恢复验证不能只看 loadavg 降下来。正确姿势是连续观察 15 分钟以上的三个 loadavg 数值同时确认 b 回落到基线水平、PSI 的 full 值归零。因为 1 分钟平均值降得快15 分钟平均值滞后明显单看一两个采样点很容易误判“已经好了”。4. 四类典型 loadavg 异常复盘4.1 磁盘 IO 卡死引发的 D 状态堆积这是最常见的一类。某台数据库实例某天下午 loadavg 突然从 8 冲到 120CPU 不到 20%业务接口全面超时。现场采集看到 b60iostat 显示数据盘 %util 满、await 超过 500msdmesg 里有设备层的超时重试日志。处理结果是磁盘控制器在反复重置属于硬件层故障。后续操作分三块切换存储路径、把业务流量摘掉、等设备完成切换后再恢复。这次复盘里最有价值的经验是“不要因为 loadavg 高就重启应用”。重启只是杀掉了表象IO 问题不解决重启之后同样的 D 状态会立刻重新堆积而且重启间隙可能导致数据不一致。4.2 短任务风暴把 loadavg 冲高另一种看起来更“诡异”的情况loadavg 瞬间飙高但 top 里根本看不到 CPU 占用高的长任务。这是短生命周期任务的典型特征——大量任务每隔几十毫秒创建、运行、退出它们在 top 的采样窗口里来来往往你根本看不全。排查这类问题需要看调度器统计pidstat -w 1pidstat 的上下文切换统计会显示每秒 cswch/s 和 nvcswch/s数值异常大说明系统里有高频创建和切换任务的进程。再结合业务侧的任务调度频次比如定时任务集中触发、消息积压导致批量消费协程爆发就能锁定是哪个业务模块了。这类问题的解法不在内核参数而在业务侧削峰、限流、把瞬时任务分散到更长的时间窗口。调内核参数去硬扛短任务风暴往往事倍功半这是我试过之后才明白的。4.3 网络文件系统挂起导致的“假高负载”曾经有位同事排查一个 loadavg 高但本地磁盘完全正常的“玄案”。后来发现根源是一个网络文件系统挂载点——远端存储网络抖动导致一堆进程在等待 RPC 响应全部进入 D 状态本地磁盘毫无压力。处理方式是调整挂载参数比如把超时和重试配置改成符合业务特性的数值并增加挂载超时保护。但这里要强调一个边界能调超时不等于建议调成无脑自动恢复。对于有严格一致性要求的场景宁可让它阻塞也不能静默返回错误配置选择必须跟业务形态匹配否则数据出错比负载告警严重得多。4.4 内存回收抖动引发的系统性波动最后一个类型容易被忽略内存压力间接推高 loadavg。当内存不足时内核会启动内存回收kswapd 和直接回收路径都会消耗 CPU同时回收过程等待页面写回也可能引入 D 状态任务结果是 loadavg、CPU、IO 三个指标同时波动。判断方法是查内存水位和回收指标cat /proc/vmstat | grep -E pgsteal|pgscan|kswapd_steal free -g如果页回收速率和 swap 使用量同步上升说明这是一场由内存水位引发的次生灾害。解法重点是给应用配置合理的内存上限而不是盲目加大内存——很多时候问题本质是应用缓存无界增长加多少内存都不够。5. 常用工具与参数速查5.1 排错命令速查表目的命令关键关注点快速总览vmstat 1 5r、b、wa、si/so看 D 状态进程ps -eo pid,stat,wchan,commwchan 列定位阻塞函数看 CPU 线程占用top -H -b -n 1线程维度而非进程维度看磁盘延迟iostat -x 1await、avgqu-sz、%util看上下文切换pidstat -w 1cswch/s、nvcswch/s 异常大看内核栈dmesg -T | grep -i hung task120 秒超时任务的调用栈看压力指标cat /proc/pressure/iosome、full 两列看内存回收cat /proc/vmstatpgsteal、pgscan、kswapd_steal这张表基本覆盖了从“收到告警”到“定位根因”的全过程我处理过的绝大多数 loadavg 案件都靠这些命令收尾。5.2 为什么 D 状态进程 kill 不掉这是排查时最常被问的问题。D 状态进程处在不可中断的内核等待路径上信号处理本身也被阻塞你 kill 的信号确实发出去了但进程根本没有机会去处理自然不会退出。所以不要反复 kill -9 一个 D 状态进程这是纯属浪费时间的操作。正确的思路是解决让进程阻塞的底层资源设备恢复、文件系统恢复、网络链路恢复之后进程会自己醒来并继续执行。如果实在等不到且业务无法恢复最后的手段往往不是杀进程而是重启系统但在重启前一定要权衡数据一致性和业务影响面。5.3 监控告警的正确打开方式给 loadavg 设一个固定阈值比如“超过核数就告警”是很初级的玩法误报率和漏报率都高。建议的做法分几层基线对比按实例维度保存历史 loadavg 分布异常判定不用“超过某个数”而是“明显偏离同时段的正常基线”。多维联动把 loadavg 和 CPU、IO、内存压力数据做关联告警时直接标注属于计算型还是阻塞型值班人不用从零开始分析。引入 PSI把 /proc/pressure 里的 some 和 full 纳入监控面板。这样告警能更早触发而且根因方向的判断在监控端就自动完成了一半。我在实际使用中发现PSI 对“loadavg 高但 CPU 低”这类告警的降噪效果最明显很多以前需要人工判断的场景看 PSI 面板一眼就能定性。5.4 可调整的内核参数边界不是所有场景都建议动内核参数。以下几项是在实践中确实有效果的kernel.hung_task_timeout_secs调整任务超时打印的阈值可以在故障早期获得更及时的日志输出。vm.swappiness影响内存回收倾向间接缓解内存压力类的负载波动。块设备队列深度参数需要根据设备类型和业务 IO 模型谨慎调整盲目调大可能让延迟恶化。但一定得明白参数只是缓解症状的表现形式解决不了业务模型和硬件层面的根因。把内核参数当成故障根源的解法是我见过最多绕弯路的操作没有之一。说句实在话loadavg 这个指标被讨论了很多年但真正让人头疼的从来不是它的算法而是它背后五花八门的现实原因。我个人的体会是把它当成一个“信号灯”而不是“温度计”——它只能告诉你系统出问题了不能告诉你问题是什么。排查那种 CPU 空转但 loadavg 爆炸的问题最幸运的时刻不是恢复业务的那一刻而是凭着一份现场快照在复盘中一步步还原出真相的时刻。所以每次接到告警先稳住手里备好命令按框架一步步来大多数案子的走向都会变得清晰。最后再分享一个小习惯在监控平台上把 /proc/loadavg 和 /proc/pressure 的数据一起存够 90 天等真要追根因的时候这些历史数据往往比告警文本有价值得多。