某次值班监控面板上一条曲线突然抬头一台跑着 CentOS 7 的业务机器CPU 从 20% 一路顶到 100%几分钟后 SSH 敲不回显ping 还在但业务全断最终整机 hang 死只能硬重启。这种从CPU 飙高滑向系统卡死的过程几乎是 Linux 运维里最折磨人的一类故障——它不像进程崩溃那样给你一个明确的退出码而是把所有线索揉在一起等你登录上去的时候现场已经被自己踩烂了。我前后处理过几次类似的案例也踩过不少坑这篇就把整个过程完整拆开从告警响起到定位过程从用户态到内核态的信号判读从 hang 死之后怎么抢救到事后怎么加固。不管你是刚接手服务器的新手还是已经能看懂 top 的老手都能从里面找到可直接抄作业的命令和判断逻辑。1. 先复盘现象CPU 飙高到 hang 死中间到底发生了什么很多人对这类故障的第一反应是CPU 太高把机器烧死了其实 CPU 跑满本身不会让系统失去响应。一台 8 核机器哪怕所有核心都 100%只要内核还能调度到 sshd你就登得进去。真正让系统 hang 死的是 CPU 被某种不可中断的东西绑住或者内核态拿不到资源导致连调度器都没法正常工作。把这条链路想清楚排查方向就完全不同了。1.1 故障曲线上的三个阶段我把这次故障的监控曲线分成三段来看事后回看每一段的特征都很清晰。第一阶段是爬坡期大概持续十几分钟。CPU 使用率从常态的 20% 缓慢上升到 60%70%同时 load average 的 1 分钟值开始明显高于 15 分钟值。这个阶段业务还正常接口响应时间略微上升但没有任何报错。这个阶段是最值钱的窗口期因为此时系统完全可控你能从容地抓数据。第二阶段是平台期CPU 顶到 100%但 load average 还在两位数。这个阶段 SSH 还能连上只是敲命令有明显的延迟top 刷新一次要等好几秒。这时候系统已经在做大量的上下文切换和内存回收用户态进程还在跑但每个进程分到的时间片都在缩水。第三阶段是崩塌期load average 冲到几百甚至上千SSH 连接超时新连接建立不了已有的会话敲回车没反应但 ping 还能通。这就是典型的 hang 死——网络栈还活着但应用层和交互层已经彻底卡住。很多人误以为这是网络问题其实 ping 能通恰恰说明网络没问题问题出在更下层。这三个阶段的分界点主要看 load average 的增长速度和 SSH 是否还能响应。一旦发现 load 快速增长且命令开始卡顿就要立刻停止一切重操作转入取证模式。1.2 为什么CPU 高和系统卡死不是一回事这里得把一个概念掰开CPU 使用率高只说明 CPU 在执行指令系统卡死说明没有进程能及时拿到 CPU 或者拿不到它等待的资源。这两件事之间隔着一个关键变量——内核态的行为。我见过两种完全不同的高 CPU。一种是用户态死循环top里%us很高%sy很低这种机器你照样能登进去kill -9一下就解决了属于最省心的一类。另一种是%sy很高或者%wa很高或者 load 高但 CPU 不高这类才是真正危险的因为内核在忙一些你没法直接干预的事情内存回收、文件系统日志提交、锁竞争、中断风暴。本次故障属于后者。事后从监控快照看%sy从 10% 左右涨到了 40% 以上%wa也有明显抬升load average 更是远远超过 CPU 核数。这三个信号同时出现基本可以判定不是简单的用户态问题。举个例子帮助理解把系统想象成一个快递站CPU 是分拣员进程是包裹。用户态死循环相当于一个包裹被反复来回搬分拣员很忙但站里秩序正常。而内核态卡死相当于仓库门被堵住了分拣员想去取货却出不去最后所有分拣员都堵在门口站里看起来人手全满实际一件货都发不出去。1.3 我给自己定的三条排查铁律处理过几次之后我给自己定了三条规矩这里直接分享出来。第一条先取证再动手。大部分人一看到 CPU 高手比脑子快直接kill -9或者重启。结果呢现场没了根因找不到下次还会复发。我的做法是先花 30 秒把top、ps、vmstat、dmesg的尾部各抓一份存下来再考虑处置。第二条能用轻量命令就别用重量命令。系统已经半死不活的时候你再去跑strace -f、lsof全量扫描、find /等于给快淹死的人再压一块石头。优先用读/proc的方式几乎没有开销。第三条不要相信单一指标。只看 CPU 会误判只看 load 也会误判。CPU、load、内存、IO、上下文切换、软中断这几个指标要横向对比着看才能拼出完整画面。后面几个章节我会具体讲怎么对照。2. 第一现场CPU 飙高时该抓哪些数据故障处理里最贵的不是解决问题是现场。系统 hang 死并重启之后你能拿到的只有重启前的历史监控曲线进程级的细节全部归零。所以在 CPU 刚开始飙高的那几分钟里抓什么、怎么抓直接决定了你能不能找到根因。2.1 top 里真正要盯的几列很多人打开 top 就盯着第一行的 load average 和进程列表里的%CPU这两项恰恰是最容易误导人的。我一般会按这个顺序看先看头部汇总行的%Cpu(s)拆分。这一行给出的us用户态、sy内核态、ni低优先级用户态、waIO 等待、hi硬中断、si软中断、st被虚拟化抢占七个数是判断问题性质的第一手材料。us高、sy低用户态代码问题死循环、正则回溯、加密运算最好查。sy高内核在忙可能是系统调用过于频繁、锁竞争、内存回收、文件系统操作。wa高CPU 在等磁盘sy通常也会跟着高因为进程在 D 状态被切换。si高网络软中断常见于小包风暴、连接数暴涨、网卡多队列没配好。st高这台机器是虚拟机宿主机 CPU 不够分了跟你自己的系统没关系。再按1展开每个核心看是所有核均匀高还是个别核高。均匀高通常是多线程应用或内存回收这类全局行为单核高往往是某个单线程进程或者某个中断绑死在一个核上配合/proc/interrupts就能定位。然后按Shift H切到线程视图。这一步非常关键。一个 Java 进程在 top 里只占一行但它内部可能是几十个线程在抢锁切换到线程视图之后你会看到某一个线程吃掉了 300% 的 CPU而其他线程在睡觉这就把问题从进程级缩到了线程级。用 top 的时候记住几个快捷键P按 CPU 排序M按内存排序1展开核心H切线程c显示完整命令行b高亮当前排序的列。这些组合起来比装一堆花哨的监控工具还管用。2.2 从进程到线程把飙高的范围缩到最小top只是入口真正定位要靠ps和/proc。我常用的第一组命令是这个ps -eo pid,ppid,stat,wchan:32,pcpu,pmem,comm --sort-pcpu | head -30这条命令的价值在于stat和wchan两列。stat里的状态码R是运行中S是可中断睡眠D是不可中断睡眠Z是僵尸。如果 CPU 排名前列的进程里出现了大量D那问题基本不在 CPU 本身而在 IO 或者内核锁。wchan会告诉你进程当前卡在哪个内核函数里比如jbd2_log_wait_commit说明在等 ext4 的日志提交xfs_buf_lock说明在等文件系统的缓冲区锁。对于多线程应用接着用-L看线程ps -eLo pid,tid,stat,wchan:32,pcpu,comm --sort-pcpu | head -40拿到具体的线程 idtid后可以去/proc/pid/task/tid/stack读它的内核调用栈需要 root 权限。这个文件是排查 D 状态问题的神器它能直接告诉你线程卡在哪个函数调用链上比猜快一百倍。还有一个很有用但经常被忽略的文件/proc/pid/status。里面的voluntary_ctxt_switches和nonvoluntary_ctxt_switches两行能看出进程是被动让出 CPU 还是被内核强制抢占。如果nonvoluntary_ctxt_switches数值飞速增长说明进程在抢 CPU 但抢不到通常是 CPU 真的不够用或者有更高优先级的任务在压着它。2.3 必须现场保存的几样东西我整理了一份现场取证清单每次遇到这类故障按这个表逐个抓一遍全程不超过一分钟比事后调监控强得多。采集项命令用途说明系统整体快照top -b -n1 /tmp/top.1保留 CPU 拆分、load、进程排名进程状态ps -eo pid,ppid,stat,wchan:32,pcpu,pmem,comm --sort-pcpu /tmp/ps.txt找 D 状态进程和内核等待点内存与 swapvmstat 1 5 /tmp/vmstat.txt看 si/so、bi/bo、cs 是否异常内核日志尾部dmesg -T | tail -200 /tmp/dmesg.txtOOM、hung task、磁盘错误的唯一来源系统日志journalctl -k --since 30 min ago /tmp/journal.txt补全 dmesg 被 ring buffer 冲掉的部分中断分布cat /proc/interrupts /tmp/irq.txt判断是否单核中断风暴软中断统计cat /proc/softirqs /tmp/softirq.txt网络包处理是否失衡内存明细cat /proc/meminfo /tmp/meminfo.txtDirty、Writeback、Slab 是否异常磁盘 IOiostat -x 1 3 /tmp/iostat.txt%util、await、队列深度这张表里我最看重的是dmesg。很多人排查故障时根本不看内核日志而 Linux 的很多关键事件——OOM killer 触发、hung task 超时、文件系统报错、网卡丢包——全部只在这里留痕而且是 ring buffer重启就没了。这一次故障的根因线索恰恰就藏在dmesg里那条blocked for more than 120 seconds的记录上后面会详细说。dmesg的输出有容量上限默认缓冲区不大高频日志场景下很容易被冲掉。建议在/etc/rsyslog.conf里把kern.*单独落一份文件或者直接把kernel.printk调低减少刷屏。3. 从用户态走到内核态hang 死前的几个典型信号CPU 从 70% 涨到 100% 的过程里其实系统一直在向你发信号只是大部分信号藏在/proc和各种计数器里不主动去看就发现不了。这一章讲三个最典型的信号也是我判断要不要立刻介入的主要依据。3.1 load average 和 CPU 使用率对不上怎么解释load average 是很多人误解最深的一个指标。它的定义是过去 1/5/15 分钟内处于**可运行状态R和不可中断睡眠状态D**的进程数量平均值。注意后半句D 状态也算进 load这就是为什么会出现CPU 只有 10%load 却有 50的诡异现象。判断逻辑很简单load 高 CPU 高 → 确实是计算密集看是哪个进程在烧 CPU。load 高 CPU 低 %wa高 → IO 瓶颈进程卡在磁盘上。load 高 CPU 低 %wa也低 → 大概率是 D 状态进程卡在内核锁、网络文件系统或者内存回收上。本次故障属于第三种和第一种的混合体。刚开始是 load 和 CPU 一起涨看着像计算密集但涨到一定程度后CPU 不再上涨而 load 还在疯涨这时候性质就变了变成了内核态的等待堆积。还有一个经验值可以参考load average 持续超过 CPU 核数的 2 倍就已经进入危险区超过 4 倍基本可以判定系统不可用。8 核机器 load 到 32 以上你敲个命令要等十几秒是常态。当然这个数值还要结合存储性能看全闪存和机械盘对 IO 等待的耐受度完全不同。vmstat是验证 load 构成最直接的工具vmstat 1 10输出里的r列是可运行进程数b列是阻塞进程数通常就是 D 状态。如果b列持续大于 0 甚至到几十而r列不高那就明确是阻塞问题了。同时看si/so两列如果持续非零说明内存在和 swap 之间来回倒腾这是系统进入恶性循环的强烈信号。3.2 D 状态进程杀不掉的僵尸活人D 状态Uninterruptible Sleep是这类故障的核心角色。这种进程连kill -9都杀不掉因为它处于内核态的系统调用中信号要等系统调用返回才能被处理而它卡在那里根本返回不了。找到 D 状态进程的方法ps -eo pid,ppid,stat,wchan:32,comm | awk $3 ~ /D/看到结果后逐个去看它们的wchan和内核栈cat /proc/pid/wchan cat /proc/pid/stack常见的几个卡点和我对应的判断卡在jbd2_log_wait_commit/ext4_sync_file文件系统日志提交慢多半是磁盘本身性能塌了或者有大量小文件同步写。卡在rpc_wait_bit_killable/nfs_*网络文件系统挂载点无响应服务端挂了或者网络抖动。这种问题最阴险一个 mount 点卡住能把整个系统的 IO 拖死。卡在congestion_wait/balance_dirty_pages脏页太多回写不过来内核在主动节流写操作。卡在mutex_lock/rwsem_down_*内核锁竞争通常伴随高%sy。这里有个非常重要的提醒D 状态进程数量是判断系统是否接近 hang 死的先行指标。当b列的数值从个位数涨到几十说明越来越多的进程被卡住很快连 sshd 都会进入这个队列那时候你就登不进去了。我从看到这个信号到系统完全失联中间大概只有五六分钟。千万不要在 D 状态进程很多的时候去kill -9它们不但杀不掉还会让它们在内核里多做一轮信号处理进一步加重负载。正确做法是先搞清楚卡在哪里。3.3 内存和磁盘的连锁反应CPU、内存、磁盘这三者在故障中往往是连锁的单独看任何一个都会漏掉关键信息。先看内存。/proc/meminfo里我重点关注这几项grep -E MemFree|MemAvailable|Dirty|Writeback|Slab|SReclaimable|SUnreclaim|SwapFree /proc/meminfoMemAvailable才是真实可用内存不要看MemFree那个数字在 Linux 上永远很小因为内核会把空闲内存拿去做缓存。Dirty是还没写盘的脏页数量如果它持续增长到接近dirty_ratio设定的比例默认 20%内核就会开始强制同步应用的写操作会被阻塞。Writeback是正在回写的数据量这个值长期居高不下说明磁盘跟不上写入速度。SUnreclaim是不可回收的 slab 内存如果它持续增长说明内核里有对象在泄漏常见于文件句柄、inode 缓存、网络连接跟踪表。再看 swap。很多人有个误区觉得服务器不该用 swap干脆把它关掉。这个做法在小内存机器上其实是危险的——没有 swap内存压力大时内核只能直接触发 OOM killer。但有 swap 也不代表安全如果si/so持续有几十甚至上百 KB/s 的交换说明系统已经在用硬盘当内存使性能会断崖式下跌。最后看磁盘。iostat -x 1里三个指标最关键%util设备繁忙度、await平均等待时间包含排队、aqu-sz平均队列长度。%util接近 100% 不一定有问题SSD 能轻松打满但await超过 50ms 且aqu-sz持续大于 2就说明队列在堆积IO 已经成为瓶颈。把这三者串起来看本次故障的链条慢慢清晰了某个进程开始大量写日志 → 脏页堆积 → 内核强制回写 → 磁盘队列变长 → 大量进程进入 D 状态 → load 飙升 → 内核内存回收与锁竞争加剧 → CPU 的%sy上升 → 最终调度器负担过重交互式进程拿不到时间片系统 hang 死。4. 本次故障的根因还原排查这类问题我的习惯是列出所有嫌疑人然后逐个用证据排除。这个过程有时候会绕路但绕的路本身也是有价值的——它能帮你把不太可能和确定不是区分开。4.1 第一个被排除的嫌疑人业务代码死循环最初看到 CPU 飙高第一反应当然是业务进程在烧 CPU。当时的判断依据是%us占比还比较高有个应用进程的 CPU 占用从 30% 涨到了 200% 多。验证方法很直接用perf抓一下热点函数perf top -p pid如果真是代码里的死循环热点会高度集中在某几个用户态函数上比如字符串处理、正则匹配、序列化。但实际抓下来的结果是热点分散在copy_user_enhanced_fast_string、__do_page_fault、__alloc_pages_nodemask这些内核函数上用户态函数一个都没进前列。这个结果基本就排除了死循环。copy_user说明在做大量的内存拷贝page_fault说明缺页频繁alloc_pages说明在疯狂申请内存页。这三个凑在一起指向的是同一个方向这个进程在大量申请内存并且频繁访问新页。顺带说一句perf在生产环境用的时候要注意开销。perf top本身开销不大但perf record -g带调用栈采样的开销就比较可观了系统已经卡的时候别用。4.2 第二个嫌疑人日志暴涨与磁盘写满应用内存涨得快第一个联想到的就是日志。检查结果也确实印证了这一点du -sh /var/log/* ls -lh /path/to/app/logs/ | tail -20发现应用日志目录下有个文件在半小时内涨到了十几 GB而且还在持续增长。日志内容高度重复是某个下游依赖超时后应用在疯狂重试每次重试都打一条完整堆栈。这就解释了一部分现象大量写日志导致页缓存里脏页堆积脏页回写又拖慢了磁盘磁盘慢导致更多超时超时又触发更多重试和日志——完美的正反馈循环。但这里有个疑点只是日志多不至于让系统 hang 死。现代 Linux 对脏页有节流机制写日志的进程会被限速最多是它自己变慢不太可能拖垮整个系统。而且磁盘容量还有富余没到写满的程度。所以我判断这是一个放大器不是根因。它把问题的严重程度放大了好几倍但最初的触发点不是它。4.3 真凶浮出水面内存回收和锁的雪崩真正的线索来自dmesg。在重启之前抢救出来的那份日志里有几条记录非常关键INFO: task java:12345 blocked for more than 120 seconds. Out of memory: Kill process 12345 (java) score 800 or sacrifice child第一条是内核的 hung task 检测说明有任务阻塞超过 120 秒。第二条是 OOM killer 被触发了。再结合/proc/meminfo里的数据MemAvailable掉到了几百 MBSUnreclaim在半小时内涨了将近 2GBDirty长期维持在接近上限的位置。把这几条串起来事情的经过就清楚了应用在某个时间点开始出现内存增长异常具体原因事后查是某个缓存结构没有设上限属于代码问题。内存吃紧之后内核开始频繁回收页缓存而页缓存里全是日志文件的数据。回收触发回写回写又要等磁盘磁盘上同时还压着日志本身的写入。于是内核的内存回收线程和文件系统日志线程开始互相等待%sy快速上升。当可用内存进一步减少内核开始扫描 slab、回收 inode 和 dentry 缓存同时尝试压缩内存页。这些操作都要持有各种内核锁锁持有时间一长其他进程就开始排队。排队导致更多进程进入 D 状态D 状态进程不计入 CPU 使用率但计入 load于是出现了CPU 看着没那么高load 却爆炸的诡异现象。最后一步是 OOM killer 启动但它杀进程本身也要遍历进程列表、计算评分、发送信号这些操作在内存极度紧张的时候同样很慢。等它把进程杀掉系统其实已经处于半瘫痪状态大量进程还堵在 D 状态里出不来交互式登录彻底无望。一句话总结内存泄漏是引信日志暴涨是助燃剂内核内存回收与文件系统回写的相互等待是爆炸本身。5. 系统已经进不去了怎么办抢救与证据保留hang 死之后的处置是最考验判断力的环节。这时候每一秒都在损失业务但你如果乱操作损失的可能就是根因线索下次还会再来一遍。我的原则是能用温和手段解决就不重启非重启不可就先尽量把证据捞出来。5.1 还能登录时的最后几步在 SSH 还没完全断、只是响应很慢的阶段你还有机会做几件事。第一步立刻停掉日志写入。找到那个疯狂写日志的进程如果是应用先把日志级别调高或者把输出重定向到/dev/null能瞬间减轻磁盘压力。操作要轻别用vim去改配置文件用sed -i或者直接给进程发信号更安全。第二步尝试回收一部分内存。温和一点的做法是sync之后释放页缓存sync echo 1 /proc/sys/vm/drop_caches注意这只能释放干净的页缓存脏页必须先写盘所以sync那一步可能要等很久甚至卡住。如果sync卡住了千万别再往下走说明磁盘已经彻底堵死。另外这个操作会让后续的读性能短暂下降属于应急手段不能当日常操作。第三步给应用进程发 TERM 而不是 KILL。kill -15能让进程走正常的清理流程释放内存kill -9直接暴力干掉虽然快但可能留下不一致的状态。只有在进程完全不响应 TERM 的时候才用 KILL。第四步如果确认是某个非关键进程导致的可以考虑用cgroup限制它的资源。CentOS 7 上用 systemd 的MemoryLimit和CPUQuota就能做到但前提是进程本来就是被 systemd 托管的运行中改限制需要重启服务这时候不一定来得及。有个小技巧SSH 连不上但 ping 通的时候可以试试直接用已有的会话如果还开着或者从同网段的其他机器用ssh -o ConnectTimeout5加上很短的超时去连。别用默认的长超时那样会白白浪费时间。5.2 Magic SysRq键盘上最后的救命稻草如果 SSH 完全进不去控制台也卡住了那就只剩 Magic SysRq 这一条路了。这是一组由内核直接处理的热键不依赖用户态进程只要内核还活着就能响应。对于云主机或者没有物理键盘的环境可以通过/proc/sysrq-trigger用串口或者带外管理写入。先确认开关打开了cat /proc/sys/kernel/sysrqCentOS 7 的默认值是 16只允许sync一个操作。要使用全部功能需要设为 1echo 1 /proc/sys/kernel/sysrq或者写进/etc/sysctl.d/99-sysrq.conf持久化。这个开关的位含义比较细16 表示只允许紧急同步1 表示全部允许更精细的控制可以查内核文档里的位掩码表。最常用的一组组合键是 R-E-I-S-U-B按顺序按下去每一步之间留一两秒按键作用说明R把键盘从原始模式切回 XLATE 模式让后续按键能被内核正确识别E给除 init 外的所有进程发 SIGTERM温和终止让进程有机会释放资源I给除 init 外的所有进程发 SIGKILL强制终止清理残局S紧急同步所有已挂载文件系统保护数据一致性U把所有文件系统重新挂载为只读防止后续写入破坏数据B立即重启最后一步在云环境或者远程环境里更实用的方式是通过/proc/sysrq-trigger逐条写入echo s /proc/sysrq-trigger # 同步 echo u /proc/sysrq-trigger # 重挂为只读 echo b /proc/sysrq-trigger # 重启这套 SUB 组合是我在硬重启前必做的动作能最大限度避免文件系统损坏。直接断电或者直接reboot -f的风险在于缓冲区里的脏数据可能只写了一半轻则丢文件重则文件系统需要 fsck。还有一个更有价值的用法在重启之前先把内核里的任务栈 dump 出来。echo w /proc/sysrq-trigger # 输出所有 D 状态任务的栈 echo t /proc/sysrq-trigger # 输出所有任务的栈 echo l /proc/sysrq-trigger # 输出所有 CPU 的 backtrace echo m /proc/sysrq-trigger # 输出内存信息这些输出会进到内核日志里如果配置了串口控制台或者 netconsole就能在另一台机器上完整接收到。这是 hang 死状态下唯一能拿到的进程级现场价值极高。我这次就是靠sysrq-w的输出确认了那几个卡在文件系统上的进程。这套机制最好在机器健康的时候就配好验证一遍。真正出事的时候你只有一次机会命令敲错一个字母就前功尽弃。我建议把这几条命令写成一个脚本放在/root下出问题直接执行。5.3 重启之后证据去哪儿找机器重启不代表结束反而是事后分析的开始。重启后第一时间要做的是把持久化的日志收集起来。几个必看的位置journalctl -k -b -1 # 上一次启动的内核日志 journalctl --list-boots # 列出所有启动记录 ls -lt /var/crash/ # kdump 生成的 vmcore dmesg -T | head -100 # 本次启动的早期日志journalctl -k -b -1这条命令是重点-b -1表示上一次启动能拿到重启前的内核日志。前提是 journald 配置了持久化存储CentOS 7 默认把日志放在/run/log/journal是内存文件系统重启就没了。要改成持久化需要创建/var/log/journal目录journald 会自动切换过去。如果想拿到更完整的信息可以提前配置 kdump。它在内核崩溃或者触发 panic 的时候把内存镜像保存成 vmcore 文件配合 crash 工具能做非常深入的分析。配置方法是设置crashkernel预留内存启用 kdump 服务然后在/etc/kdump.conf里指定保存路径。注意 CentOS 7 上预留内存要按实际内存大小计算一般 2GB 内存预留 128M8GB 以上建议预留 512M 甚至更多。6. 事后加固把下一次挡在门外故障处理完如果只做重启恢复业务这一件事那这次故障就白经历了。我一般会花半天时间做几件事补监控、调参数、改配置、写文档。这一章分享我认为投入产出比最高的几项。6.1 监控指标怎么配大部分团队配监控只看 CPU 和内存使用率这两个恰恰是最后才出问题的指标。真正该盯的是下面这几个先行指标指标采集方式告警阈值建议D 状态进程数vmstat的b列持续 1 分钟 5 告警load average / 核数uptime或node_load1持续 5 分钟 2 告警可用内存比例MemAvailable / MemTotal 15% 预警 8% 告警脏页比例Dirty / MemTotal 10% 告警IO awaitiostat的 await 100ms 持续 3 分钟上下文切换vmstat的cs同比突增 3 倍内核日志关键字dmesg抓取出现 OOM、hung task、blocked 立即告警这里重点说两个。D 状态进程数是我认为最有价值的指标因为它直接反映系统卡住的程度比 CPU 和 load 都更灵敏。采集方式也简单用vmstat 1 1的输出取第二行第二列或者直接读/proc/loadavg的第四段分子是当前运行阻塞进程数分母是总进程数。内核日志关键字告警是另一个必需品。OOM killer、hung task、文件系统错误、网卡丢包这些事件全在内核日志里而且它们的出现往往早于业务告警几分钟甚至几十分钟。做法很简单用dmesg或者journalctl -k定期抓取正则匹配关键字推送到告警平台。除了监控我还建议把sysstat装好并确认sadc在正常采集。默认是 10 分钟采样一次保留 7 天数据。事后回溯的时候sar -q、sar -r、sar -b能给你非常完整的时序数据比监控平台的曲线细多了。# 修改采集频率为 1 分钟 sed -i s/^\*\/10/* * * * */ /etc/cron.d/sysstat # 查看历史 load sar -q -f /var/log/sa/sa156.2 内核参数与系统限制的几处调整参数调整是最容易被照抄错的环节。网上流传的一堆Linux 性能优化参数很多是十年前的服务器、特定负载下的经验直接套用可能适得其反。我只分享几个确认有普适价值的调整而且要说明白为什么要改。第一处是脏页回写策略。默认的dirty_ratio是 20%意味着允许 20% 的内存变成脏页这在内存大的机器上是一个巨大的数字——64G 内存允许 12G 脏页一次回写能堵死磁盘好几秒。我一般会调低vm.dirty_ratio 10 vm.dirty_background_ratio 3同时设置dirty_expire_centisecs 150015 秒让脏页更早开始后台回写。这个调整的代价是会多一些小的写操作但换来的是更平滑的写延迟。第二处是强制阈值和后台阈值的比例。dirty_background_ratio触发后台回写dirty_ratio触发同步阻塞写。后台阈值一定要显著小于强制阈值否则会出现后台还没来得及写前台已经被堵住的情况。经验值是后台设为强制的 1/3 到 1/2。第三处是 OOM 相关的panic_on_oom。默认是 0允许 OOM killer 杀进程。有些团队会设成 1让系统直接 panic 重启理由是反正要重启不如干脆点。这个要看业务如果是无状态服务重启确实比半死不活强如果是有状态服务杀进程可能比重启更温和。没有标准答案但必须是有意识的决策不能是照抄来的。第四处是hung_task_timeout_secs。默认 120 秒。这个参数只影响告警不影响行为但有些老文档建议把它改成 0 来消除告警这是极其危险的做法——它只是把警报关了问题还在。正确做法是保持默认或者根据业务特性调小到 30 秒让问题更早暴露。第五处是fs.file-max和进程级的nofile。文件句柄耗尽是一个非常隐蔽的问题表现出来往往也是 CPU 高、响应慢。检查方法cat /proc/sys/fs/file-nr # 已分配/空闲上限/系统上限 ulimit -n # 当前 shell 的限制 cat /proc/pid/limits | grep files # 进程的实际限制如果第一个数接近第二个数说明系统级句柄快满了。这种情况多半是有进程泄漏了句柄先去查进程别急着调大上限。调大上限只是把问题往后推。还有一类问题值得单独提一句inotify watch 耗尽。日志采集、配置热加载这类工具会监听文件变化每个监听都占一个内核对象。max_user_watches默认值在 CentOS 7 上大概是 8192一些采集工具动辄要监听几十万目录直接把内核内存吃掉一大块。检查方法cat /proc/sys/fs/inotify/max_user_watches find /proc/*/fd -lname anon_inode:inotify 2/dev/null | wc -l这个问题在故障现场极其难联想表现出来的症状是某些进程莫名其妙变成 D 状态查看栈又看不出所以然。我是在第二次遇到类似问题之后才把它加进检查清单的。6.3 常见问题速查表整理一份速查表出问题的时候按表查比凭印象瞎猜快得多。现象最可能的原因首选排查命令load 高但 CPU 低、%wa也低D 状态进程卡内核锁ps -eo stat,wchan,comm | grep D%sy高、%us低系统调用密集或锁竞争perf top -g看内核函数占比%si高网络软中断小包或连接数过多cat /proc/softirqs对比各核%wa高、await高磁盘瓶颈iostat -x 1看队列深度si/so持续非零内存不足在用 swapvmstat 1配合/proc/meminfokswapd0占高 CPU内存回收频繁看Dirty和SUnreclaimjbd2/*占高 CPU文件系统日志提交频繁检查是否大量小文件同步写sshd 连不上但 ping 通应用层阻塞或进程数耗尽查看 max processes 和 D 状态进程数进程杀不掉D 状态信号无法投递cat /proc/pid/stack找卡点日志出现 blocked for more than内核 hung taskecho w /proc/sysrq-trigger抓栈文件句柄报错 Too many open files句柄泄漏或上限太小lsof -p pid | wc -l网络连接异常但网卡正常conntrack 表满dmesg | grep conntrack这张表里我想特别强调第一行和最后一行因为这两类问题最容易被误判成CPU 问题。前者实际是 IO 或锁后者实际是网络栈的资源耗尽去查 CPU 永远是南辕北辙。最后说一个实操层面的经验排查过程中把每一步的命令和输出都记录下来。不一定要很正式一个文本文件就行随手粘贴。原因有二一是人的短期记忆在压力下极其不可靠你十分钟前看过的那个数字回头想复现经常记错二是复盘的时候这些记录是写事故报告最直接的素材也是团队知识沉淀的基础。我个人在实际操作中的体会是这类CPU 飙高到 hang 死的故障八成以上的根因不在 CPU 本身而在内存和 IO 的相互作用上。真正决定排查效率的不是你会多少命令而是你能不能在系统还活着的那几分钟里忍住先重启再说的冲动把该抓的东西抓下来。那几分钟的克制换来的是后面几小时甚至几天的从容。另外一个我踩过很多次的坑是别在故障现场去改内核参数试试看参数的影响面太大试错成本极高所有调整都应该在测试环境验证过再上生产。