
1. 先搞清楚故障排查到底在排什么干了这么多年运维我见过太多同事在系统出问题时直接抄起键盘就开始一通操作看到CPU高就kill进程看到磁盘满就rm文件折腾半天发现故障还在甚至把业务搞得更糟。问题出在哪出在缺少一套系统性的排查思路。Linux系统故障排查核心不在于你会用多少命令而在于你如何把“现象”还原成“链路”。一台服务器从硬件到内核再到文件系统、进程调度、网络协议栈每一层都可能出问题而现象往往是最表面的一层。比如用户反馈“网站打开很慢”可能的根因有几十种CPU争抢、磁盘I/O、网络丢包、DNS解析慢、后端应用线程阻塞、甚至是物理机邻居抢占带宽。如果没有一套成体系的排查思路你就是在瞎猜。我倾向把排查思路拆成六个固定动作确认现象、评估影响、收集信息、形成假设、验证根因、修复复盘。这六步看起来简单但每一步都有讲究。确认现象不是听用户说“卡了”就完事要量化到底什么指标异常评估影响决定了你的处理优先级——是保业务优先还是定位根因优先收集信息要求你在动手“修”之前先把现场保留下来否则进程一重启、缓存一清空证据就没了。这套方法论适合谁刚入行的运维新手、从开发转到运维的工程师、以及想要标准化团队故障处理流程的运维负责人。后面我会用实际案例把每一步掰开来讲包括我这些年踩过的坑和总结出的经验。提示故障排查的第一原则是“先恢复后定位”。如果业务正在受损优先通过重启服务、切换流量、扩容节点等手段止损再慢慢分析根因。别为了保现场而让故障持续发酵。2. CPU与系统负载最容易被误判的一类问题2.1 先看懂负载和CPU使用率的关系很多人一看到load average超过CPU核数就慌了认定是CPU不够用。这是个非常大的误区。我做个类比CPU使用率是“正在干活的比例”而负载是“排队等干活的人数”。想象一家银行柜台一个柜台一个CPU核同时只能服务一个客户使用率是说这个柜员有多忙而负载则统计着正在办业务加上排队等着的所有人。如果一个柜员同时在处理大量的事务但每个事务都在等磁盘返回数据也就是I/O等待他手上没闲着可也没在真正计算——这时候CPU使用率可能不高但负载飙升。所以排查的第一步永远是先区分负载升高到底是CPU计算密集、I/O等待还是进程阻塞。实操上我会依次执行三条命令uptime top vmstat 1 5uptime看过去1/5/15分钟的负载趋势判断是突发还是持续。top看整体CPU占用分布重点看us用户态、sy内核态、waI/O等待和id空闲几个值。vmstat 1 5每隔1秒采样5次看r运行队列、b阻塞进程数、wa、cs上下文切换这几列。如果us高说明有进程在疯狂消耗CPU下一步用top -Hp pid找到具体线程或者用pidstat -t -p pid 1观察哪个线程占CPU。如果wa高而us低那问题在磁盘I/O别在CPU层面浪费时间直接跳到磁盘排查。如果r队列长期大于CPU核数的数倍但us/sy并不高多半是进程在锁等待或者内核态出了问题。2.2 CPU排查的实战路径定位到CPU消耗大户之后接下来要回答“它在干什么”。我常用的组合是perf top或者pidstat配合/proc/pid/status查看线程状态。生产环境不一定有perf权限这时候strace -p pid可以看系统调用但注意strace在高频进程上有严重性能开销使用前要评估影响。排查CPU问题时的三个容易翻车的点第一别忽略内核态sy过高。如果sy持续超过30%多半是有系统调用风暴比如频繁的网络收发、锁竞争、或硬件中断处理过多。用top按CPU排序时如果排前面的都是ksoftirqd或kworker这类内核线程就得检查是不是网卡软中断在某个CPU上集中触发。查看/proc/interrupts能确认中断分布是否均匀必要时调整网卡队列的RPSReceive Packet Steering设置。第二当心上下文切换过高。vmstat的cs列持续几十万说明系统在疯狂切换进程/线程CPU资源大量消耗在“换人”而不是“干活”上。常见诱因是线程池开得过大、锁竞争严重或者定时器频繁触发。我处理过一个案例应用线程池配置了200个线程但活跃线程只有几个大量线程在锁上等待上下文切换高得吓人把CPU全部浪费完了。把线程池调小之后性能反而上去了。第三CPU绑核相关的隐蔽问题。多核环境下如果进程频繁在不同CPU核之间迁移缓存命中率下降也会造成CPU使用率偏高。NUMA架构下跨节点访问内存更是性能杀手。排查看/proc/pid/numa_maps能发现内存是否跨节点分布必要时调整进程的NUMA策略。3. 内存与SwapOOM不是终点只是起点3.1 内存问题的排查顺序内存排查我先看三条命令的输出free -h vmstat 1 5 dmesg -T | tail -50free -h看总量、已用、可用和Swap使用情况。有个细节必须说清楚Linux的free输出里available才是真正可分配给新进程的内存量而不是free列。因为buff/cache在内存压力下能回收所以一台内存用了90%的机器如果available还有30%系统并不会触发OOM。vmstat重点看siswap in和soswap out。如果这两个值持续不为0说明物理内存已经不够用系统在靠Swap硬撑。注从磁盘换进换出内存页的性能损耗极大比直接物理内存访问慢好几个数量级。我见过一台数据库服务器Swap使用了8GB查询延迟翻了几十倍就是这个原因。dmesg一定要看因为OOM Killer杀进程时会在内核日志里留下完整记录包括谁触发了OOM、当时内存最紧张的进程是谁、回收了多少内存。日志里通常还有这样一行Out of memory: Killed process 12345 (java) total-vm:...。这是定位“谁杀了我的进程”的最直接证据。3.2 OOM的深层原因很多人以为OOM就是“内存不够了”其实背后还有几种情况。一是内存碎片化。物理内存总量还有不少但连续的“大块”内存满足不了应用的需求而Linux默认启用的是虚存管理可能无法有效处理大块连续分配。密集的内核对于这类请求会失败、触发OOM。这种情况跟应用申请大块内存比如JVM堆、搜索引擎倒排索引加载有关系。/proc/buddyinfo能看到内存碎片状况如果高阶块全为0基本可以判断是碎片问题。二是内存泄漏。应用长时间运行内存只涨不降最终触发OOM。排内存泄漏我通常用ps aux --sort-rss看RSS排序锁定增长异常的那个进程然后配合监控确认它的内存趋势。我实习带过一个案例一个后端服务每处理一次请求就多占用几MB内存跑三天就OOM最后定位是代码里某个缓存在无限增长没有清理。这种问题排查的难度不在定位而在于需要长时间观察所以建议监控告警里一定要有进程内存的趋势曲线。三是Cgroup限制。容器环境里最常见宿主机还有大把内存但容器内的Cgroup内存限制到了一样触发OOM。排查时不要只看宿主机free要检查容器的Cgroup使用量cat /sys/fs/cgroup/memory/memory.usage_in_bytes cat /sys/fs/cgroup/memory/memory.limit_in_bytes3.3 Swap的正确配置姿势关于Swap不同的场景有不同的处理方式。数据库这类延迟敏感型的服务我通常建议降低swappiness——这个内核参数控制内核倾向于换出内存页的程度取值范围0~100默认通常是60。数字越大内核越积极使用Swap。降低到10甚至个别场景直接关掉Swap能让数据库尽量留在物理内存里。但对于一些批处理型任务或者内存峰值明显的应用留部分Swap做兜底其实是保护机制防止运营高峰期直接OOM。这就需要在监控上看到 Swap 使用明显上升时就要发出预警因为通常应用程序会因内存压力剧烈波动而性能急剧下降。在修改swappiness参数时用sysctl vm.swappiness10可以临时生效要持久化需要写到/etc/sysctl.conf里。修改之前先看当前值改完之后用sysctl -p重载。有一点要记牢这个参数只影响内核换出的“积极性”不会适配应用都禁用Swap。在计划禁用时你得保证应用内存峰值确确实实低于物理内存容量否则就是在赌运气。4. 磁盘与文件系统空间满只是最浅的一层4.1 两个容易搞混的“满”磁盘满分两种容量满和inode满。前者好理解后者是很多新手没概念的东西。inode可以理解为文件系统的“户口本”每个文件都要占一条记录。如果小文件特别多可能容量还有剩但inode已经用完表现为“No space left on device”。排查命令分别用df -h和df -i。如果df -h显示还有几十GB但写入文件报错磁盘满第一反应就该查df -i。我在一个日志采集服务器上遇到过这个问题每天产生上千万个小日志文件日志轮转配置没做好一个月之后inode耗尽。当时df -h才用了60%但整个分区再也写不进任何数据。处理inode耗尽的思路找到哪个目录文件数量最多通常用for d in /var/log /tmp /home /data; do echo $d; find $d -xdev -type f | wc -l; done先定位再清理。事后一定要把日志轮转logrotate策略修正甚至把应用改为按时间分批存储大文件减少小文件数量。这里多说一句很多 NoSQL 存储和消息队列产生的“小文件堆积”问题根子都出在业务设计时没考虑文件数量而不只是运维配置的锅。4.2 找出谁在偷偷占空间du -sh /*从根往下逐层定位是排查“容量满”的最高效方式。小技巧定位到具体目录后用du -sh /目录/* | sort -hr | head -20列出最大的一批文件。注意du在大目录上会很慢特别是包含大量小文件的情况优先用--max-depth1限制层级。可能有朋友遇到过这种情况du -sh /加总算出来的总量远小于df -h显示的已用空间。这不是统计误差最常见的原因是“文件被删除但进程还持有句柄”。进程打开了一个文件rm删除了它但从内核的角度这个文件仍然存在直到持有它的进程把句柄关闭才会真正释放磁盘块。这就是为什么df显示空间满但du找不到大文件。排查命令lsof L1它会列出所有“被删除但仍被进程打开”的文件包括大小。看到结果后找到占空间最大的那个评估它属于哪个进程。如果进程没法重启可以尝试用/proc/pid/fd/下的软链接配合truncate -s 0清空文件内容这招在遇到磁盘阻塞的情况下特别管用——不是真正截断底层已删除文件的内容而是让磁盘块能被系统回收。磁盘空间满还有一个容易被忽视的点root用户的保留空间。Ext4文件系统默认给root预留5%的空间这是为了避免普通用户把磁盘写满后系统无法正常运作。对于大容量分区5%可能浪费几十GB对于家目录这类分区可以调小但对根分区我建议保留。调预留比例用tune2fs -m 2 /dev/mapper/xx-root修改前先用dumpe2fs查看当前保留块设置。4.3 I/O瓶颈用iostat和iotop精准定位当vmstat的wa值高时I/O层面要立刻转场排查。第一步iostat -x 1 5看%util、await、svctm这几个参数。很多人有个误解认为%util达到100%就是磁盘“满了”其实%util表示设备忙但不代表无法服务更多请求。如果%util只有30%但await已经很高可能是有请求在队列里排队或者底层设备比如磁盘阵列控制器出现了性能瓶颈又或者请求大小和模式不合理导致单次I/O延迟高。定位到具体是哪些进程在刷盘用iotop或pidstat -d 1。这类工具需要权限如果没有可以先把block_dump监测器打开或者用bcc工具库里的biosnoop追踪I/O事件。生产环境里我定位过几次I/O相关的场景数据库频繁小写刷新每次事务都要刷日志文件典型的同步写模式在机械盘上延迟很大。优化方向是缓存/批次写入、独立日志盘、或换SSD。日志收集工具并发读大量文件缓存基本无法命中随机读I/O压力巨大。某应用 fork 了大量子进程后同时启动每个子进程都要读取共用的配置文件瞬间读放大把I/O打满。解决方案是把共享配置做成动态加载避免启动时全量读取。5. 网络层故障连接问题的背后5.1 先分清是“不通”还是“拒绝”用户说“连不上”背后可能是完全不同的故障。我要求团队先跑三条命令ping 目标IP ss -lntp | grep 端口 telnet 目标IP 端口 或 nc -vz -w 3 目标IP 端口ping不通说明网络链路或对端主机有问题也可能是防火墙拦截ICMP。但注意ping通不代表端口可用ping不通也不代表端口不可用——有些服务器出于安全考虑禁ICMP但业务端口正常。所以连通性测试只是第一步端口测试才是关键。telnet/nc连不上时要区分两种情况连接超时timeout和拒绝连接refused。timeout通常意味着中间链路不通或者对端防火墙把包丢了用的是DROP策略refused说明包已经送到对端但对端没有进程监听该端口或者本机防火墙用的是REJECT策略。一个常见场景服务刚启动还没完全监听端口或者进程已经挂掉但健康检查还在探测这时候对端会回RST表现就是refused。5.2 细看监听与连接状态ss是现在的主流工具我之前的老同事还在用netstat但生产上ss信息更全、速度更快。看监听和连接状态ss -lntp ss -s ss -ant | awk {print $1} | sort | uniq -css -s做总览——当前建了哪些连接、在等待的、在排队的一目了然。重点看两个异常状态SYN-SENT大量堆积说明本机发出去的连接请求没得到应答多半是对端或中间网络出问题TIME-WAIT堆积是正常现象但如果量大到让端口耗尽就需要调整内核参数。针对高并发的短连接应用场景比如网关TIME-WAIT过多是个经典问题内核参数调整要小心权衡net.ipv4.tcp_tw_reuse搭配tcp_timestamps可以在出站连接上复用TIME-WAIT连接但乱改可能引入连接复用的异常风险。如果ss -lntp显示监听队列出现溢出Recv-Q堆积这是半连接队列或全连接队列满的表现。全连接队列溢出可以通过ss -lnt看到 Recv-Q 超过Send-Q来判断原因通常是应用处理连接速度跟不上新连接到达速度。排查时先看这几点应用线程数是否足够、listener backlog设置是否偏小很多部署默认值往往是128对于高并发场景明显不够调整为2048或以后、系统net.core.somaxconn是否同步调大。5.3 抓包定位问题链路网络排查里最有力的终极大招就是抓包。tcpdump是必会工具简单场景看三次握手阶段是否有遗漏比如SYN发出去了对方不回SYN-ACK问题多半在网络路径或对端防火墙如果是SYN-ACK回过来了但ACK不回问题可能出在本机的协议栈或socket队列。tcpdump -i eth0 host 目标IP and port 8080 -w /tmp/capture.pcap抓包文件用wireshark分析或者直接tcpdump -r看。抓包一般建议在问题的两端分别抓对照时间戳。举个例子一次偶发访问变慢单机抓包看到TCP重传频繁换网络设备侧一起抓结果发现是中间交换机的链路聚合hash策略不均衡导致的单链路拥塞。这个案例说明网络层问题不一定能通过看本机指标定位出来跨节点抓包对照才是正解。DNS问题也是网络排查中极高频的痛点。表现为“某些域名时通时不通”、“首次访问特别慢”。用dig trace可以看到解析链路的耗时在哪个环节/etc/resolv.conf检查配置的DNS服务器是否合理。我排过的一个案例应用配置的DNS服务器指向一个早已下线的内网DNS解析超时后才走备用每次请求白白消耗5秒。把DNS配置改对后延迟骤降。6. 常用排查工具组合与监控基线6.1 建立一套工具组合和排查字段用哪个工具不关键关键是你对输出字段的含义是否了然于心。我给自己定了一套排查顺序也推荐给团队第一梯队发现异常uptime、top、free、df、ss、dmesg第二梯队关联分析vmstat、iostat、sar、pidstat第三梯队深挖定位strace、perf、tcpdump、lsof、gdbsar在我心里的地位比较特殊。新接手一台机器我会先看它的历史数据来确定“正常态”长什么样。它能返回历史某段时间的CPU、内存、磁盘、网络数据这是其他工具替代不了的——你没有历史数据怎么判断现在的“异常”是真的异常呢有些年头久远的监控工具已经部署好了但还是建议把sysstat装好定时采样默认配置覆盖范围很广别随意关闭。排查时我习惯把“时间线”拉出来把系统报障时间、监控告警时间、应用日志报错时间、内核日志异常时间对齐。故障排查和刑侦差不多时间线对不上很大概率走弯路。举例监控显示磁盘I/O 3点开始飙高但应用发版是3点半业务量也是3点半才开始涨的说明I/O问题是先发生的业务变慢可能是I/O导致的结果而不是原因。这么一捋排查方向马上纠正过来。6.2 监控基线与告警阈值的设计思路运维工作一半在故障发生后的排查另外一半在故障发生之前的预警。我给客户做方案时经常强调“监控基线”的概念。每台服务器都有自己的“性格”有的业务本身就在夜间跑批CPU占满很正常有的服务内存就是会缓慢增长两三个月重启一次属于正常节奏。盲目套用通用告警阈值只会让告警失去可信度久而久之成了狼来了。建立基线的做法至少收集各个核心指标的最近30天数据用百分比线进行建模。例如CPU使用率的P95第95百分位是60%那告警阈值设在80%比较合理——既不会频繁误报又能在真实异常出现时及时提醒。内存监控要重点关注available而非used磁盘监控要同时看容量和inode网络监控除了流量还要看重传率、丢包率。这些从故障处理中学到的经验应该沉淀成监控配置的标准。注意监控不是越多越好过多的曲线只是在告警时增加噪音。先覆盖能反映问题的关键指标逐步迭代补齐。7. 典型故障复盘与避坑经验7.1 一个“空转”CPU问题的完整复盘有一次同事反馈一台应用服务器响应变慢我按顺序排查top看到us30%sy40%负载却不低。当时第一反应是系统调用异常。用pidstat找出占用高的进程再strace -c -p pid统计系统调用分布发现futex用户态锁调用次数高得离谱。继续看线程栈发现大量线程堵在pthread_cond_wait同时业务线程和回收线程在共享资源上有极高的锁竞争。确认根因后修复方案是把原来的单锁拆分成分段锁同时调整了线程池大小。这个案例的关键点是如果只停在“CPU 使用率高”这个层面就盲目重启下次故障很快又会出现。锁竞争问题靠重启是掩盖不掉的。这个案例也说明有时候CPU的sy高并不只与内核代码路径有关应用层不合理的锁使用同样会引发内核态的高开销因为futex的实现正是大量动用内核的系统调用。7.2 一次磁盘“离奇”占满的排查记录一个分区df显示用量100%du -sh却只统计出几GB这一看就猜到有文件被删了但进程没释放。执行lsof L1后看到一堆被日志进程打开、但文件名已经带(deleted)标记的大文件总大小几十GB。这些句柄属于一个常驻进程理论上重启进程能解决但生产环境不允许随意重启。当时我用了一种更轻量的方式找到lsof显示对应进程的/proc/pid/fd/数字软链接对其中一个 fd 执行: /proc/pid/fd/数字或truncate -s 0把它的文件内容清零让磁盘块被回收。这个办法可以应急但要注意直接对服务进行清空文件内容操作虽然能快速释放磁盘但后续日志写入可能因为文件偏移量异常而出问题稳妥方案还是尽快通知应用方重启、恢复日志轮转配置。这个坑告诉我的经验是日志文件被删除的现象远比想象中频繁add 一条带大小排序的lsof L1巡检脚本到运维工具箱里能节省很多救火时间。7.3 网络“间歇性”超时的迷惑行为一个内部服务偶发超时一个月被投诉两次每次都是规律性地在业务高峰期出现。单查服务端负载不高网络流量也不大往返延迟看起来正常。加了抓包后看到规律抓包客户端在特定时间点出现多个TCP Dup ACK和重传包。因为访问链路上有一跳负载均衡设备它的连接跟踪表老化机制在高连接数下会误杀部分存活连接导致对端不断重传。绕过或调整硬件的会话保持参数后问题消失。这个案例值得记住的是网络问题有链路设备和主机两层很多偶发问题只在本机排查永远找不到根本原因。要勇于跳出单机的视角沿着数据路径从应用端到服务端一步步验证包括中间的所有设备。7.4 几个可以直接“抄作业”的排查口诀带新人时我常给几句口诀也写在这里供参考负载高先分三类计算型看usI/O型看wa锁型看cs别绕弯。内存不足看available别纠结free太小。磁盘报满查两处空间df -hinode 用df -i都满了才叫满。连接异常先分四类超时、拒绝、重置、卡住——对应不同的排查方向。事前不理解事后不看记录排查一万次也就停留在表面。把每次故障的时间线、根因和修复方案写进文档才是成长最快的方式。7.5 关于工具权限与安全操作的提醒最后提一点安全事项。生产环境使用系统排查工具必须严格遵守“只读优先”的原则。像top、vmstat、iostat、ss、dmesg这些只读命令可以放心使用但strace会停住目标进程尤其高频场景gdb介入更是停进程级操作使用前必须经过审批并在低峰期执行。另外涉及修改内核参数的命令每次尽量只改一个参数并用sysctl -a验证避免批量改动后无法确定问题来源。我自己有过一次教训为了一次性解决某个网络问题我同时改了五六个TCP相关参数结果问题没解决反而引入了新问题——最后花了一下午逐项调回去才恢复。从那以后我给自己立了一条规矩生产环境的任何调整都要“小步快走、可回退、留记录”。写在这个内容之后做运维这些年我最大的体会是故障排查能力不是靠背命令练出来的而是靠一次次故障后的复盘积累出来的。工具是固定的系统的机制是清晰的难的是如何在紧急情况下冷静地按着链路走不被表象带偏。如果你刚入行建议从今天开始做一个记录每次遇到故障把时间线、现象、排查动作、根因、修复方案写清楚。坚持半年你再看故障视角会完全不同。还有一点多读内核文档和系统源码中对调度、内存、网络机制的解释本质上排查功夫的深浅其实就看你对系统机制理解的深浅。