深夜手机震到床头柜嗡嗡响,监控大屏一片飘红,CPU、内存、磁盘、IO全在告警,群里@消息刷了几百条——这种场景,搞过运维的都懂。越是这种时候越不能慌,更忌讳凭感觉瞎敲命令。Linux故障排查看着是命令积累,本质上是一套有顺序、有逻辑的应对流程:先看什么、再查什么、怎么缩小范围、怎么确认恢复,每一步都有讲究。这篇文章就是把我这些年半夜爬起来处理告警的完整思路整理成一张“作战地图”,从接到告警到定位根因,再到后续治理,一条线讲清楚,适合运维、SRE、后端开发和对Linux排查感兴趣的朋友参考。
1. 告警刷屏后的前十分钟:先定级,再动手
1.1 告警洪峰不是“全都要修”,是“先抓根因”
告警轰炸的时候,最典型的错误就是被带着走。磁盘报警去清磁盘,CPU报警去看进程,内存报警去杀进程,结果搞了半天发现本质上是同一个问题:比如某应用疯狂写日志,同时把磁盘、IO、CPU全打满了。你单独修每一项都是在陪跑,只有找到那个源头进程,所有告警才会一起消失。
所以告警刷屏后的第一件事不是执行命令,而是做“告警归类”。把当前触发的告警按指标维度分组:哪些是CPU相关、哪些是内存相关、哪些是磁盘和IO相关、哪些是网络相关、哪些是应用进程相关。分完组之后,再找“公共交集”——比如某个应用在这段时间部署了新版本,同时所有告警指标都异常,那大概率就是这次变更引起的。这个判断不需要立刻验证,但心里要先有一个假设,后面所有排查都围绕验证这个假设展开。
1.2 30秒信息快照:把告警转成排查线索
告警平台上的告警信息通常包含主机IP、指标名、触发时间、当前值、持续时间,这些信息在慌乱中最容易被忽略,但恰恰是排查的第一手输入。我建议在动手之前,先手工记录或截屏保存以下信息:
- 告警主机和IP,确认是不是自己负责的那批机器;
- 各指标当前的具体数值和阈值,比如CPU是99%触发的还是85%触发的,区别很大;
- 告警首次触发时间,这个时间点前后是否有变更、发布、定时任务;
- 告警持续时间,是持续了半个小时还是刚刚才触发。
这些信息加起来不会超过30秒,但能帮你快速判断问题的性质。比如持续时间“刚刚触发”和“已经持续一小时”,排查优先级完全不一样。前者可能是瞬时抖动,后者则意味着系统已经在异常状态下运行很久,需要尽快处理。
1.3 排查环境的准备:别让工具拖你后腿
深夜排查最怕什么?最怕SSH连不上、命令没权限、工具没安装。我个人的建议是提前把常用排查工具装好,别等到告警来了才想起来补。最少要保证每台服务器上有这些基础工具:top或htop、vmstat、iostat、netstat或ss、lsof、strace、tcpdump、iftop或nload。如果是CentOS/RHEL系,用yum install sysstat net-tools lsof strace tcpdump iftop就能覆盖大部分;Debian/Ubuntu系用apt装同样的包。
另外,建议提前确认一下监控平台里能查到多细的历史数据。深夜排查时,历史趋势图往往比当前时刻的快照更有说服力。比如内存告警,如果趋势图显示过去一周内存占用缓慢爬升,那是泄漏类问题;如果显示半小时内从30%跳到95%,多半是流量突增或某进程异常申请内存。两种情况的处理方向完全不同。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主机基本面体检:CPU、内存、负载的快速抓取
2.1 uptime和vmstat:先看全局再看局部
接通服务器后,我先敲的永远是uptime和vmstat 1 5,原因很简单:这两条命令能在10秒内给出这台机器当前的整体健康画像。
uptime输出里的三个负载值分别代表1分钟、5分钟、15分钟的平均负载。判断负载是否异常的基准不是看数字大小,而是看它和CPU核数的关系。比如一台8核机器,负载跑到16,说明每个核平均排队了2个任务,明显过载;但如果负载是2,即使报警阈值配置得低,也不用太担心,因为距离饱和还有很大余量。
vmstat 1 5更有意思,它每秒打印一行,连续显示5次。重点看这几列:
r:运行队列中的进程数,持续大于CPU核数说明CPU饱和;b:处于不可中断睡眠状态(通常是等待IO)的进程数,持续大于0说明IO有问题;si和so:交换分区换入换出量,不等于0说明物理内存不够用,系统在拿磁盘当内存,性能会断崖式下降;wa:CPU等待IO的时间占比,这个数值高通常意味着磁盘响应慢,可能是磁盘硬件故障,也可能是IO请求太多。
这里有个容易踩的坑:vmstat的wa高分析起来要结合b列一起看。如果wa高但b也高,说明大量进程在等IO,磁盘是瓶颈;如果wa高但b不高,有可能是CPU本身计算量就大,等待只是表象,要看r列确认。
2.2 free命令的隐藏信息:关注available而不是free
刚入行的时候看内存,我只看free那一列,总以为内存快没了。后来才知道Linux的内存管理机制里,内存被用来做缓存是常态,真正需要关注的是available这一列,它表示在不触发交换的情况下,还能分配给应用程序的内存估算值。
排查内存告警时,我按这个顺序来看:
bash复制free -h
看三行数据:Mem行、Swap行、以及缓冲区缓存的使用。这里有个常见的陷阱:buff/cache占了很多内存不代表内存不够用,因为内核在应用申请内存时会自动回收缓存。真正的风险指标是available一直往下掉,并且Swap的used持续增长。如果available只剩不到总内存的10%,同时swap已经在用了,说明物理内存已经严重吃紧,需要马上找出内存大户。
找内存大户用top然后按M键排序,看RES列。RES是进程实际占用的物理内存,比VIRT(虚拟内存)更有参考价值。还有一个容易被忽略的点:有时候top里看不到明显的大进程,但内存就是不够,这种情况多半是内核slab缓存异常增长或者tmpfs占用了内存,可以用slabtop看看内核内存,用df -h查看/dev/shm这类tmpfs文件系统的使用量。
2.3 top命令的第一屏怎么读
top命令第一屏的信息密度很高,但很多人只是扫一眼就往下翻进程列表。其实第一屏里藏着判断全局状态的关键:
load average,同uptime;%Cpu(s)这一行的us(用户态)、sy(内核态)、wa(等待IO)、id(空闲)、st(被虚拟机管理程序偷走的时间)。如果是云服务器,st高说明宿主机资源争抢严重,这时候别在系统内部死磕,赶紧排查宿主机或迁移实例才是正事;KiB Mem和KiB Swap的total、free、used、buff/cache,看内存总况;Tasks行里的running数量和sleeping数量,结合后面进程列表的S状态列一起看。
我在实战中会先看这几个指标是否指向同一个结论:如果%Cpu(s)里的us高,说明业务进程在跑,去看进程列表;如果sy高,说明系统调用频繁,可能是网络连接风暴或者频繁的文件读写;如果wa高,直接跳到磁盘排查;如果st高,那就不是我们能通过杀进程解决的了。
2.4 实战:负载高但CPU不高,问题藏在D状态进程里
分享一个我踩过的典型场景:某台数据库服务器告警负载飙升到30,但top里看CPU空闲还有80%,这看起来很不合常理。后来执行vmstat 1 5,发现b列一直是3到5,也就是说有3到5个进程卡在不可中断睡眠状态。
不可中断睡眠(D状态)意味着进程在等内核态IO完成,而这种状态下进程既不能被杀掉也无法退出。用ps aux查看进程状态为D的进程,逐个排查它们在访问什么文件或设备:
bash复制ps -eo pid,ppid,stat,cmd --sort=-pcpu | grep ' D '
排查下来发现是一块通过USB外接的备份硬盘响应超时,导致所有写该硬盘的进程全进入D状态。拔掉这块硬盘后负载立刻恢复正常。这个案例就是典型的“CPU不高但负载爆高”,核心判断依据就是vmstat的b列。以后再遇到类似告警,我会第一时间看是不是有D状态进程,少走很多弯路。
3. 磁盘和IO排查:空间、inode、占用进程一个不能少
3.1 df和du的经典误判:磁盘满但找不到大文件
磁盘告警在所有告警里算是最常见的,但很多人在“磁盘明明满了却删不掉、找不到大文件”这件事上卡很久。先说一个我自己的经验:磁盘告警后的第一步,永远是用df -hT确认哪个分区满了、文件系统类型是什么,然后才用du去找大文件。
du找大文件有个坑:它会遍历目录并计算文件大小,但如果你在一个挂载点特别大的目录上执行du -sh *,可能迟迟不出结果;更麻烦的是,有些文件已经被删除,但仍有进程持有文件句柄,du根本看不到它们,而空间就是被这些“幽灵文件”占着。
遇到这种情况,用lsof查已删除但未释放的文件:
bash复制lsof +L1
+L1表示显示link count小于1的文件,也就是已经被删除但还被进程打开着的文件。查出来后,如果那个进程是可以重启的,重启后空间立刻释放;如果是核心应用不能重启,就需要考虑别的方案,比如先把进程日志文件清空而不是删除。
3.2 文件句柄与inode耗尽
磁盘空间正常但报磁盘满,这种情况我至少遇到过三次,原因清一色是inode耗尽。df -h显示剩余空间充足,但df -i显示/分区已用100%。inode是文件系统用来记录文件元信息的索引节点,每个文件或目录都要占一个,小文件特别多时容易先把inode消耗光。
排查思路是先定位哪个目录下小文件最多:
bash复制for dir in /var /tmp /home /opt; do
echo "目录 $dir 下的文件数:"
find $dir -xdev -type f 2>/dev/null | wc -l
done
找到目录后,再用find结合-mtime参数批量清理旧文件,或者优化应用的日志策略,防止小文件无限累积。另外,文件句柄耗尽和inode耗尽不同,但它同样表现为“磁盘空间充足但应用创建文件失败”,排查方法是用cat /proc/sys/fs/file-nr确认当前系统级句柄数是否接近上限,然后通过ulimit -a查看进程级的限制。这一项不归磁盘管,但症状和磁盘相关,放在一起排查更高效。
3.3 IO打满判断:iostat和iotop配合
磁盘IO打满比磁盘空间满更隐蔽,因为空间满了会立即可见,而IO慢很多时候被误判成“服务器卡了”。判断IO是否打满,我用iostat看现场,用iotop看进程。
bash复制iostat -x 1 5
重点看这几列:
%util:设备繁忙程度,持续接近100%说明设备被持续压着打;await:IO请求平均处理时间,包含排队时间,高说明请求在排队;svctm:IO请求实际处理时间,如果await远大于svctm,说明大量请求在排队;r/s和w/s:每秒读写次数,配合rkB/s和wkB/s判断是大量小IO还是少量大IO。
定位到设备后,再用iotop -o只看有IO活动的进程:
bash复制iotop -o -P
-o只显示正在发生IO的进程,-P显示进程而不是线程。实际操作中我发现,很多IO告警其实来自同步写日志,优化方式是把日志改到异步写入,或者把日志挂载到单独的磁盘上,避免和业务数据抢IO。
3.4 案例复盘:vsphere证书状态告警,根因其实是磁盘日志爆掉
有次收到一条vsphere证书状态告警,初看以为是证书到期或证书服务异常,但登录后台检查发现,vcenter目录所在分区磁盘占用100%,/var/log下的日志文件已经把分区塞满。因为日志写不进去,证书相关的服务无法正常读写状态文件,才触发了证书告警。清掉旧日志、恢复磁盘空间后,证书告警自动消失了。
这个案例给我最大的启发是:告警触发的原因未必和告警名称直接一致,不要被告警标题带偏。证书告警可能来自磁盘、网络或时间同步,数据库告警可能来自磁盘IO,应用超时告警可能来自网络丢包。排查的思路永远是从底层基础资源开始逐层向上排查,而不是看到什么告警就只查什么组件。这也正是“作战地图”的意义所在:按既定顺序排查,永远比零散地东敲一榔头西敲一棒子快得多。
4. 网络层连接排查:端口、连接状态、带宽一起看
4.1 ss系列命令:比netstat更高效的网络排查
网络类告警在深夜同样高频,尤其是连接数暴涨或者端口不通。已经2024年了,我强烈建议不要再用netstat,改用ss命令,同样是输出连接信息,ss比netstat快得多,在连接数上万的服务器上体验差异尤其明显。
bash复制ss -lntp
这行命令列出所有监听中的TCP端口和对应的进程。看有没有预期外的端口在监听,有没有已知服务的端口没起来。排查端口不通时,我一般会按这个顺序走:先看端口有没有监听,如果有,再用curl或telnet从本机访问测试,如果本机通但外部不通,检查防火墙:
bash复制iptables -L -n
注意现在很多系统用的是firewalld,规则要看firewall-cmd --list-all。还有一个容易被忽略的坑:云服务器的安全组规则也要一起排查,很多时候系统内部一切正常,但云控制台上的安全组没有放行端口,外部永远连不进来。
4.2 TIME_WAIT与CLOSE_WAIT:两种“卡死”症状
连接数类的告警,常见的两个极端症状是TIME_WAIT堆积和CLOSE_WAIT堆积,前者影响性能,后者通常代表代码有bug。
TIME_WAIT多,通常出现在高并发的短连接场景。它的存在本身是TCP协议为了保证可靠性和防止旧连接干扰新连接,但如果TIME_WAIT数量巨大,会占用大量端口和内存。处理手段有几种:开启net.ipv4.tcp_tw_reuse允许复用TIME_WAIT连接;调整net.ipv4.ip_local_port_range扩大端口范围;更推荐的做法是从应用层面优化,改用连接池或长连接,减少频繁建连拆连。
CLOSE_WAIT堆积则是另一个故事。CLOSE_WAIT表示对端已经关闭了连接,但本端应用没有调用close关闭自己的socket。出现大量CLOSE_WAIT,基本可以认定是应用代码里连接没有被正确关闭。用ss -ant | awk '{print $1}' | sort | uniq -c | sort -nr统计连接状态分布,如果CLOSE_WAIT上万,直接去查应用代码里处理连接关闭的逻辑,而不是调内核参数,因为内核参数对这种问题绝大多数情况下无效。
4.3 带宽打满和延迟排查工具
带宽打满的告警,排查工具我用iftop或nload。iftop -i eth0从输出里能直接看到哪个IP和哪个端口占用了最多带宽,瞬间定位到是某个业务在大量传输数据,还是有人在跑下载任务。
延迟和丢包类的网络问题,ping和mtr组合使用。ping验证延迟和丢包是否存在,mtr则能逐跳显示从本机到目标IP之间的每一跳情况:
bash复制mtr -rwzc 100 目标IP
-r报告模式、-w宽屏输出、-z显示AS号、-c 100发100个包。如果丢包集中出现在最后几跳,说明是目标服务器或机房问题;如果丢包出现在中间某跳,那就要联系对应网络运营商排查链路质量了。这个工具在平时“用户反馈慢”的工单里也是神器,能快速确定瓶颈发生在本地机房、运营商骨干网还是目标服务器所在机房。
5. 深入应用进程:从进程行为到日志证据链
5.1 ps的排序与进程父子关系
基础资源排查完,如果还没找到根因,就需要下钻到应用层。第一件事是用ps按资源占用排序,找出谁是“罪魁祸首”:
bash复制ps -eo pid,ppid,%cpu,%mem,stat,etime,cmd --sort=-%cpu | head -20
看重CPU最高的进程,同时看它的etime(运行时长)和父进程PID。为什么要看父进程?因为很多时候CPU高的只是子进程,真正的调度逻辑在父进程里。比如某个Java应用的GC线程占用CPU高,问题是JVM参数配置不合理,如果只盯着PID杀进程,过一会它自动重启后又会继续报警。
5.2 /proc目录:每个进程的“体检报告”
Linux的/proc虚拟文件系统是个宝藏,排查进程细节时,比任何外部工具都直接。几个我常用的文件:
bash复制cat /proc/PID/status
cat /proc/PID/io
cat /proc/PID/fd | wc -l
cat /proc/PID/cmdline
status里有进程的状态、内存、线程数等信息,能快速看进程是否处于异常状态;io目录下有进程的读写字节数,能判断是不是IO大户;fd目录下的fd数量能看进程总共打开了多少文件描述符,如果这个数字异常巨大,那很可能存在文件描述符泄漏。配合lsof -p PID能直接看到这个进程打开了哪些文件、哪些socket,很多连接泄漏、文件占用问题在这一步就能定位。
5.3 Java应用线程分析:从CPU占用到jstack
如果告警对象是Java应用,top里看到某个Java进程CPU飙高,但Java进程内部发生了什么,top是看不出来的。这时候需要两步走:先用top -H -p PID找到CPU占用最高的线程PID,再把这个PID转成十六进制:
bash复制printf '%x\n' 线程PID
然后用jstack输出线程快照,在快照里搜索对应的十六进制线程号:
bash复制jstack PID | grep -A 50 十六进制线程号
通常能看到该线程卡在什么方法上,是GC频繁运行、锁竞争,还是某个方法的死循环。这套方法对线上问题定位极其有效,我处理过的几次Java应用告警,靠这一招直接找到问题代码的概率非常高。顺带提一个从热词里看到的“java告警raw use param”,意思是Java代码里直接使用原始参数而没有做参数校验,这在故障场景里经常表现为传入非法值导致异常分支跑偏,排查时也要注意检查代码的入参校验逻辑,必要时先临时做参数合法范围限制。
5.4 日志检索:时间窗口内的证据拼图
进程层面的信息拿到后,最后一块拼图是日志。告警触发前的那段时间窗口里,应用日志和系统日志往往是定位根因的最有力证据。
系统日志统一用journalctl,配合-u指定服务名、--since和--until限定时间范围,先看指定时间窗内有没有OOM、segfault、文件系统错误等关键信息:
bash复制journalctl --since "2024-05-20 23:00" --until "2024-05-21 01:00" -p err
-p err只看错误级别以上的日志,避免被无关信息淹没。应用日志则要看应用自己定义的日志路径,检索的关键词通常是异常类名、错误码、线程名。日志分析的正确姿势是先看时间窗口,再看关键词,最后按时间线把所有异常事件排起来。我见过很多人一上来就在整个日志文件里grep错误关键词,抓到几个Error就以为找到根因了,实际上那些Error可能已经是故障发生之后的次生异常。
比如有一次排查接口超时告警,应用日志里报的是数据库连接池获取超时,但继续往前翻,发现是因为连接池已经被慢查询打满,而慢查询的根源是一条没有索引的新上线的SQL。只看“获取连接超时”这个错,可能就去盲目调大连接池参数了,结果只是治标不治本。日志排查一定要有“证据链”意识,从触发源头到最终表象串起来,才能不被中间环节误导。
6. 告警治理与沉淀:让“炸裂”变成“常规”
6.1 告警屏蔽为什么经常不生效
告警多了之后,有人会想到屏蔽告警,但屏蔽不生效是另一类常见问题。热词里有个“flashduty告警屏蔽不生效”,这类问题的根因通常不是屏蔽功能坏掉了,而是告警匹配的维度没对。屏蔽规则一般按主机、监控项、容器标签等条件匹配,如果真实的告警事件里主机名带了域名后缀,而你屏蔽规则里只写了简写主机名,那就匹配不上。
排查屏蔽不生效的思路是:在告警平台里调出这条告警事件的完整标签,逐一对照屏蔽规则的匹配条件,看是标签对不上、时间窗口设错,还是屏蔽范围包含关系写反了。另外,很多告警屏蔽是针对“事件触发”的,但如果告警已经处于“持续中”状态,新配置的屏蔽规则不会自动作用于已触发的告警,要么等它恢复后重新触发,要么手动处理当前告警。这点非常容易踩坑。
6.2 zabbix告警确认与关闭的常见坑
zabbix是目前用的比较广的监控系统,围绕告警确认和关闭的疑问也很多。zabbix 7.0版本的告警“手动确认关闭”机制,6.0及之前版本叫“Acknowledge”,7.0里改成了“Manual close”,本质都是通过确认操作来标记问题已处理。如果发现确认按钮点了没反应,先看当前登录用户有没有操作权限,再看问题状态是否已经是“已解决”,已解决的问题是无法再确认的。
还有个zabbix实际使用中的坑:用钉钉webhook收告警时,媒介(Media type)配置不生效。7.0的webhook媒介脚本要求传入的参数格式必须和脚本预期一致,很多人直接照搬网上的JavaScript脚本,但没改URL、钉钉机器人的加签密钥等参数,导致测试消息发不出来。解决方法是先在媒介配置里点“测试”,用测试参数验证连通性,再检查脚本里读取的alert.message、alert.subject等宏变量是否和实际触发时的数据一致。
6.3 沉淀自己的站点checklist与复盘模板
处理完一次告警,把它沉淀成文档,比处理告警本身更有价值。我个人的做法是每次故障处理完,按固定模板做复盘记录:故障现象、触发时间、影响范围、排查链路、根因、临时处理手段、长期修复方案、监控改进项。
时间长了,把这些复盘笔记汇总成站点checklist,也就是一份针对自己业务环境的“作战地图”手册。比如:某电商核心链路的主机,如果出现CPU告警,优先看订单处理线程池;如果出现磁盘告警,先查日志分区是否被订单日志占满。这些经验写在文档里,下次告警来的时候照着走,效率比临时思考高得多。
我还会定期把checklist里的知识反哺给监控平台:把那些“懂了之后就能更快定位问题”的指标组合成新的复合告警规则,把那些“经常误报”的监控项调高阈值或直接删除,让告警越来越准、越来越少。告警治理不是一朝一夕的事,但每处理一次故障就往里面补充一点,半年之后你会明显感受到告警变“安静”了,真出问题时反而更容易引起重视。
这套排查流程,从接到告警开始到最终沉淀成文档,基本覆盖了Linux服务器故障处理的所有关键环节。说句实在话,排查故障这事,技术命令只占三分,剩下七分是稳定的心态和一套不依赖灵感的流程。把这些步骤练成肌肉记忆,深夜告警来了,你不会慌,因为你心里那张地图已经画得明明白白了。
