系统异常如何快速排查

wen 开源项目 29

从现象到根因的黄金30分钟法则

目录导读

  1. 排查前的思维准备:异常分级与响应策略
  2. 五步排查法:从症状到根源的标准化流程
  3. 高频场景实战:数据库锁死、CPU飙高、内存泄漏
  4. 工具链活用:日志、监控、链路追踪的协同
  5. 常见问答(Q&A):新手易踩的6个雷区
  6. 总结与自查清单:用模板固化排查动作

排查前的思维准备:异常分级与响应策略

问题:系统告警突然响起,你应该先做什么?
回答: 先看影响范围,再动代码,90%的崩溃源于“先重启再说”的冲动,对于S级(全站不可用)或A级(核心功能受阻)异常,立即执行以下动作:

系统异常如何快速排查

  1. 止血:回滚最近一次变更、降级非核心服务、限流(如Nginx限制单IP QPS)。
  2. 取证:保留进程快照(如 jstack -l pid)、内存dump(注意:大内存场景需提前配置 -XX:+HeapDumpOnOutOfMemoryError)。
  3. 同步:在钉钉/飞书群发送“当前影响、已执行动作、预计修复时间”模板。

踩坑案例:某团队在双11大促期间,因“错误率突然升高”直接重启了订单服务,结果导致购物车数据丢失,事后查明是配置中心推送了一个空值,但重启后旧配置被清空,反而加重了故障。正确处理:应该先切流至备用版本,保留现场再分析配置变更日志。


五步排查法:从症状到根源的标准化流程

第1步:可视化“病人状态”

  • 看大盘:登录Grafana/Prometheus,观察CPU、内存、磁盘IO、网络IO的“四维曲线”,如果某指标突然从平滑变为脉冲式抖动,这就是切入点。
  • 查日志:重点看ERROR级别日志的时间段与量级,用 grep "ERROR" app.log | tail -200 配合 awk '{print $1}' | sort | uniq -c 统计错误频率。

第2步:定位“病灶区域”

  • 如果是API接口慢:用top命令找出高CPU进程,再用strace -p pid追踪系统调用,常见凶手是“无索引SQL”或“死循环代码”。
  • 如果是数据库负载高:执行 show processlist(MySQL)或查询pg_stat_activity(PostgreSQL),找出stateLockActive超过30秒的会话,直接KILL(kill query id)。

第3步:深度诊断“病因”

  • CPU飙高top -H -p pid 找出高线程ID,然后jstack pid | grep -A 30 nid=0x转换的十六进制,定位到具体代码行。
  • 内存泄漏jmap -histo pid | head -20 查看对象分布,如果char[]byte[]数量爆炸,大概率是日志错误或JSON序列化遗漏了释放。

第4步:根因验证

  • 模拟复现:在测试环境构造相同条件(如并发数、数据集大小),确认修复后不再触发。
  • 观察指标:修复上线后,监控页面“红色曲线”是否在5分钟内恢复基线。

第5步:复盘归档

  • 在Confluence/飞书文档中记录:异常时间首次发现现象排查链路根因修复方案,经验值+1。

高频场景实战:数据库锁死与CPU飙高

场景A:数据库“锁等待”导致接口超时

现象:业务监控显示“下单接口”超时率从0.1%飙升至30%,但CPU和内存正常。
排查

  1. 查MySQL information_schema.INNODB_TRX,发现事务TRX_ID=12345已运行120秒未提交。
  2. 根据trx_mysql_thread_id,连接该线程:select * from processlist where id=xxx,发现它在执行UPDATE inventory SET stock=stock-1 WHERE product_id='A'
  3. 查看INNODB_LOCK_WAITS,发现被另一个正在执行SELECT ... FOR UPDATE的会话阻塞(该会话由手工数据导出触发)。
    解决:KILL阻塞会话,并给业务加lock_timeout=5s保护。

场景B:Java服务CPU占用100%

现象:告警群通知“订单服务CPU超90%”,top看到进程PID=9876。
排查

  1. top -H -p 9876 找到线程PID=12345占用300% CPU。
  2. printf '%x\n' 12345 转换为十六进制0x3039
  3. jstack 9876 | grep -A 100 'nid=0x3039' 发现线程卡在:
    while(true) {  
        // 某缓存框架的自动重试逻辑,未设置最大重试次数  
        redisTemplate.opsForValue().increment(key);  
    }

    解决:添加RetryTemplate的最大重试次数和指数退避策略。


工具链活用:日志、监控、链路追踪的协同

工具类型 推荐方案 排查场景 核心命令/配置
日志 ELK + Filebeat 查错误堆栈、慢查询日志 grep "Error" \| less;配置slow_query_log=1
监控 Prometheus + Grafana 看CPU/内存的“时间序列突变” 关注irate(rate)导数,而非绝对值
链路追踪 SkyWalking / Jaeger 查调用链中耗时最长的环节 spanstatusdurationerror
远程调试 Arthas 在线查看方法入参、返回值 watch com.example.service.* invoke '{params,returnObj}'

技巧:日志中加“标记字符串”(如["traceId":"xid_123"]),方便Grep过滤,同时启用AsyncAppender避免日志I/O阻塞业务线程。


常见问答(Q&A):新手易踩的6个雷区

Q1:告警来了,要不要先重启服务?
A:只有在“无法登录服务器”或“服务已完全不可用”时才重启,否则先保留现场,执行jstackjmap,重启会丢失临时文件,可能掩盖根因。

Q2:如何区分是代码Bug还是基础设施故障?
A:用“排除法”:

  1. 检查同一集群的其他实例——如果只有一台出问题,大概率是硬件/本地配置。
  2. 检查最近变动的代码版本——用git log --oneline看最近提交。
  3. 检查依赖服务(数据库、Redis、MQ)——如果它们都报错,可能是网络分区。

Q3:日志太多了,如何快速找到关键信息?
A:用tail -f app.log | grep -E "(ERROR|FATAL)" 实时过滤;或设置日志级别为WARN来减少噪音(但在排查期间可临时改到DEBUG,注意回滚)。

Q4:磁盘空间占满,如何定位罪魁祸首?
A:du -sh /var/log/* | sort -hr | head -10 看日志文件大小;lsof | grep deleted 看已被删除但未释放的文件句柄(常见于日志滚动时未重启应用)。

Q5:为什么“重启大法”在某些场景会失效?
A:因为重启会清空内存中的热点数据,导致缓存雪崩——请求全部打向数据库,反而引发更严重的故障,正确做法是限流+分批重启。

Q6:微服务场景下,如何快速定位“跨服务慢调用”?
A:关闭所有服务的本地缓存,用链路追踪工具(如SkyWalking)查看调用链,重点看spandurationresponseStatus,若某服务dubbo://order-service/send耗时2秒,则进入该服务内部继续追踪。


总结与自查清单:用模板固化排查动作

标准化排查步骤(打印贴键盘旁)

  1. 1分钟:确认影响范围→通知团队→启用降级/限流。
  2. 3分钟:拉取服务日志+系统监控+最近变更列表。
  3. 5分钟:定位是“代码变更”、“配置变更”还是“外部依赖”导致。
  4. 10分钟:如果包含数据库,执行show processlistINNODB_LOCK_WAITS
  5. 20分钟:通过jstackstrace定位具体代码行。
  6. 30分钟:制定修复方案(回滚、改配置、加限流阈值)并上线验证。

避坑清单

  • [ ] 是否开启了 -XX:+HeapDumpOnOutOfMemoryError
  • [ ] 日志中是否包含了 traceIdrequestId
  • [ ] 数据库的 max_connectionswait_timeout 是否合理?
  • [ ] 是否已关闭“自动重启”策略(如K8s的restartPolicy: Always)?
  • [ ] 是否有“排查文档”模板,避免每个人重新造轮子?

系统异常排查的效率,取决于你平时积累的“排查肌肉记忆”,从今天起,把每个异常当作一次演习——记录时间、记录步骤、记录教训,当故障再次降临时,你会感谢那些曾经被耐心记录的自查清单。

抱歉,评论功能暂时关闭!