一、核心原则与心态

  1. 保持冷静,数据驱动:切忌盲目猜测。一切结论都应基于日志、监控指标和性能数据。
  2. 先恢复,后定位:对于严重影响线上服务的问题(如P0级故障),首要目标是快速止损(重启、扩容、降级、熔断),恢复服务,然后再深入排查根因。
  3. 系统性视角:现代应用是复杂的分布式系统。问题可能出现在应用代码、数据库、中间件、网络、操作系统或硬件等任何环节。需要逐层排查。
  4. 可观测性是基石:没有完善的监控(Metrics)、日志(Logging)和链路追踪(Tracing),线上问题定位就像盲人摸象。建设好这三大支柱是前提。

二、通用问题定位流程

这是一个从宏观到微观,逐步收敛问题范围的通用流程。

第1步:问题识别与确认

  • 目标:确认问题的现象、范围和影响。
  • 动作
    • 收到告警(CPU、内存、磁盘、QPS、RT、错误率飙升)。
    • 用户反馈(页面打不开、功能报错、响应慢)。
    • 查看核心监控大盘:确认是全局性问题还是局部问题?是哪个服务、哪个接口、哪个机房出了问题?
    • 初步判断:是性能问题还是功能问题?

第2步:信息收集与取证

  • 目标:收集一切可能与问题相关的数据和现场信息。
  • 动作
    • 查看日志:迅速查看应用错误日志(tail -f, grep ‘ERROR\|Exception’),寻找明确的错误堆栈信息。这是最快定位问题的方式之一。
    • 查看监控
      • 基础设施层:CPU、内存、IO、网络流量。
      • 中间件层:数据库连接池、Redis缓存命中率、MQ堆积情况。
      • 应用层:QPS、响应时间(RT)、错误率、JVM GC情况(GC频率、耗时、Full GC)。
      • 业务层:关键业务指标(如订单创建成功率、支付成功率)。
    • 链路追踪:找到一个慢请求或错误请求的TraceID,查看完整的调用链,定位到耗时最长的Span或出错的Span。

第3步:分析与定位根因

  • 目标:基于收集到的信息,提出假设并验证,找到问题的根本原因。
  • 动作:这是最核心的一步,需要根据不同类型的问题使用不同的工具和思路。
    • CPU飙升
      • top 找到占用CPU最高的进程。
      • top -Hp [pid] 找到该进程中占用CPU最高的线程。
      • printf ‘%x\n’ [tid] 将线程ID转换为16进制。
      • jstack [pid] | grep -A 20 [nid] 查看Java线程堆栈,定位该线程在做什么。常见原因:死循环、频繁GC、计算密集型任务。
    • 内存泄漏/OOM
      • 查看JVM监控,观察老年代内存使用率是否持续上升且Full GC无法回收。
      • 使用 jmap -histo:live [pid] 查看存活对象 histogram(谨慎使用,会触发Full GC)。
      • 使用 jmap -dump:live,format=b,file=heap.hprof [pid] 导出堆内存快照。
      • 使用MAT(Eclipse Memory Analyzer)或JProfiler分析hprof文件,找到占用内存最大的对象和引用链。
    • 响应时间RT变长
      • 链路追踪定位慢在哪一环。
      • 检查数据库慢查询(slow query log)。
      • 检查外部依赖接口是否超时或变慢。
      • 检查是否存在锁竞争(数据库锁、分布式锁、synchronized/ReentrantLock)。
    • 错误率飙升
      • 查看错误日志中的异常信息。
      • 检查数据库连接池是否占满。
      • 检查缓存、MQ等中间件是否可用。
      • 检查是否触发了熔断或限流。

第4步:修复与优化

  • 目标:实施解决方案。
  • 动作
    • 紧急修复:修复代码Bug,紧急发布。
    • 配置调整:调整JVM参数、数据库连接池大小、线程池参数等。
    • 容量扩容:临时增加机器实例。
    • 流程优化:优化慢SQL、添加缓存、重构耗时逻辑。

