资讯详情

资讯详情

服务器故障排查实战:CPU、内存、磁盘、网络与服务启动全解析

1. 从一次深夜告警说起服务器故障排查的底层逻辑凌晨两点手机突然震动监控平台推送了一条告警某台业务服务器的CPU使用率持续15分钟超过95%。我爬起来打开电脑第一反应不是直接重启而是先连上去看看到底是什么进程在吃CPU。这个习惯救过我很多次——盲目重启虽然能暂时缓解问题但根因没找到过不了多久又会复发。服务器故障这件事说到底就是“预期状态”和“实际状态”出现了偏差。你预期它应该稳定提供服务结果它响应变慢、服务中断、数据异常。排查的过程本质上就是不断缩小范围、定位偏差来源的过程。很多刚接触服务器运维的朋友容易犯一个错误一遇到问题就凭感觉操作重启、改配置、加资源三板斧下去有时候碰巧好了有时候越搞越糟。问题在于你没有建立起一套系统的排查思路。这篇文章想聊的就是服务器日常运维中最常见的那几类故障——CPU飙高、内存泄漏、磁盘写满、网络不通、服务起不来——每一类我会从“现象长什么样”讲到“怎么一步步定位”再到“怎么彻底解决和预防”。不管你是刚入行的运维新人还是偶尔需要兼顾服务器管理的开发者这些内容都能直接拿去用。我不会只给你一个结论而是把排查过程中的思考路径也摊开来讲因为真正值钱的不是“重启大法”而是你知道为什么要重启、重启之前应该先看什么。2. CPU占用率异常飙升从“哪个进程”到“哪行代码”CPU问题是服务器故障里出现频率最高的之一。现象通常很直观监控面板上CPU曲线突然拉高SSH连上去敲命令都卡顿业务接口响应时间从几十毫秒变成几秒甚至超时。但“CPU高”只是一个结果真正要回答的问题是谁在消耗CPU为什么消耗这么多。2.1 用top和pidstat锁定嫌疑进程第一步永远是看全局。top命令是最顺手的工具输入之后按P键按CPU排序排在最前面的进程基本就是嫌疑人。但这里有个细节很多人忽略top默认显示的是瞬时值有些进程是周期性爆发你打开top的那一秒可能刚好没抓到。所以我习惯用pidstat做持续采样pidstat -u 1 10这条命令每秒采样一次共采10次输出每个进程的CPU使用率。如果某个进程反复出现在列表顶部那基本可以锁定它了。锁定进程之后接下来要区分两种情况这个进程是“应该跑但跑得太猛”还是“根本不该跑”。前者比如你的Java业务进程在高峰期CPU高是正常的后者比如一个莫名其妙的挖矿脚本在后台跑那就属于安全问题了。2.2 线程级定位找到具体是哪条线程在忙如果是多线程应用光知道进程还不够得进一步看到线程级别。用top -Hp pid可以列出该进程下所有线程的CPU占用找到占用最高的线程IDTID。然后把TID转成十六进制printf %x\n tid拿到十六进制值之后如果是Java应用用jstack pid | grep 十六进制值就能定位到具体的线程堆栈看到它正在执行什么代码。这个链路我走过很多次有一次定位到一个死循环的定时任务就是通过这种方式找到的。对于非Java应用可以用gdbattach上去看调用栈或者用perf top -p pid实时看热点函数。perf这个工具非常强大它能告诉你CPU时间到底花在了哪个函数上是用户态还是内核态。2.3 常见CPU飙高场景与对应处理场景典型表现处理方式死循环/无限递归单核CPU跑满进程持续高占用修复代码逻辑加超时保护频繁GCJava进程CPU高日志中有大量Full GC调整堆参数排查内存泄漏定时任务重叠周期性CPU尖刺与cron时间吻合加锁防止任务重叠执行外部攻击/挖矿陌生进程CPU满载网络连接异常隔离主机排查入侵途径上下文切换过多CPU使用率高但实际计算少减少线程数排查锁竞争这里特别说一下上下文切换的问题。有时候你看到CPU使用率很高但系统实际做的有效计算并不多这时候要看看vmstat输出中的cscontext switch列。如果每秒上下文切换次数达到几十万甚至上百万说明大量时间花在了线程切换上而不是真正的计算。这种情况通常是线程数配置过多或者锁竞争激烈导致的解决办法是减少线程池大小或者优化锁的粒度。注意处理CPU问题之前先确认业务是否正在受影响。如果业务还能正常跑优先保留现场再排查如果业务已经不可用可以先临时扩容或限流保住服务再回头慢慢分析。3. 内存泄漏与OOM为什么重启只能管一时内存问题和CPU问题不一样它往往更隐蔽。CPU高了监控立刻报警内存是慢慢涨上去的等到触发OOMOut Of Memory的时候进程直接被系统杀掉业务瞬间中断。更麻烦的是很多人遇到OOM的第一反应是重启服务重启之后内存确实降下来了但过几个小时又涨回去周而复始。3.1 先分清是系统内存不足还是进程内存超限这两个概念经常被混淆。系统内存不足是指整台机器的物理内存被耗尽内核的OOM Killer会挑一个“得分最高”的进程杀掉。进程内存超限是指某个进程自身的内存使用超过了它被允许的上限比如容器环境下的cgroup限制或者JVM的堆上限触发进程级别的OOM。判断方法很简单free -h看系统整体内存ps aux --sort-%mem | head看哪个进程占用最多。如果是系统级别的还要看dmesg | grep -i oom确认OOM Killer杀了谁。3.2 用pmap和smaps拆解进程内存分布确认了是某个进程内存异常之后下一步是看它的内存到底花在哪里了。pmap -x pid可以列出进程的内存映射包括每一段的大小和类型。你会看到堆、栈、共享库、mmap文件等各占多少。更细粒度的信息在/proc/pid/smaps文件里它记录了每一段内存的RSS实际物理内存、PSS按比例分摊的共享内存等指标。如果发现某段匿名映射特别大那大概率是堆内存泄漏。对于Java应用jmap -heap pid看堆的整体使用情况jmap -histo:live pid看各类对象的数量和大小。如果某个类的实例数量异常多基本就是泄漏点了。再配合jstack看线程堆栈找到是谁在持续创建这些对象。3.3 内存泄漏的典型模式和修复思路内存泄漏的本质是“对象该被回收的时候没有被回收”。常见的原因有这么几类静态集合只增不减比如一个static的HashMap当缓存用但从来没有清理策略时间长了越积越多。监听器/回调未注销注册了事件监听但没有在合适的时候移除导致对象一直被引用。ThreadLocal未清理线程池场景下ThreadLocal变量用完之后没有remove线程复用时旧数据一直挂着。连接/流未关闭数据库连接、文件流、网络连接用完没close底层资源一直占用。修复的思路就是找到持有引用的那条链在合适的位置断开它。如果是缓存导致的加LRU淘汰策略或者设过期时间如果是监听器在销毁生命周期里反注册如果是ThreadLocal在finally块里调remove。实操心得排查内存泄漏的时候我习惯先做一次堆转储jmap -dump:live,formatb,fileheap.bin pid然后用MAT或者VisualVM打开分析。看“Dominator Tree”找到占用最大的对象再看“Path to GC Roots”找到是谁在引用它。这个路径往往就是泄漏的根源。4. 磁盘空间告急不只是删文件那么简单磁盘写满这个问题说大不大说小不小。小到日志文件没清理大到数据盘被写爆导致数据库崩溃。我见过最离谱的一次是某台服务器的根分区被一个失控的日志文件撑满了文件本身有几百GB直接把系统盘写爆SSH都连不上。4.1 快速定位哪个分区满了哪个目录最大第一步永远是df -h看哪个分区使用率接近100%。确认分区之后用du -sh /*逐层往下找大目录。但这里有个坑如果目录下有大量小文件du会跑得很慢。这时候可以用du -sh --max-depth1 /path限制深度或者用ncdu这个交互式工具它能快速扫描并让你像文件管理器一样浏览。还有一种情况是“文件被删了但空间没释放”。这是因为进程还持有已删除文件的文件句柄。用lsof | grep deleted可以找到这类文件重启对应进程或者用 /proc/pid/fd/fd清空内容就能释放空间。4.2 日志治理从源头控制磁盘增长大部分磁盘写满都是日志惹的祸。治理日志有几个层次第一层是日志轮转。用logrotate配置按天或按大小切割保留最近N份。这是最基本的但很多人配了之后就不管了结果轮转策略不合理比如保留30份每份1GB那就是30GB。第二层是日志级别控制。生产环境不要开DEBUG级别INFO也要谨慎。有些框架默认打大量无用日志需要针对性关掉。第三层是异步日志和限流。同步写日志会阻塞业务线程异步日志虽然性能好但队列满了会丢日志或者阻塞。合理的做法是异步队列满时降级为丢弃或采样。第四层是集中式日志。把日志推到远端存储本地只留少量缓冲。这样本地磁盘压力小查询也方便。4.3 inode耗尽一个容易被忽视的坑有时候df -h看空间还有富余但就是写不进去文件报“No space left on device”。这时候要检查inodedf -i。如果inode使用率100%说明小文件太多了。常见于缓存目录、session目录、邮件队列等。解决办法是找到哪个目录小文件最多清理掉。长期方案是调整应用逻辑比如用合并文件代替大量小文件或者定期清理过期缓存。问题类型检查命令典型原因处理方式分区空间满df -h日志/数据增长清理或扩容已删除文件未释放lsof | grep deleted进程持有句柄重启进程或清空fdinode耗尽df -i大量小文件清理小文件目录磁盘IO饱和iostat -x 1读写压力大优化IO或加盘5. 网络连接异常从ping不通到连接超时网络问题是最让人头疼的因为它涉及的环节多从物理网卡到交换机到路由到对端服务任何一环出问题都表现为“连不上”。而且网络问题的现象往往很模糊有时候是超时有时候是拒绝有时候是时通时不通。5.1 分层排查从物理层到应用层逐级确认我习惯按OSI模型从下往上查物理层/链路层ip link show看网卡状态是不是UPethtool 网卡看有没有物理连接。网络层ip addr看IP配置ip route看路由表ping网关和外部地址。传输层ss -tlnp看端口监听状态telnet或nc测试端口连通性。应用层curl -v看HTTP请求的详细过程确认是连接阶段还是传输阶段出问题。这个顺序不能乱。我见过有人一上来就抓包分析HTTP结果发现是网线没插好。5.2 连接超时 vs 连接拒绝现象背后的不同原因这两个现象经常被混淆但原因完全不同连接超时SYN包发出去了但没有收到SYN-ACK。通常是防火墙丢包、路由不可达、对端没监听但也没回RST。连接拒绝收到了RST包。说明对端主机可达但目标端口没有服务在监听或者被防火墙明确拒绝。用nc -zv host port可以快速区分超时会卡住然后报timeout拒绝会立刻报refused。如果是超时重点查防火墙规则iptables -L -n、安全组配置、路由表。如果是拒绝重点查服务是否启动、监听地址是否正确比如只监听了127.0.0.1而不是0.0.0.0。5.3 TIME_WAIT和CLOSE_WAIT堆积的处理这两个状态是TCP连接管理中的常见问题。ss -s可以看整体统计ss -tan state time-wait | wc -l看具体数量。TIME_WAIT是主动关闭方产生的正常情况下会持续2MSL通常60秒然后消失。如果数量特别大说明短连接太多。解决办法是启用tcp_tw_reuse注意不是recycle或者改用长连接。CLOSE_WAIT是被动关闭方产生的如果大量堆积说明应用收到FIN之后没有正确关闭连接。这是代码问题需要检查连接池配置和异常处理逻辑确保连接在使用完毕后被正确释放。注意调整内核网络参数之前一定要先理解每个参数的含义。比如tcp_tw_recycle在NAT环境下会导致连接异常已经被新内核废弃了。不要照搬网上的“优化参数”要结合自己的场景。6. 服务启动失败从日志到依赖的完整排查链服务起不来是另一个高频问题。现象很直接执行启动命令后进程秒退或者进程在但端口没监听。排查的核心思路是先看日志再看依赖最后看资源。6.1 日志是第一手线索但要会看大部分服务启动失败都会在日志里留下痕迹。关键是找到正确的日志文件。有些服务默认输出到stdout有些写到指定文件有些用syslog。如果不确定可以用strace -f -e tracefile 启动命令跟踪文件操作看它打开了哪些日志文件。看日志的时候注意几个关键词ERROR、Exception、Failed、Permission denied、Address already in use。其中“Address already in use”是最常见的之一说明端口被占了。用ss -tlnp | grep 端口找到占用进程决定是杀掉还是换端口。6.2 依赖检查数据库、缓存、消息队列是否就绪很多服务启动时需要连接外部依赖。如果依赖没就绪服务可能启动失败或者启动后不可用。检查顺序数据库能否连通mysql -h host -u user -p或者用nc测端口。缓存能否连通redis-cli -h host ping。消息队列能否连通看对应客户端工具。配置文件中的连接串是否正确主机名、端口、用户名密码、库名。有一个容易忽略的点是DNS解析。如果配置里写的是域名而不是IPDNS挂了也会导致连接失败。可以用getent hosts 域名测试解析。6.3 资源限制文件句柄、线程数、内存上限有时候服务启动失败是因为系统资源限制。用ulimit -a看当前shell的限制但注意服务实际运行时可能用的是systemd或者容器的限制要看对应的配置文件。常见限制包括文件句柄数nofile默认可能只有1024高并发服务需要调到几万。进程/线程数nproc默认可能不够用。内存上限容器环境下cgroup限制超了会被OOM Kill。PID上限系统级kernel.pid_max一般够用但极端情况会碰到。调整systemd服务的限制用LimitNOFILE等指令调整容器用--ulimit参数。7. 那些监控没告诉你的事建立主动发现机制前面讲的都是故障发生后的排查。但真正成熟的运维重点应该放在故障发生前。监控不是万能的很多问题监控指标正常但业务已经异常了。所以除了常规的CPU、内存、磁盘、网络监控还需要一些主动发现的手段。7.1 业务层健康检查比系统层监控更重要系统指标正常不代表业务正常。比如一个服务进程在、端口在监听但所有请求都返回500这时候CPU内存可能都很低。所以要有业务层的健康检查接口定期探测核心功能是否可用。健康检查的设计要注意不要只检查“进程是否存活”要检查“关键依赖是否可用”“核心逻辑是否正常”。比如数据库连接池是否能拿到连接、缓存是否能读写、关键配置是否加载正确。7.2 日志异常检测从被动查看到主动告警日志里藏着大量信号但人工看不过来。可以配置关键字告警比如出现“OutOfMemory”“Connection refused”“Timeout”就触发通知。更高级一点可以用日志聚类自动发现异常模式。我自己的做法是在日志采集链路上加一层过滤把ERROR和WARN级别的日志单独发到一个通道设置阈值告警。比如5分钟内ERROR超过100条就通知这样既能发现突发问题又不会被正常波动干扰。7.3 定期巡检把问题消灭在萌芽期再好的监控也有盲区定期人工巡检仍然有必要。我一般每周做一次看磁盘增长趋势预测什么时候需要扩容。看证书有效期避免过期导致服务中断。看备份是否正常完成抽查恢复流程。看安全更新及时打补丁。看日志中的慢查询、慢请求优化性能瓶颈。这些工作看起来琐碎但每一次提前发现的问题都避免了一次线上故障。8. 故障复盘比修复更重要的是不再犯每次故障处理完之后如果不做复盘那这次故障就白经历了。复盘不是写检讨而是搞清楚三件事故障的根本原因是什么、为什么没有提前发现、怎么防止再次发生。8.1 根因分析多问几个为什么不要停留在表面原因。比如“磁盘满了”只是现象要问为什么满了因为日志没清理。为什么没清理因为logrotate配置错了。为什么配置错了因为上线时没检查。为什么没检查因为流程里没有这一项。问到这一层解决方案就不是“清理日志”而是“完善上线检查清单”。8.2 改进措施要可落地、可验证复盘得出的改进措施要具体。不要说“加强监控”要说“在XX指标上增加YY阈值的告警由ZZ负责下周上线”。不要说“优化代码”要说“在XX模块增加连接释放逻辑下个版本发布”。每项措施都要有负责人、时间点、验证方式。8.3 建立故障知识库每次故障的排查过程和解决方案都记录下来形成内部知识库。下次遇到类似问题可以直接检索。知识库的价值在于它记录的是“真实发生过的场景”比任何教科书都实用。而且随着积累你会发现很多故障其实是重复的只是表现略有不同。我在实际运维中最大的体会是服务器故障处理这件事技术能力只是一部分更重要的是心态和习惯。心态上要不慌按流程一步步来习惯上要记录每次操作和观察都留痕。时间长了你会发现自己对系统的“手感”越来越准很多问题在爆发之前就能察觉到苗头。这种直觉不是天生的是一次次踩坑之后长出来的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →