半夜两点被电话叫醒睁开眼看到屏幕上一排红色告警心跳直接拉满。这种场景几乎所有做过线上系统的开发者都经历过。告警本身不可怕可怕的是大脑一片空白不知道该先点哪里也不知道哪些命令能救命。这篇文章就是把我这些年处理线上问题攒下来的那套“肌肉记忆”整理出来从接到告警到定位根因、再到恢复止损给你一条可以直接照着走的路径。适合后端开发者、运维、SRE、DBA也适合刚接手线上系统的同学——照着这套思路来不说百分百解决问题至少能让你在关键时刻稳住阵脚把影响降到最低。1. 先别慌线上排查的顺序比技巧更重要很多人第一次接线上故障第一反应是打开代码一顿翻或者盯着监控曲线大脑宕机。我给自己的铁律是先恢复再定位。这两件事的优先级永远不能反。系统已经挂了你慢慢悠悠找原因每多熬一分钟用户那边就多积压一堆请求业务损失是实打实的。1.1 为什么“快速止血”永远排在“定位根因”前面很多技术人员有个执念觉得线上出问题必须当场把根因挖出来否则不甘心。这个心态我能理解但放到生产环境里非常危险。线上系统的核心指标是可用性不是“谜底揭晓”。用户不管你是内存泄漏还是死锁他们只知道“打不开了”“转圈了”“报错了”。所以第一步永远是判断影响面然后立刻恢复。这里的关键是想清楚“保留现场”和“快速恢复”之间的平衡如果系统还没完全挂只是变慢可以先抓现场、再重启如果已经大量报错、请求堆积直接重启或者回滚先把业务拉起来再说。我还见过更极端的情况某同事排查到一半发现是某个服务把自己所在的集群拖垮了但为了保住“现场证据”硬撑着不重启结果整条调用链全被打挂最后花了两个小时才恢复。定位根因固然重要但现场没了可以下次再补业务挂了是真的挂了。只要监控、日志系统还在重启之后一样能捞到大量线索没必要拿生产环境去赌。记住一句我自己反复跟团队强调的话线上故障的止损窗口通常只有几分钟到几十分钟。这个窗口里你做的每一个决定都是在跟时间赛跑。1.2 排查时间轴5分钟-10分钟-30分钟该做什么我习惯把排查过程卡成三个时间节点每个节点只做特定的事不允许提前跳到后面的步骤。时间段核心目标具体动作0-5分钟止血快速确认影响范围判断是单机、单服务还是全链路能回滚直接回滚能重启直接重启不要犹豫5-10分钟采集现场抓 CPU、内存、线程、GC、日志快照把第一手现场数据保存下来为后续根因定位留证据10-30分钟根因分析结合告警、变更记录、日志和现场快照逐步推理找到触发条件验证猜测第一个时间节点里最忌讳的是在一个界面上反复刷新没有任何动作。你至少要做三件事看告警详情、看变更记录、看最近发布的版本。很多线上事故都是发布引起的查变更永远是最快路径。第二个时间节点采集现场是技术活但也是可以提前练熟的——哪些命令先敲哪些文件先拷贝都是有套路的我会在后面专门写一节。等到了第三个时间节点你已经不需要慌了手上有数据、有时间线、有变更记录剩下的就是推理。这个过程要允许自己大胆假设但验证必须小心求证千万不要看到一点蛛丝马迹就急着下结论。2. 五类高频线上问题与快速识别手段故障千千万但 80% 的线上问题最后都绕不开五类CPU 飙高、内存异常、接口变慢、连接池耗尽、线程阻塞。把这五类的特征和首查命令刻在脑子里比背一百个理论都有用。2.1 五类问题速查表先用一张表把核心信息列出来后面再逐个展开讲。问题类型典型现象怀疑方向第一命令CPU 飙高监控曲线像火箭响应超时死循环、频繁 GC、正则回溯、业务算法异常top再看进程内线程内存异常内存曲线持续上涨Old 区居高不下内存泄漏、大对象、集合类持有引用、本地缓存膨胀jmap -histo结合 dump 分析接口变慢RT 从几十毫秒涨到几秒QPS 掉锁竞争、数据库慢 SQL、远程调用超时重试、磁盘 IO 高Arthas trace慢日志连接池耗尽报错连接超时获取连接失败连接泄露、并发量超过预估、下游慢拖死连接回收看连接池监控抓线程栈线程阻塞请求堆在某个点部分线程卡死死锁、饥饿、阻塞等待、线程池被占满jstack 取线程栈找 BLOCKED/WAITING这张表我建议直接截图保存。虽然每个系统细节不同但排查方向大体一致先命中大类再往深处挖比无头苍蝇式乱看强太多。2.2 每类问题的识别要点CPU 飙高是最常见也最好定位的。第一步不是看代码而是 top 一下找到占用率最高的那个进程记下 PID然后执行top -H -p PID按 CPU 排序看线程把最忙的线程号转成十六进制再jstack去对应线程栈里找基本一抓一个准。遇到正则回溯或者死循环栈里往往能看到同一行代码反复出现或者某个业务方法长时间占据线程。内存问题比 CPU 要隐蔽因为它不会瞬间爆发而是温水煮青蛙。特征是 GC 频率逐渐升高响应一点点变慢最后突然 Full GC 卡死。遇到这种情况别急着 dump先用jmap -histo:live看下前十位的对象类型如果发现某个业务对象数量异常大基本能锁定方向。然后再 dump 出来用 MAT 分析引用链看是谁hold住了这些对象十有八九是静态集合或者缓存忘记清理。接口变慢的排查第一反应应该是看调用链和日志而不是抓线程栈。先确认变慢是发生在应用内部、数据库、还是下游服务。用 Arthas 的 trace 命令可以精准看到方法内部每一步的耗时如果发现是 SQL 慢就去数据库看慢日志如果是下游超时就检查依赖服务的状态和超时时间配置。这里容易犯的错是把重点放在代码逻辑上其实大部分慢接口都卡在 IO 和跨服务调用上。连接池耗尽容易识别报错信息往往直接告诉你“获取连接超时”。但根因需要小心有可能是并发真的上来了也有可能是某条慢 SQL 把连接占住不释放。抓线程栈看那些线程卡在哪一步比单纯调大连接池要靠谱。调大连接池只是拖延问题根因不解决迟早还是满。线程阻塞问题jstack 是最好的工具。多取几次线程栈间隔几秒看线程状态是不是一直卡在同一个位置。如果是死锁会有明显的相互等待如果是线程池耗尽会看到大量线程 WAITING 在队列等待任务上那问题基本出在线程池配置和任务堆积上。3. 实操实录一次接口超时故障的完整排查过程讲理论总觉得飘我把一次真实处理过的故障复盘拆给大家看。背景是某系统的订单查询接口某个周五下午突然告警接口成功率掉了 30%RT 从平均 80 毫秒涨到 4 秒以上。这个场景很典型几乎每个团队都会遇到。3.1 故障现象与第一反应监控面板上的曲线非常吓人接口调用量没有明显变化但 RT 像心电图一样突刺错误率持续爬坡。当时的第一反应不是去查代码而是看这两个东西变更记录和发布记录。因为非突发流量导致的性能劣化大概率是新发版本带进去的或者某个配置被改动了。查看之后发现前一天晚上确实发布过一个版本改动点集中在订单详情的组装逻辑里涉及一个新的数据聚合服务。再往下看监控发现依赖的那个数据聚合服务的调用耗时也在同步上升。到这里基本可以初步断定问题出现在应用与下游服务之间的调用链路上而不是订单服务自身。3.2 现场抓取与信息收集确认茶壶方向后立刻登到对应的机器上执行了 top发现应用进程 CPU 并不高只有 20% 左右这跟 CPU 死循环场景不同说明卡点不在计算而在等待。紧接着用 Arthas 的 trace 命令跟踪了订单查询接口的调用路径发现大部分时间耗在了一个远程调用的 execute 方法上。再往深处 trace看到下游数据聚合服务的连接获取耗时占了整个调用时间的 80% 以上。到这里矛头已经指向了连接池。马上看连接池监控发现活跃连接数已经顶到上限等待获取连接的线程数持续上涨。同时配合 jstack 取了线程栈看到大量线程卡在创建连接到下游的代码段状态全部是 RUNNABLE 但实际在等待 socket 连接。现场证据够了可以下结论了。3.3 根因定位与修复动作根因是下游数据聚合服务在大促前做了一批缓存预热导致服务端处理不过来部分请求超时而客户端连接池的建立超时时间设置得太长导致线程在上游长时间挂住不放。这个超时时间过长加上没有配置合理的最大等待时间使得下游服务抖动的影响被放大到整个订单查询链路上。修复动作分了三步第一步立即对下游服务扩容同时让订单服务的连接池把建立超时时间从 10 秒砍到 3 秒避免线程长时间被拖死。第二步在订单服务加上对聚合服务的降级逻辑如果该服务耗时超过阈值直接走本地缓存的结果。第三步事后把连接池参数、超时配置和熔断策略做了一轮全面检查并顺手补齐了告警规则。这次故障给我的最大启发是线上问题的根因往往不是单一节点而是一串连锁反应。下游稍微打个喷嚏上游配置不合理就放大成整个服务不可用。排查时不要只盯着第一个异常点把调用链上下游都扫一遍才能避免修了个表面的坑底下的雷还在。4. 可直接套用的排查命令与工具速查工具箱里常备几个命令熟练到条件反射的程度关键时刻是真的能救命。以下命令覆盖了最常见的排查场景都是我自己反复验证过的可以直接复刻。4.1 系统层命令先看机器状态进到一台出问题的机器别急着说“Java 有问题”先把系统层看明白。uptime看平均负载free -h看内存top看 CPU 和进程概况。平均负载如果远超 CPU 核数说明系统已经堵了可能是 CPU 忙也可能是大量线程在不可中断睡眠。iostat -x 1看磁盘 IOvmstat 1看上下文切换和阻塞线程数。这些命令组合起来能在 30 秒内判断是计算型问题、内存型问题还是 IO 型问题。如果进程 CPU 高先记录下 PID然后top -H -p PID这个命令能按线程展示 CPU 占用。记下最上面的线程号转成十六进制printf %x\n 线程PID得到十六进制线程号后配合 jstack 定位线程栈。这是我用过最高效的组合拳。4.2 JVM 层命令线程栈与堆内存线程栈是排查一切线程问题的万能钥匙命令很简单jstack -l PID thread_dump_$(date %s).txt重点强调一下多取几次间隔 5 到 10 秒而且最好附带当时的 top 记录。单次线程栈有很大的偶然性可能你取的时候线程刚好调度走了取三次以上才能准确判断是不是“一直卡在那”。死锁、无限等待、线程池耗尽都会在线程栈里留下非常明显的痕迹。堆内存方面的命令按优先级排是这样jmap -histo:live PID | head -30 jmap -dump:live,formatb,fileheap.hprof PID第一条命令用来快速看存活对象的数量分布第二条命令用来抓完整堆快照做深入分析。现场抓完服务就可以随便重启了dump 文件已经是最好的证据。用 MAT 或者 VisualVM 打开堆快照重点看两个地方大对象怎么来的、GC Roots 是什么。在实际业务里缓存撑爆内存和集合引用了大对象是排前两名的元凶。4.3 在线诊断工具Arthas如果现场已经抓完业务也恢复了但根因还没头绪Arthas 是当前条件下最推荐的在线诊断利器。启动方式很简单java -jar arthas-boot.jar它最常用的功能一个是dashboard实时看线程、内存、GC 的整体情况一个是trace跟踪方法内部耗时再一个是watch观察方法的入参、返回值和异常。对于接口变慢的问题trace 基本是降维打击它能把方法内每一层调用的耗时拆出来给你看数据库查询慢、下游调用慢、本地计算慢一目了然。使用 Arthas 有一个注意点生产环境 attach 到 JVM 有风险特别是高并发服务建议在低峰期测试或者用阿里开源的 arthas-tunnel 做权限管控。不要在生产环境随意 watch 所有方法性能开销不是小数目。4.4 日志排查的 grep 技巧日志永远是最朴实但最有效的情报来源。线上排查时我常用的套路是grep -n 异常关键词 app.log | tail -100 grep -rn 订单号 app.log | grep 异常 | head -50多服务之间用 traceId 串起来查调用链先在一个服务里找到出错的那条日志把 traceId 拿出来然后去下游服务 grep 同一个 traceId整个链路上的日志就拼出来了。这个动作在排查跨服务故障时价值极高比看半天监控曲线更直接。还有个容易被忽略的小技巧看日志的时间要跟系统时间校准不少故障案例里应用日志和数据库日志时间差了好几分钟导致排查时错位判断。提前做好日志规范化、时间统一化排查会顺手很多。5. 止损与恢复别让业务在排查期间持续受损排查很重要但业务恢复永远是第一目标。很多人把“定位根因”和“恢复服务”混在一起做结果两边都没做好。正确的姿势是定位到大概率方向之后马上做止损把系统拉回稳定状态再回头慢慢验证根因。5.1 四类止损手段的选用条件止损手段不是越多越好而是越对症越好。回滚、限流、降级、重启各有适用场景。回滚一般用在“发布后立刻引发问题”的场景。改动导致的问题回滚到上一个版本是最简单、最快、最有效的方案。回滚之后要确认的不仅是“业务恢复了”还要保留好发布包的版本号和配置差异后续复盘要用。限流适合流量冲击型故障比如活动流量超出预估、某个接口被刷。限流不是为了修 bug而是为了保护系统不要被流量打死同时给排查争取时间。降级则是针对依赖故障的止损手段把非核心链路上依赖方摘掉比如改用缓存、返回兜底数据保证主链路能用。重启是最快速的恢复手段适合内存泄漏初期、线程池被打满这类情况但它只能暂时缓解必须抓紧后续处理根因。每次用这些手段都要记下时间和操作内容复盘时你才知道哪一步真正起了作用。5.2 恢复之后的容量评估与观察期故障恢复不等于问题结束恰恰相反问题可能还在暗处发酵。我见过不少团队把服务拉起来就散了结果第二天同一时间又挂了。恢复之后第一件事是重新评估容量CPU、内存、连接数在恢复后的走势是不是还在安全水位。如果 CPU 还是 90%服务虽然能跑但下一次抖动分分钟再出事。观察期至少要覆盖一个完整业务周期对多数业务来说是一到两天。这段时间重点盯三类指标是不是还有隐藏的报错、GC 频率是不是正常、依赖的下游服务是不是稳定。如果这些指标都平稳再宣布“故障解除”不迟。切记不要在恢复后立刻做大量变更哪怕你觉得自己已经知道根因了。先观察、再验证、后变更这是底线。6. 我踩过的排查盲区与经验清单做线上排查这么多年印象最深的不是那些顺利定位的问题反而是一开始毫无头绪、绕了很大弯子的那些案例。这些“盲区”如果不写下来新手大概率还会再踩一遍。6.1 容易忽略的五个盲区第一个盲区是只看应用不看中间件。有一次排查接口超时应用层怎么看都正常CPU 也低线程也不堵最后发现是缓存服务出现了慢查询读请求大量集中超时。中间件的监控往往不会被第一时间想到但它恰恰是很多隐蔽故障的源头。第二个盲区是只看监控不看日志。监控只能告诉你“哪里坏了”日志才能告诉你“为什么坏”。两个缺一不可只看监控容易瞎猜只看日志容易迷失在细节里。第三个盲区是重启太快导致现场丢失。很多团队遇到卡顿直接重启很果断然后问题复现了但一点线索都没有。正确的操作是抓完线程栈、堆内存再做重启前后就差几分钟。第四个盲区是忽略 GC 日志。排查 Java 服务问题GC 日志必须看很多内存问题和“突然变慢”都跟 Stop-The-World 有关。最好线上默认保留 GC 日志并按日期拆分否则真出事时你连证据都没有。第五个盲区是告警风暴中乱操作。告警一堆时最容易犯的错是东点一下西点一下毫无章法。稳住心态按时间轴逐项梳理比任何骚操作都有效。6.2 故障复盘会怎么开才有效复盘会的核心目标不是追责而是让下一次故障更快被发现、更快被恢复。一个有效的复盘至少要产出三样东西时间线记录、根因结论、改进项清单。时间线要精确到分钟谁在什么时间做了什么这些信息拼起来才能发现是“没人发现问题”还是“发现问题没人决策”。根因结论要用证据支撑而不是“我猜是这样的”。改进项不要写空话“加强监控”这种等于没写。好的改进项要具体到某条告警规则、某个配置参数、某个代码逻辑。每一条都要有负责人和截止日期下次开会逐个过。6.3 三个长期建议与最后的经验之谈第一个建议是定期做故障演练人为制造一些可控的问题看团队能不能按流程走下来。很多人第一次处理线上问题手忙脚乱就是因为没演练过。第二个建议是建设好可观测性日志、监控、链路追踪这三驾马车是线上排查的基础设施别等需要查的时候才去补。第三个建议是维护好变更记录绝大多数线上故障都跟变更相关一份完整的变更记录能帮你在排查时省掉一半时间。最后说句实在的。排查线上问题的核心能力与其说是技术深度不如说是冷静的判断力和一套随时能调用的方法论。命令是背不完的工具也在不断更新但稳定的排查框架、清晰的时间线意识、以及保留现场的好习惯会在每次故障中都帮你兜底。希望大家都能少熬夜排查但真遇到了也知道手往哪里放。