ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux服务器故障排查实战:从告警到根因的完整指南

Linux服务器故障排查实战:从告警到根因的完整指南 凌晨两点手机在床头柜上疯狂震动企业微信群里告警机器人连发四五条红色消息后面跟着一串“值班同学”。你揉着眼睛爬起来打开笔记本连上跳板机——这时候最怕的不是告警本身而是面对一堆监控图表和命令行大脑一片空白不知道该从哪里下手。我自己干运维这些年Linux服务器故障排查其实最考验的不是背了多少命令而是脑子里有没有一张清晰的“作战地图”。告警只是表象CPU飙高、内存耗尽、磁盘写满、网络抖动、进程僵死背后往往是一连串因果链条。深夜值班本来就困如果还像无头苍蝇一样瞎试只会把问题越搞越复杂。这篇东西我打算把多年积累的排查思路、常用命令、真实场景里的判断逻辑整个串一遍没有水话全是能直接拿去用的东西。文中的命令和思路适用于CentOS 7、Ubuntu 18.04、Rocky Linux等主流发行版新手可以照着敲老手也可以看看有没有遗漏的角度。1. 先别急着敲命令故障排查的底层思路面对一台“看起来不对劲”的服务器新手容易犯的毛病是看到一个现象就急着下结论。比如负载很高立刻去 kill 进程再比如磁盘满了马上去删文件。这样做运气好能蒙对运气不好反而把现场破坏掉丢了关键线索。我自己的习惯是先建立一个整体的排查框架把“收集信息、定位问题、处理恢复、复盘改进”这四步刻在脑子里。好多人问我故障排查最快的捷径是什么我的答案是没有捷径但是有正确的优先级。第一步是确认服务到底挂没挂影响范围有多大——这决定了你的事情紧急程度第二步是收集系统当前的状态快照第三步才是分析根因。顺序一旦搞反就容易出现“处理了半天结果发现就是监控误报”的尴尬。还有一个容易被忽视的东西叫“时间线”。告警发生的时间点、服务异常的时间点、最近一次变更的时间点这三者一旦能对上很多问题的原因就直接浮出水面了。比如昨天下午上线了新代码、今天凌晨内存告警那多半和新版本的内存泄漏有关再比如刚重装了系统老服务起不来那先检查权限和依赖库别去调系统参数。1.1 黄金十五分钟先收数据再动手我给自己定过一个规矩叫“黄金十五分钟”。就是说接到告警后的前十五分钟之内只做信息采集不做任何变更操作。这十五分钟里需要跑一组基础的“体检命令”把系统当前的状态完整记录下来。date uptime top -bn1 | head -30 free -h df -hT cat /proc/loadavg dmesg -T | tail -50 ss -antlp | head -50这一串命令下来CPU负载、内存余量、磁盘空间、内核报错、端口监听情况基本就摸清了。最关键的其实是dmesg它记录的是内核层面的信息很多用户态工具看不到的硬件错误、OOM内存耗尽记录、磁盘 IO 报错都能在这里找到线索。我见过不少同行排查了半天都不知道内存是被谁吃光的结果在 dmesg 里一眼看到 OOM Killer 把 Java 进程杀了——这种时候如果不看内核日志真的是绕一大圈。采集完这些基础数据后我会顺手保存一份到本地文件里。别嫌麻烦这就是后面分析问题和写复盘报告的原始素材而且如果处理过程中你动了什么参数至少还有个对照基线。1.2 信息汇总别变成盲人摸象信息一多人的注意力就容易被带偏。我的习惯是拿到数据后先在脑子里或者直接在记事本里快速回答这几个问题这台机器是突然出问题还是慢慢变差的这决定了是“急性故障”还是“慢性恶化”。是单机问题还是集群里多台机器一起出问题单机往往是资源耗尽或硬件故障多台机器一起出问题则优先怀疑交换机、网络入口、公共配置中心。是 CPU、内存、磁盘、网络这四个维度里的哪个最先达到临界点监控面板上通常会有趋势图往前翻一翻能看到拐点时间。信息汇总这一步很多人觉得没用但实际值夜班的时候它就是救命稻草。有一次我接到一个数据库主从延迟的告警第一反应是去查数据库慢查询折腾了十几分钟没结果。后来回头看了下监控趋势发现从节点所在宿主机的磁盘 IO 使用率在同一个时间点突然飙到 95%——原来是宿主机上其他虚拟机在做全量备份把磁盘带宽吃满了。这种跨层面的问题如果不先做整体汇总只盯着数据库日志看永远找不出答案。2. 第一梯队侦察CPU、内存、磁盘、网络的“体检”命令系统四大件——CPU、内存、磁盘、网络——是任何故障排查都绕不开的基础。我下面按维度逐个拆开讲每个维度先给核心命令再讲怎么看输出最后补充一些容易误判的地方。这些命令都不需要额外装软件属于排障的“标准弹药”。2.1 CPU先看负载再看占用看 CPU 的第一步不是去看哪个进程占得高而是先看uptime输出的三个负载值。我曾经用一句话总结过负载的含义它表示的是“等待运行的进程平均数”不是 CPU 使用率。单核机器负载超过 1 就意味着任务排队了四核机器负载超过 4 才说明抢 CPU 了。但负载高不一定代表 CPU 真的忙它也可能是进程处于不可中断的 IO 等待状态比如磁盘快要挂掉的时候一堆进程卡在 IO 上CPU 使用率未必高负载却可能飙到几十。uptime top -bn1 mpstat -P ALL 2 5 pidstat -p PID 1 5推荐在排查时优先使用mpstat -P ALL它是 sysstat 包里的工具可以分别显示每个 CPU 核心的使用率。如果看到某个核的使用率接近 100% 而其他核都是空的多半是单线程应用或者 CPU 亲和性设置导致的如果所有核都很忙那可能是多线程并发任务过多需要去进程里找具体是哪个业务线程。真正判断 CPU 繁忙的根因时top进去按P键按 CPU 使用率排序看排在最前面的进程是谁。如果是 Java 应用进一步用top -Hp PID查看该进程内所有线程的 CPU 占用然后jstack把线程栈导出来结合线程 ID 转成十六进制去匹配能看到具体是哪个业务线程在疯狂计算。这一套组合拳在定位 JVM 抢占 CPU 的问题上非常经典值得反复练习。2.2 内存free 好用但别只看 free我见过不少新手拿到free -h的输出后看到 used 非常高就直接喊“内存不够了”这是最常见的误判之一。Linux 的内存管理机制里空闲内存会拿来作缓存cache这是正常的、有益的行为不应该把这些缓存当成可用内存的敌人。看内存是否真的紧张要关注的是available这一列它才是“在不触发交换的情况下还能分配给新进程的内存估算值”。free -h cat /proc/meminfo ps aux --sort-%mem | head -20三条命令组合使用基本能判断内存状况。free -h看总量和可用量/proc/meminfo看更细的指标比如CommitLimit、Committed_AS这类和内存超卖相关的字段ps aux --sort-%mem则直接按内存占用给进程排序告诉你内存被谁吃掉了。排查内存问题还有两个非常容易忽略的点。一个是共享内存和 tmpfs/dev/shm默认占用物理内存的一半某些框架会往里面写数据用着用着就容易把内存吃光另一个是内核的 slab 缓存有些场景下dmesg或者slabtop会发现隐藏的内存消耗。如果free -h里的 used 特别高但ps里又找不到明显的大内存进程顺手敲一下slabtop往往能发现真正的元凶。2.3 磁盘从空间到 IO 的完整链路磁盘类的告警一般分成两类一类是空间不足比如使用率超过 85%另一类是 IO 性能下降iowait 高、读写延迟大。两类问题的排查命令不太一样但都不复杂。先看空间。df -hT能看出哪个分区满了这里有个坑如果你发现是根分区满了但用du -sh /*逐层找又没有找到特别大的目录那很可能是有文件被删除但进程还握着文件句柄导致空间无法释放。这时候用lsof | grep deleted找到那些还在运行的、打开已删除文件的进程重启或 kill 掉空间就会被释放出来。这个问题我几乎每年都能遇到几次非常典型。再来看 IO。iostat -x 1输出的字段里%util表示设备繁忙程度await表示平均 IO 响应时间svctm是服务时间。如果%util接近 100% 而await很高说明磁盘真的是瓶颈如果%util不高但await很高那通常说明磁盘硬件有问题或者在读写过程中碰到了大量碎片需要用smartctl这类工具进一步检测。iotop能直接看到哪个进程在大量读写适合在定位“IO 消耗大户”时使用需要 root 权限不过大多数场景下值得这么操作。高 IO 还有一个隐藏领域是 swap 交换分区。物理内存不够时系统会把不常用的内存页换到磁盘上然后频繁的换入换出会让磁盘 iowait 飙升。遇到这种问题根源往往还是在内存上单纯优化磁盘没有意义。2.4 网络连接数、丢包与时延网络类故障在夜里特别多原因很简单夜晚是备份、同步、批处理任务的高峰期流量容易打满。排查网络的命令我按照“从整体到细节”的顺序来用。先看端口监听和连接状态ss -antlp比老的netstat更高效输出也更友好。重点关注ESTABLISHED建立连接、TIME_WAIT主动关闭后的等待、SYN_SENT发起了连接但没回包这三类状态的数量。SYN_SENT大量出现基本可以断定对端地址不可达或防火墙拦截TIME_WAIT大量出现常见于短连接高并发场景可以通过修改内核参数进行优化。接下来用sar -n DEV 1 5来看网卡的实际流量。它能看到每张网卡的收发包速率、带宽占用、丢包率。如果某个网卡的带宽利用率接近上限那就是流量拥塞如果有 rxdrop 或 txdrop说明网卡缓冲区打满了需要检查流量整形或相应队列长度。最后是时延和路由。ping外网网关看基础连通性traceroute看路径上的每一跳延迟。如果内网访问正常但公网超时先mtr一下看丢包发生在哪一跳这样就能判断是机房出口问题还是云厂商链路问题。实际值班时有一半的“网络卡”最后查出来根本不是网络的问题而是应用层的线程池或者数据库连接池打满了所以网络排查要结合应用日志一起看不要一头扎进 ping 和 traceroute 里出不来。3. 顺着进程抓“真凶”从现象到根因的层层递进系统层面的指标都看完了接下来要干的事情是顺着指标揪出具体的进程。很多人停在这一步看到 top 里某个 Java 进程占了 100% CPU就结束排查了。但实际上“占 CPU 的 Java 进程”是个现象不是根因——到底是哪段代码在循环它为什么会被触发这些还需要往下挖。3.1 ps/top 定位“嫌疑进程”先看一张 top 输出图。按P键按 CPU 排序、按M键按内存排序能快速找到资源消耗排在最前面的进程。如果是 Java 进程记下 PID如果是 Nginx、MySQL、Redis 这类知名服务记下进程名和它的运行状态然后去对应的日志目录看最近有没有报错。ps命令是静态快照适合精确查看某个进程的详细信息。比如ps -fp PID可以看到进程的启动命令、父进程、运行时长ps -eo pid,ppid,stat,cmd --sort-%cpu | head可以直接按 CPU 占用排序比在 top 交互界面里点击更利于写脚本或记录。进程状态也值得学一下。ps输出的STAT一列中R是运行中S是睡眠中D是不可中断睡眠通常是 IO 等待Z是僵尸进程。其中D状态非常值得警惕大量进程卡在D状态说明底层 IO 设备出问题的概率很大这时候不能盲目重启否则可能导致数据不一致。3.2 strace/lsof 确认进程行为找到嫌疑进程之后接下来的问题就是“它到底在干什么”。最有力的工具是strace它能跟踪进程发起的系统调用把进程的“行为轨迹”记录下来。比如一个进程 CPU 狂飙用strace -cp PID跑几秒按统计方式看这个进程主要集中在哪些系统调用上。如果大量时间花在read、write上说明它在疯狂做 IO 读写如果花在futex上可能是线程锁竞争激烈如果花在epoll_wait上说明它大部分时间是在等待事件CPU 占用高另有原因。lsof在排查进程和文件句柄问题时也很有奇效。一个进程打开了哪些文件、监听了哪些端口、连了哪些外部地址这些信息全部能从lsof -p PID里拿到。遇到进程无法 stop 的怪问题比如明明 kill 了却还在看看是不是有子进程在占用资源先用pstree -p PID看清进程树再决定怎么处理。这里提醒一句strace生产环境慎用它会给进程带来显著的性能损耗如果被跟踪的进程本身已经在高负载运行strace可能会让它彻底卡死。一般建议先离线或者在低峰期试验实在不行用perf top这类采样工具替代损耗小得多。3.3 从内核视角看异常用户态的工具看完了如果还是没有头绪就该把视角切换到内核。dmesg -T是最快的内核诊断入口它会把内核环形缓冲区里的日志全部打出来包括硬件错误、文件系统报错、OOM 事件、TCP 异常等。举个例子有一次我排查一个服务频繁重启的问题用户态日志里只看到“OutOfMemoryError”但排查进程内存占用也不高。后来在 dmesg 里看到了这么一条Out of memory: Killed process 14837 (java) total-vm:8500000kB, anon-rss:3200000kB原因很清楚这台机器上的 cgroup 限制了内存上限而 Java 进程的堆内存加上 JVM 的元空间超出了限制被内核 OOM Killer 当成恶霸进程直接杀了。不看 dmesg这个问题根本定位不到 cgroup 这一层。内核层面的排查工具还有perf、bcc这些但说实话日常值班百分之九十的问题用不上这么深的东西dmesg配合前面的系统命令基本就能覆盖。不过把dmesg当成排查流程里固定的一环是每个运维都应该养成的习惯。4. 完整走一遍一次典型的深夜告警排查流程理论讲了不少但很多人看完还是不知道“招式”怎么连起来。下面我结合一个高度典型化的场景把整个排查流程从头到尾走一遍。这个场景是我把多年值班经历里最常见的告警类型抽象出来的几乎每个团队都会遇到。4.1 告警触发拿到的第一手信息假设凌晨 2:14监控平台推送了一条告警某台 web 服务器的 CPU 使用率持续 5 分钟超过 90%同时该机器上的后端服务接口超时率上升。告警信息里还附带了机器的 IP、告警时间、当前负载值。值班的同学是我就当是正在看文章的你打开电脑登录跳板机准备开始排查。这里强调一下告警面板上给你展示的“当前值”只是瞬间快照直接看监控大盘的历史曲线能获得更多信息。告警前 30 分钟、1 小时、24 小时的趋势分别是怎样的是一个缓坡还是一个陡峭的台阶这在很大程度上决定了问题的性质。注意观察服务变慢是从什么时候开始的和上一次发版、配置变更的时间能不能对应上这个对应关系极其重要。4.2 系统层逐项排除登录到服务器后我第一步跑的是uptime看到 load average 分别是 22、18、12也就是说最近 1 分钟的负载比 15 分钟前高了不少说明问题正在快速恶化。同时top -bn1里能看到 CPU 的 us 百分比很高大多数时间花在用户态初步判断不是内核层面阻塞更像是某个应用进程在抢 CPU。紧接着跑ps aux --sort-%cpu | head -10看到排在第一的是一个 Java 进程CPU 占用已经超过 800%这台机器是 8 核相当于吃满了 8 个核心。此时我用top -Hp PID查看这个 Java 进程内部的线程情况发现其中有 4 个线程的 CPU 占用特别集中。把这几个线程的 ID 从十进制转成十六进制记住这个值然后执行jstack PID thread_dump.txt在 dump 文件里搜索对应的十六进制线程 ID看到这几个线程全部阻塞在同一个方法的调用栈里面——是 Redis 客户端的连接池获取连接逻辑。到这里表面原因是“Java 进程疯狂消耗 CPU”真正的原因其实是“Redis 连接池获取不到连接线程在反复重试导致 CPU 空转”。如果我在上一步就急着重启 Java 服务可能当时能恢复但过几十分钟问题又会重现因为没有解决连接池为什么拿不到连接的根本问题。4.3 定位根因并止血接着去查 Redis。先 ping 了一下 Redis 所在机器网络通再用redis-cli -h redis-ip ping发现返回的是LOADING Redis is loading the dataset in memory——这说明 Redis 实例因为某些原因刚重启过正在从持久化文件中加载数据所以暂时不对外提供服务。之前的连接池请求不断超时、重试Java 进程的 CPU 自然就飙上去了。根因是 Redis 内存使用接近 maxmemory 上限触发了内存淘汰策略但淘汰过程中系统负载过高导致 Redis 进程被 OOM Killer 杀掉或者直接触发重启重启后加载 RDB 文件需要时间。这个链条如果只看单台 web 服务器根本发现不了必须把所有迹象串联起来。止血操作分三步先临时调大 web 服务的连接池超时时间并减少重试次数避免 CPU 空转再把 Redis 的maxmemory-policy改成更合适业务的淘汰策略并考虑扩容或者清理无用 key最后等 Redis 数据加载完成后服务自动恢复了。这个例子的价值在于告诉你故障排查不是“看一个指标 fix 一个问题”而是把所有指标串成一条线顺着线找到真正的因果链。单台服务器的资源告警往往只是整个系统里某个环节故障的“外在表现”。5. 高频故障速查与行为细节老手都踩过的坑章节最后我把这些年值班过程中遇到的高频故障整理成一张速查表顺便再补充一些容易踩的细节坑。这张表不是面试八股文而是真正拿“血和泪”换来的经验。5.1 高频故障排查速查表故障现象大概率原因首选排查命令常见处理手段负载高但 CPU 使用率不高大量 D 状态进程、磁盘 IO 瓶颈iostat -x 1、ps -eo stat,pid,cmd定位 IO 消耗大户检查磁盘硬件必要时迁移业务内存 remain 少但 available 够正常缓存占用无需惊慌free -h看 available 列不必处理持续观察根分区满了但 du 找不到大目录文件被删除但进程占用句柄lsof | grep deleted重启或 kill 对应进程释放空间应用端口连不上防火墙、服务未启动、端口被占用ss -antlp、systemctl status 服务按提示放行端口或启动、重启服务服务频繁 OOM 被杀进程内存超限、cgroup 限制、swap 不足dmesg -T | grep -i oom调整内存参数、升级配置或优化业务内存使用网络请求普遍超时带宽打满、连接队列溢出、对端异常sar -n DEV 1 5、ss -antlp定位流量来源扩容带宽或排查对端服务服务卡顿但资源指标都正常应用代码问题、锁竞争、下游依赖慢jstackJava、strace -p PID结合应用日志和线程栈定位瓶颈考虑限流降级磁盘 IO util 不高但响应慢磁盘硬件问题、RAID 重建、碎片过多smartctl -a /dev/sda、iostat -x 1检查硬件健康状态联系机房或云厂商更换这张表覆盖了我日常值班里 80% 的告警场景。它最大的价值不是告诉你“用哪条命令”而是告诉你“先往哪个方向想”。方向对了命令自然会用方向错了命令再熟练也是白搭。5.2 那些容易被忽略的细节最后分享几个亲自踩过坑、特别容易被忽略的细节前面分散提到过一些这里集中再强调一遍第一个是关于dmesg的时间。dmesg 默认输出的时间戳是系统启动以来的秒数不是标准时间直接看会很不直观。建议加-T参数或者使用dmesg -T配合date命令看不然你根本不知道这条报错是五分钟前还是三天前的。命令行里敲dmesg -T | tail -50是标准姿势我见过不少同行因为没加-T把时间完全搞错的例子排查时很容易误判前后顺序。第二个是重启大法虽然有效但不能乱用。很多进程问题重启一下确实能解决比如内存泄漏、连接数占满、临时文件堆积但重启意味着丢失现场——那些本来能帮你定位根因的日志、dump、线程栈重启后就全没了。实在要重启先把journalctl、dmesg、ps、top的快照保存下来最好是能顺手执行一次jstack或gcore收集核心转储给后续复盘留点素材。第三个是语法糖式的历史记录习惯。排查过程中执行过的每一个命令、每一条输出都保留在一个专门的日志文件里。你可以用script命令把终端整个会话记录下来也可以把关键输出tee到文件。出一次重大故障之后写复盘报告时你就知道这个习惯有多重要了。第四个是多看systemd日志里的服务状态。很多服务是被 systemd 托管的systemctl status 服务里不仅有进程状态还会显示最近几次启动失败的记录和最近的关键日志。journalctl -u 服务 --since 30 minutes ago能快速过滤出某个服务的最近日志配合-f参数还能实时跟踪是定位“服务为什么一直重启”问题的利器。第五个是磁盘空间排查的一个高级技巧——inode 耗尽。df -h显示空间还有富余但程序就是报磁盘已满这种情况多半是 inode 用尽了。用df -i一看便知。大量的小文件比如日志切割失败、邮件队列堆积、临时文件没清理都会把 inode 吃光在/var/spool、/tmp这些目录里检查一下一抓一个准。我个人在实际值班过程中最深的一点体会是故障排查更像是一个“排除法加证据链”的过程而不是“灵光一闪找到答案”的过程。你收集的信息越完整、越准确得出的结论就越靠谱。与其追求“一秒钟定位问题”的神技不如老老实实在平时就积累起自己的命令清单和排查模板把每一台机器的常见流量模式、日志位置、依赖关系都记录在案。真到了深夜告警接二连三的时候你手里那张提前画好的“作战地图”远比临场去翻文档可靠得多。最后再分享一个小技巧每次处理完一个故障花十分钟把你用过的命令、看到的现象、定位的过程压缩成三五条要点记到一个 markdown 文件里。这个文件不用维护得多精致但积累一年之后它就是属于你自己的故障排查手册比任何网上的教程都贴合你的实际环境。下次遇到相似问题时翻一翻它可能五分钟就能定位到问题而不是重新从零开始。
返回列表