
深夜收到“Redis内存使用率超过90%”“Java进程CPU使用率99%”“磁盘空间不足”之类的告警绝对是每个运维和开发最不想遇到的事。但告警不是来了就慌更不是直接重启机器就能糊弄过去——重启可能把现场销毁让后续排查变得更难。我这些年处理过不少Linux服务器和K8s集群的突发故障攒下了一套“作战地图”从收到告警到定位根因有一套固定打法。这篇就把这套方法毫无保留地分享出来希望能帮你在告警炸裂的深夜少走弯路、快速止血。这篇内容主要面向Linux系统运维、SRE、后端开发尤其是被K8s、Java应用、数据库实例等各种告警轰炸过的人。即使你刚入行只要会基础的Linux命令也能从这套方法论里找到直接可用的排查思路。我会先讲整体判断逻辑再拆解CPU、内存、磁盘、网络、容器应用等常见告警的具体排查命令最后给出一份可以直接收藏的速查表。1. 先从“告警面”框定故障边界告警不是越早处理越好关键在于判断“要不要马上处理”以及“病根大概在哪个层面”。很多新手收到告警后第一反应是登录服务器看一眼发现负载高就开始杀掉进程这种操作挺危险的。1.1 别急着动手先看告警类型我自己的习惯是把告警分成三类持续型告警、突发型告警、趋势型告警。持续型告警比如CPU已经连续5分钟保持在90%以上这种通常不是瞬时抖动多半有长期线程跑满或者死循环。突发型告警比如磁盘IO一秒冲高过两分钟又恢复正常这种大概率是定时任务、日志压缩、或某个批处理跑完就结束了。趋势型告警内存持续上升、磁盘空间随时间减少这类要特别当心往往是泄漏比如Java堆外内存没释放、日志文件没有轮转。收到告警后先把告警里的主机IP、检查项、当前值、阈值、持续时间记录下来。别说“反正知道了”后面写复盘定位时间线的时候这些初值非常关键。1.2 快速登录后第一件事收集现场不管什么类型的告警登录服务器后第一个命令我几乎永远是三连uptime dmesg | tail -50 dateuptime看负载顺便看开机时长和当前时间。如果系统时间不对会对日志定位造成非常大的干扰。dmesg看内核日志尤其是OOM killer、磁盘IO错误、硬件异常、进程被杀这类记录。内核日志在排障里经常是“破案关键”。date确认时间配合应用日志去对时间线。这里想特别强调一点先看dmesg的优先级经常比top还要高。有一次K8s节点报NotReady我跑过去先top发现CPU不高、内存也够绕了半天后来才想起看dmesg发现是内核报了很多hung_task_timeout_secs说明节点上有进程在不可中断的D状态访问存储节点层面的健康检查自然就挂了。1.3 用load average快速判断负载形态查看uptime输出时不要只看数字大小要看1分钟、5分钟、15分钟的走势load average: 23.45, 18.32, 12.67如果1分钟负载明显高于15分钟说明负载是正在快速拉高大概率是瞬时任务或流量突刺如果三个值都比较平均且高说明系统已经在高负载下运行了一段时间如果1分钟低、15分钟高说明高峰期可能刚过目前正在回落。很多同学误以为load average只表示CPU占用实际上它统计的是处在可运行状态和不可中断睡眠状态的进程数。也就是说当大量进程阻塞在磁盘IO上时CPU可能空闲但负载依然很高。这一点是判断CPU问题还是IO问题的第一道关卡。具体判断方法可以配合top看us、sy、wa三个值us高、wa低基本可以认为是计算密集型问题。wa高、us低大概率是存储IO在拖后腿。sy高可能是系统调用频繁、锁竞争或者上下文切换太厉害。2. 系统级指标逐层拆解CPU、内存、磁盘与文件句柄框定大概方向后就要逐项拆解资源指标。上一节说到了看负载这节把几个最常出问题的资源层面展开细讲。2.1 CPU飙高的快速定位思路CPU告警是Linux排障里出现频率最高的一类。先跑top按P把进程按CPU使用率排序找到最靠前的那个进程PID。注意top里的CPU数值是动态刷新的如果进程忽高忽低需要看几秒再判断。必要时按1看每个CPU核心的使用情况避免只看到整体平均。确定进程后用top -Hp PID查看该进程下所有线程的CPU占用找到CPU占用最高的线程TID然后转成十六进制printf %x\n TID如果进程是Java应用就直接用jstack PID thread_dump.txt导线程快照再到文件里搜线程ID对应的nid注意nid是十六进制。如果进程是C/Go这类程序可以用gdb或用perf top -p PID看热点函数。在真实案例里我遇到过一次Java服务CPU狂飙top里看到的是GC线程占满了CPU这说明JVM在做频繁Full GC。先用jstat -gcutil PID 1000看一眼如果Old区都满了说明内存里堆积了大量不可回收对象这时候单纯加CPU没有意义要查对象是谁创建的用jmap -histo抛出来看实例数量最多的类。还有一点要提醒公司内部如果装了各种Agent监控、日志采集、安全加固组件CPU飙高时也别忘了看一眼是否有Agent自身异常。我遇到过Agent疯狂扫描文件导致CPU超过100%杀掉后业务一切正常。这种问题排查起来最烦人所以现在我会在告警定位阶段就把进程的启动命令和所属用户一起查出来ps -ef | awk $2PID{print}通过启动参数能快速判断是业务进程、中间件进程还是Agent进程。2.2 内存告警与OOM排查内存告警的严重程度往往高于CPU因为它可能导致进程被OOM Killer杀掉。Linux的内存机制比较特殊进程申请的内存不一定会立刻占用物理内存只有在真正写入时才触发缺页中断分配物理页。所以看内存要用两套思路第一套看系统整体free -h查看available而不是只看free。available是估算的可用于新进程的内存包含可回收的page cache、slab等。如果available所剩无几系统就可能开始swap。第二套看进程详情top -o %MEM ps aux --sort-rss | head -20RSS是常驻物理内存但要注意它包含共享内存库多个进程共享的同一个so文件会重复计入每个进程的RSS所以用RSS估算实际占用只能当参考。如果系统已经发生过OOMdmesg里会留下类似Out of memory: Killed process的记录。带上进程名和PID之后去查这个进程当时为什么吃那么多内存。如果Java应用内存飞涨我会用jmap -heap PID看堆配置和当前使用率再用jstat -gcutil看GC曲线。还有一个容易被忽略的点Java使用堆外内存DirectBuffer、线程栈、JNI等通过top看到的RSS远大于JVM堆内存配置这时要去查堆外是否泄漏。顺带说说swap。Linux swap分区耗尽本身不会直接报错但一旦系统开始使用swap性能会断崖式下降。排查时需要留意vmstat里si、so两列vmstat 1 10如果si和so持续大于0说明内存压力很大系统正在频繁换页。这种情况下内存型告警往往已经进入“质变”阶段要尽快释放内存或者对进程做隔离。2.3 磁盘IO与inode压力排查磁盘告警不单指“空间不足”还包含“IO性能下降”和“文件系统元数据耗尽”。空间不足是最直观的df -h du -x --max-depth1 / | sort -rh | head -20注意df -h看的是文件系统使用率有时候通过lsof | grep deleted能找到已经被删除但仍被进程占用的文件这类文件不会因为rm就释放空间必须重启相关进程或服务才能真正释放。IO性能问题要用iostat看iostat -x 1 5重点看%util、await、svctm。如果%util接近100%说明磁盘设备已经到达瓶颈。如果%util不高但await很高可能存在大量IO排队或者磁盘链路有问题比如磁盘坏道、RAID重建、云盘限流。另外对于机械硬盘还要关注rrqm/s和wrqm/s它们表示合并读写的请求数量。如果合并太少说明IO模式偏随机对机械盘非常不友好。日志服务、数据库这类应用如果随机写入频繁一般建议使用SSD或者在软件层做缓冲合并。inode耗尽是个阴间问题。有时候df -h还有空间但应用就是报“No space left on device”很可能是inode满了df -i排查哪个目录的文件数量过多时可以用for i in /*; do echo $i; find $i -xdev | wc -l; done一旦发现某个目录下小文件数量异常多半是日志没轮转、临时文件没有清理或者是某些消息中间件在消费端积压产生了大量数据文件。2.4 文件句柄数不足的隐性故障进程打开的文件句柄超过上限时通常会报Too many open files。这个问题在连接型应用里特别常见尤其是Java应用、Nginx、Redis等。排查思路分两层# 查看进程当前句柄数 ls /proc/PID/fd | wc -l # 查看进程句柄上限 cat /proc/PID/limits如果进程实际句柄数接近上限就看看这些fd都指向什么ls -l /proc/PID/fd | grep deleted如果大量指向socket:说明是网络连接没释放如果大量指向deleted文件说明文件被删除但进程没关闭fd通常是日志框架持有文件句柄过多。系统层面的fs.file-max也要检查但大多数业务场景下瓶颈都在进程自己的nofile限制改ulimit即可注意要改到位并重新启动进程才能生效。3. 应用告警、K8s告警与日志的协同分析有些告警来自云平台或监控系统指标表现也很明显但根因不在系统资源层而在应用容器、服务发现、端口状态这些方面。这一节专门讲应用层和容器层的排障思路。3.1 Java进程告警确认线程状态与瓶颈Java应用出现告警的时候第一反应应该是拿线程快照而不是重启。别急着重启先留证据。# 连续拿两次间隔10秒便于比对线程变化 jstack PID /tmp/jstack_1.txt sleep 10 jstack PID /tmp/jstack_2.txt看线程快照时重点关注java.lang.Thread.State: RUNNABLE占比高可能一直在执行计算。BLOCKED或WAITING数量多可能存在锁竞争、线程池耗尽。频繁看到同一个业务方法栈说明热点集中。有一次线上服务偶发超时CPU不高但线程快照里大量线程阻塞在数据库连接获取上。顺着调用链查下去发现是数据库连接池被打满原因是最底层的一个第三方接口变慢持有的连接迟迟不归还导致上层线程全部排队。如果当时只盯着top看CPU必然找不到真相。Java内存方面别忘了堆外内存的问题。进程的RSS可能远超Xmx设置。如果不确定是不是堆外内存占用可以观察/proc/PID/status里的VmRSS和GC日志、jstat的堆使用情况对比如果堆占用很低但RSS很高就要查堆外。3.2 K8s场景的告警排查顺序K8s里很多告警最终落到“Pod重启”、“节点NotReady”、“CrashLoopBackOff”第一反应不是登录节点跑top而是先看K8s层面的状态kubectl get nodes -o wide kubectl describe node node kubectl get pods -A -o wide | grep -i Error看Pod状态时需要区分几种情况CrashLoopBackOff启动后立即崩溃进程反复重启。Pending调度不成功可能是资源不足或亲和性不满足。ImagePullBackOff镜像拉取失败最常见的是版本写错或镜像仓库认证失效。OOMKilled容器内存超过limit被cgroup杀掉。Evicted节点资源压力触发驱逐通常是内存或磁盘压力过高。定位到问题Pod后用kubectl logs看业务日志用kubectl describe pod pod看事件列表。如果容器已经重启要加--previous参数看上一次容器的日志否则你看到的只是最后一次启动后的输出容易漏掉崩溃前的关键报错。在K8s中还有一个非常容易踩坑的点容器内看到的系统指标和宿主机看到的系统指标不是一回事。容器内的free看到的是宿主机的全局内存可能被cgroup限制视图影响但/sys/fs/cgroup/memory/memory.limit_in_bytes才是容器真实的内存上限。排查容器OOM时不要只看容器内free要看cgroup的限制和当前使用量cat /sys/fs/cgroup/memory/memory.current cat /sys/fs/cgroup/memory/memory.limit_in_bytes3.3 网络断开、连接堆积与端口异常网络类告警出现的频率也很高但很多网络问题的根子其实在应用层。排查时我习惯按“连通性、丢包、端口监听、连接队列、握手/重传”的顺序走下来。第一步连通性与丢包ping -c 10 target看丢包率和延迟。如果是跨机房跨云还需要结合mtr来看哪一跳有丢包。注意很多云厂商的防火墙会限制ICMPping不通不代表TCP不通反之亦然。第二步端口监听状态ss -lntp看目标端口是否处于监听状态以及监听在什么地址上。如果你发现服务监听在127.0.0.1而不是0.0.0.0外部网络自然访问不到。第三步TCP连接数ss -ant state established | wc -l ss -s以运维经验来看大量TIME_WAIT连接通常不是大问题但如果SYN_RECV或ESTABLISHED数量持续异常高就要进一步排查了。第四步抓包确认tcpdump -i eth0 host target and port 8080 -w /tmp/cap.pcap抓包是终极手段但也要有目的性。比如客户端报超时服务端却看不到请求进来那问题大概率在中间链路或负载均衡层如果服务端看到了请求但响应很慢就要回头查应用逻辑和线程池了。我印象很深的一次案例某次服务间歇性不可用Socket连接大量CLOSE_WAIT。CLOSE_WAIT意味着对端已关闭连接但本地进程没有主动close一般是因为应用代码没有正确释放连接。顺着ss -antp找到持有这些连接的进程再结合Java线程栈排查最终定位到HTTP客户端连接池的返回连接没有关闭积少成多导致线程一直阻塞。这类网络告警纯粹靠调内核参数没用必须改代码。4. 常用命令组合与问题速查表如果说前面的内容是“招法”那么这一节就是帮你把招法串成一套完整的套路。很多人在故障现场容易大脑空白这是因为缺少一套肌肉记忆式的执行顺序。4.1 一套五步骤快速检查顺序我自己在排障现场会把下面的顺序背下来形成条件反射第1步看负载与内核日志。uptime dmesg -T | tail -100dmesg -T可以把内核日志时间转成可读格式方便和业务告警时间比对。重点关注Out of memory、hung_task、blocked for more than 120 seconds、I/O error等关键字。第2步看资源占用TOP进程。top -c -b -n 1 | head -30 ps -eo pid,ppid,%cpu,%mem,cmd --sort-%cpu | head -20top -c能显示进程的完整命令行而不仅仅是进程名这对快速识别Jar包、Python脚本、Agent特别有效。第3步看IO和内存换页。iostat -x 1 5 vmstat 1 5vmstat输出里r列表示正在运行和等待CPU的进程数如果这个值一直大于CPU核数说明CPU确实饱和了。b列表示不可中断睡眠状态的进程数长期大于0通常指向IO阻塞。第4步看进程级详细状态。cat /proc/PID/status ls -l /proc/PID/fd | wc -l cat /proc/PID/limits这一套能把进程的内存、线程数、文件句柄数、资源限制都查到适合深入单个进程。第5步结合监控平台看历史曲线。登录自己的Zabbix/Prometheus/Grafana看指标是从什么时候开始异常的异常之前有没有发布变更、有没有定时任务执行。如果没有监控平台至少要养成保留top和dmesg快照的习惯很多问题不是当时能定位的回头看快照反而能发现线索。4.2 资源突发问题速查表平时处理的告警五花八门我把最常见的几类现象和对应排查命令整理成了速查表可以直接抄作业。告警现象高概率原因优先排查命令CPU单核跑满线程死循环或热点函数top -Hp, perf top, jstackCPU整体高但us低wa高磁盘IO瓶颈iostat -x 1, pidstat -d内存持续增长不回收内存泄漏或GC失败jstat -gcutil, jmap -histo系统负载高但CPU不高D状态进程阻塞在IOvmstat 1, cat /proc/ /statusNo space left文件系统满或inode满df -h, df -iToo many open files连接泄漏或fd泄漏ls /proc/ /fd, ss -antp端口不通未监听/防火墙/iptablesss -lntp, iptables -L -n大量CLOSE_WAIT应用未正常关闭连接ss -antp, jstackK8s Pod OOMKilled容器超限/Limit配置过小kubectl describe pod, cat cgroup文件节点NotReady节点负载高或D进程阻塞dmesg, kubectl describe node这个表只是方向真正定位时还要结合自己的业务场景做交叉验证。例如出现CLOSE_WAIT时同时看一下监控里的线程数如果有大量线程阻塞就能进一步验证是连接未释放导致应用线程被打满。4.3 如何让告警本身更“可排查”从源头规避一部分告警噪声也非常重要。我见过很多团队被毫无区分度的告警折腾得筋疲力尽一天到晚都在处理“CPU80%”这种连业务影响都说不清楚的告警。成熟的监控策略应该区分“业务可用性告警”和“资源风险告警”并设置不同的响应级别。对于Linux主机我建议重点监控以下指标而不是把所有指标都拉出来告警负载相对值对比CPU核数而不是固定值。CPU使用率区分us/sy/wa。内存可用量被系统OOM的时间点。磁盘空间和inode使用率设置两级阈值如80%warning、90%critical。文件句柄使用率。TCP连接状态中ESTABLISHED和CLOSE_WAIT数量。同时告警内容最好带上主机角色、最近变更记录链接、负责人信息。故障来的时候最有价值的不是“CPU高”这三个字而是“哪个业务模块、什么时间开始的、最近改了什么”。这比事到临头再查要高效得多。5. 实战复盘一次Java服务CPU飙高的完整排查记录前面的内容偏方法这一节我拿一次真实的故障来串一遍。从中可以看到方法不难难的是在紧张状态下不乱节奏。5.1 一次内存泄漏引发的Full GC风暴某天凌晨监控突然报警某核心Java服务的CPU使用率冲到700%接口响应从20ms涨到5秒。我先执行了基础三连确认是Java进程占满多核不是Agent或系统进程。接着top -Hp PID看到大量GC线程CPU使用率极高随后jstat -gcutil PID 1000确认FGC次数和耗时都在猛涨Old区使用率接近100%。这时候基本可以锁定方向内存里对象堆积触发频繁Full GCGC线程把CPU占满。用jmap -histo:live PID | head -30查看存活对象结果发现一个内部缓存类的实例占据了海量内存。顺着代码排查发现这个缓存的key没有设置过期而且每次请求都会往里写新数据时间一长就撑爆了Old区。换句话说这不是简单的“缓存没清”而是代码设计上把缓存当成了无限存储。这次排障给我最大的教训是CPU告警不一定真的是“计算变多”很有可能是GC或锁导致的自旋。如果一开始就盲目地加机器、加CPU问题只会被短暂掩盖过几个小时还会重现。5.2 踩过的几个坑写出来给你避雷踩坑一重启解决一切但丢掉了现场。有一次线上容器反复重启同事图省事直接delete Pod让它重新调度。结果新Pod起在别的节点后问题依旧但旧Pod的日志和事件已经全部丢失只能通过监控曲线猜原因。现在我的原则是除非服务完全不可用必须先保留现场至少要把进程的thread dump、堆内存快照、内核日志保存下来再操作。踩坑二看监控只盯平均值。监控面板上CPU平均值可能只有60%但实际某个核心已经100%打满。多核场景下只要有一个线程把某个核跑满了应用的整体RT就会变差。排查时一定要看每核使用率不要只看整体。踩坑三K8s里执行命令容易下意识看宿主机的top。有一次排查某个Pod CPU飙升我先跳上节点敲top半天没找到可疑进程。后来才想起这个节点上有很多PodCPU使用率会聚合而且相互干扰。正确的做法是用kubectl exec进入容器跑top或者根据cgroup路径找到对应PIDcat /proc/PID/cgroup再结合kubectl logs、kubectl describe pod去确认。5.3 沉淀可复用的SOP比英雄救火更重要排障能力不是靠一次两次“力挽狂澜”练成的而是靠一次次复盘沉淀成团队知识库。每处理完一个告警我都会花10分钟整理一份简短的记录包含故障发生时间、告警内容、影响范围。排查时间线每一步看了什么命令、得到了什么结论。根因分析和临时规避方案、长期修复方案。对监控告警策略的改进点。同时把高频排查命令整理成shell脚本或者Markdown手册放在团队Wiki里新同事遇到类似问题可以直接照着走。比如公司里已经有一个“Linux应急排查脚本”一句话就能输出系统关键概览echo UPTIME; uptime; echo LOAD; cat /proc/loadavg; echo MEM; free -h; echo DISK; df -h; echo TOP CPU; ps -eo pid,ppid,%cpu,%mem,cmd --sort-%cpu | head -15; echo TOP MEM; ps -eo pid,ppid,%cpu,%mem,cmd --sort-%mem | head -15; echo KERNEL LOG; dmesg -T | tail -100这类脚本的价值在于人一紧张就会漏命令脚本可以保证每次排查起点一致。6. 给新人的几条实战提醒文章最后聊几个不一定写进教科书、但实战中特别重要的判断。第一不要做“工具人式排查”。很多人习惯把所有工具跑一遍但不知道拿结果去验证假设。看到top里进程CPU高你下一步要问的不是“还有什么命令可以看”而是“这个进程为什么CPU高”。先假设一个原因再用命令去验证它比漫无目的地敲命令高效得多。第二时间线非常关键。看到告警后先把date记下来把监控曲线异常开始的时间点找到然后对照有没有发布记录、有没有定时任务调度。至少三成故障的根因都能从“这个时间点我改了什么”里找到线索。第三告警分级和值班机制要提前定好策略。不是所有告警都需要半夜爬起来处理。如果业务是单节点、没有高可用CPU再高你也只能起来处理如果业务多副本且流量可控完全可以先观察一段时间再决定是否介入。把“告警是否影响用户”放在判断优先级的第一位能有效避免无意义的熬夜。第四平时多做演练。系统没有故障的时候可以在测试环境主动制造故障把磁盘写满、用stress工具拉高CPU、模拟kill掉关键进程再让团队按SOP做一轮排查。演练过一次和完全没演练过到真实故障现场的心态完全不一样。排查的过程本质上是在“混乱中建立秩序”。不管告警多吓人有一套固定的行动顺序手里有命令脑中有假设就不会被带着跑偏。希望你下一次遇到告警的时候不是盯着屏幕发呆而是能安静地敲下第一行命令然后把问题一步步逼到死角。