第5步:复盘与预防

  • 目标:避免同类问题再次发生,将经验沉淀。
  • 动作
    • 书写事故报告:详细记录故障时间线、根因、修复动作、后续改进项。
    • 改进项跟踪:例如:完善监控告警、增加压测、修复代码缺陷、优化架构、完善应急预案等。
    • 知识分享:团队内部分享,共同进步。

三、常用工具/命令清单

类型 Linux命令/工具 Java生态工具 云平台/分布式工具
CPU/内存 top, htop, vmstat, pidstat jstack, jmap, jstat -
磁盘IO iostat, df, du - -
网络 netstat, ss, tcpdump, ping - -
日志 grep, awk, sed, tail, less ELK(Elasticsearch, Logstash, Kibana) Splunk, Loki
监控 - JMX, Prometheus, Micrometer Grafana, 云监控(CloudWatch/APM)
链路追踪 - SkyWalking, Zipkin, Jaeger -
诊断神器 strace, perf Arthas(阿里巴巴开源的Java诊断利器) -

特别推荐 Arthas: 它可以在不重启JVM的情况下,进行动态跟踪、诊断,非常强大。

  • watch: 监控方法调用参数、返回值、异常。
  • trace: 追踪方法内部调用路径,并输出每个节点的耗时。
  • jad: 反编译线上代码,确认正在运行的版本。
  • dashboard: 实时查看系统面板,包括线程、内存、GC等信息。

四、优化策略

定位到问题后,常见的优化方向:

  1. 代码层面
    • 优化算法和数据结构复杂度。
    • 避免不必要的对象创建,减少GC压力。
    • 使用线程池,避免频繁创建销毁线程。
    • 使用连接池(数据库、HTTP)。
  2. 数据库层面
    • 优化慢SQL(添加索引、优化SQL语句)。
    • 读写分离、分库分表。
    • 引入缓存(Redis),减少数据库直接压力。
  3. JVM层面
    • 根据应用特点(CPU密集型 vs IO密集型)和机器配置,合理设置堆大小(-Xms, -Xmx)。
    • 选择合适的GC器(如G1,ZGC)。
  4. 架构层面
    • 异步化:将非核心逻辑异步处理(通过MQ、线程池)。
    • 削峰填谷:用MQ缓冲瞬时流量。
    • 熔断降级:防止故障扩散,保证核心链路。
    • 弹性伸缩:根据流量自动扩容缩容。

五、实战案例:电商接口RT突然飙升

  1. 识别:监控大盘发现「商品详情页」接口RT从200ms飙升到2s,错误率轻微上升。
  2. 收集
    • 日志:grep错误日志,发现大量TimeoutException,调用「商品库存服务」超时。
    • 监控:发现「商品库存服务」的CPU和RT也同时飙升。
    • 链路追踪:通过TraceID查看,耗时全集中在「商品库存服务」的一个getStockById方法上。
  3. 分析
    • 登录「商品库存服务」服务器,top看到CPU占用高达300%。
    • top -Hp找到高CPU线程,jstack查看堆栈,发现多个线程都卡在执行一条SQL语句上。
    • 查看数据库慢查询日志,发现该SQL(SELECT * FROM stock WHERE item_id = ?)执行了10s+。
    • 检查该表,发现item_id字段有索引,但表数据量已过亿。
    • 根因:虽然 item_id 字段上有索引,但索引失效(比如存在隐式类型转换),而是进行了全表扫描(Full Table Scan)。对于亿级大表,全表扫描的代价是灾难性的。
  4. 修复
    • 短期:紧急扩容库存服务实例,增加数据库连接池,暂时缓解。
    • 长期:优化SQL,确保索引有效;考虑按item_id进行分库分表;为getStockById方法添加本地缓存。
  5. 复盘
    • 改进项:增加数据库慢查询的实时告警;对核心接口进行压测,提前发现容量瓶颈;完善缓存策略。