非常抱歉给你带来了不好的体验!首先,关于格式问题,我会重新调整排版,使用更清晰的分级标题、列表和段落分隔,让内容更易读;其次,之前内容确实未完整生成,现在会按照原文完整呈现,确保不遗漏任何部分。以下是重新整理且完整的内容:
Java 线上问题排查:从基础到高级的全面指南
一、线上问题的基本分类与特征
在 Java 应用的生产环境中,问题表现形式多种多样,但可以归纳为几类典型问题。了解这些问题的基本分类和特征,是快速定位和解决问题的基础。
1.1 资源相关问题
资源相关问题是线上最常见的问题类型,主要涉及 CPU、内存、磁盘和网络资源的使用异常。
- CPU 使用率过高:
- 特征:服务器响应变慢,系统负载升高,通过
top
命令查看发现 Java 进程 CPU 占用率持续超过 80%。 - 可能原因:死循环、频繁 GC、大量计算操作、线程竞争等。
- 典型场景:电商促销期间,订单处理服务 CPU 使用率飙升至 99%,导致响应延迟增加。
- 特征:服务器响应变慢,系统负载升高,通过
- 内存溢出(OOM):
- 特征:应用突然崩溃,日志中出现
java.lang.OutOfMemoryError
异常,系统可用内存急剧下降。 - 可能原因:内存泄漏、堆内存设置过小、对象创建过多且未及时释放等。
- 典型场景:日志服务因未正确关闭日志文件句柄,导致内存持续增长最终溢出。
- 特征:应用突然崩溃,日志中出现
- 磁盘问题:
- 特征:应用写入日志或数据文件时速度变慢,
df -h
命令显示磁盘空间不足或使用率 100%。 - 可能原因:日志文件过大未清理、临时文件未删除、磁盘硬件故障等。
- 典型场景:监控系统因日志保留策略不当,导致磁盘空间被耗尽,服务无法写入新日志。
- 特征:应用写入日志或数据文件时速度变慢,
- 网络问题:
- 特征:服务间调用超时增加,
ping
命令响应时间延长,TCP 连接异常断开。 - 可能原因:网络带宽不足、防火墙配置错误、DNS 解析问题等。
- 典型场景:微服务架构中,因网络波动导致服务间调用频繁超时,引发连锁反应。
- 特征:服务间调用超时增加,
1.2 性能相关问题
性能相关问题通常表现为响应时间延长、吞吐量下降,但应用仍在运行,未完全崩溃。
- 接口响应变慢:
- 特征:用户反馈操作响应慢,监控显示接口平均响应时间(RT)明显增加,如从 50ms 增加到 500ms。
- 可能原因:数据库查询变慢、第三方服务调用延迟、代码逻辑复杂度过高。
- 典型场景:电商搜索服务在商品数量增加后,未优化的模糊查询导致响应时间延长。
- 线程池满:
- 特征:日志中出现线程池拒绝执行任务的警告,应用吞吐量明显下降。
- 可能原因:任务处理时间过长、线程池配置不合理、并发请求量突增。
- 典型场景:秒杀活动期间,订单服务线程池因处理能力不足而被耗尽。
- GC 频繁或耗时过长:
- 特征:GC 日志显示频繁的 Young GC 或长时间的 Full GC,应用出现明显的停顿。
- 可能原因:堆内存分配不合理、内存泄漏、大对象频繁创建。
- 典型场景:未正确设置 JVM 参数的 Web 应用,在高并发下频繁触发 Full GC,导致服务卡顿。
1.3 功能相关问题
功能相关问题是指应用虽然运行正常,但某些功能无法按预期工作,可能涉及业务逻辑、数据一致性等问题。
- 业务逻辑异常:
- 特征:日志中出现业务异常,如
NullPointerException
、IllegalArgumentException
等,但应用未崩溃。 - 可能原因:参数校验不充分、业务流程设计缺陷、数据状态不一致。
- 典型场景:用户积分系统中,因未正确处理并发更新导致积分扣减异常。
- 特征:日志中出现业务异常,如
- 数据不一致:
- 特征:数据库、缓存、文件等不同数据源中的数据不一致,业务操作出现异常结果。
- 可能原因:事务处理不当、缓存更新策略错误、异步操作未正确同步。
- 典型场景:订单系统中,数据库已更新但缓存未及时更新,导致用户看到不一致的订单状态。
- 分布式事务问题:
- 特征:涉及多个服务的业务操作部分成功、部分失败,数据处于中间状态。
- 可能原因:分布式事务协调机制失效、网络中断、补偿机制未正确实现。
- 典型场景:跨多个微服务的订单创建和库存扣减操作,因网络问题导致库存已扣但订单未创建。
1.4 基础设施与环境问题
基础设施与环境问题通常由服务器、网络、中间件等底层组件引起,影响应用的正常运行。
- 依赖服务不可用:
- 特征:应用日志中出现大量与数据库、缓存、消息队列等连接失败的异常。
- 可能原因:数据库宕机、Redis 服务中断、消息队列服务崩溃等。
- 典型场景:电商支付服务因第三方支付接口不可用,导致大量支付请求失败。
- 配置错误:
- 特征:应用启动失败或某些功能异常,日志中出现与配置相关的错误。
- 可能原因:配置文件错误、环境变量设置不当、配置中心同步失败。
- 典型场景:因数据库连接字符串配置错误,导致应用无法连接数据库。
- 权限问题:
- 特征:应用无法读写文件、访问网络端口,出现
Permission Denied
等错误。 - 可能原因:文件权限设置错误、用户权限不足、SELinux 或 AppArmor 限制。
- 典型场景:应用因没有写入权限而无法创建日志文件。
- 特征:应用无法读写文件、访问网络端口,出现
二、线上问题排查的基本原则
面对线上问题,遵循科学的排查原则可以提高效率,避免因操作不当导致问题扩大或复杂化。
2.1 先恢复后排查
- 核心原则:在生产环境中,应优先恢复服务可用性,再进行详细排查和修复。
- 快速响应:
当发现服务异常时,首先评估问题影响范围和严重程度,判断是否需要立即采取恢复措施。
对于影响核心业务的严重问题,如服务完全不可用,应立即启动应急预案,而不是花费时间详细分析。 - 临时解决方案:
可采取降级、限流、切换备用服务等临时措施,尽快恢复服务的基本功能。
示例:电商支付服务出现性能问题时,可暂时关闭部分非核心校验逻辑,提高处理速度。 - 恢复与记录并重:
在采取恢复措施的同时,详细记录操作步骤和时间点,便于后续分析和复盘。
示例:记录降级时间、切换的备用服务、调整的配置参数等关键信息。
2.2 系统化排查方法
- 核心原则:采用系统化的排查方法,从宏观到微观,逐步缩小问题范围。
- 分层排查:
按照 “业务表现 → 应用服务 → 中间件 → 数据库 → 基础设施” 的顺序进行排查。
示例:用户反馈支付失败时,先检查支付接口返回状态,再查看应用日志,接着检查数据库操作,最后确认支付网关是否正常。 - 数据驱动:
基于监控数据、日志信息、性能指标等客观数据进行分析,避免主观臆断。
示例:通过 APM 工具查看接口调用链路的耗时分布,确定性能瓶颈所在。 - 假设验证:
基于已有信息提出可能的原因假设,然后通过进一步收集数据来验证或排除假设。
示例:假设数据库慢查询是接口响应慢的原因,通过分析慢查询日志来验证。
2.3 保持环境一致性
- 核心原则:在排查过程中,保持生产环境的稳定性和一致性,避免引入新的变量。
- 避免在生产环境直接调试:
不要在生产环境中添加调试代码、修改业务逻辑或进行复杂的诊断操作。
示例:不要在生产环境中添加System.out.println
语句或修改核心业务逻辑。 - 最小变更原则:
每次排查操作应尽可能小且可逆,以便在发现问题时能够快速回退。
示例:调整 JVM 参数时,一次只调整一个参数,并记录调整前后的状态。 - 环境隔离:
如果需要复现问题,应在与生产环境尽可能一致的测试环境中进行。
示例:使用生产数据的脱敏副本在测试环境中复现问题,分析原因。
2.4 记录与复盘
- 核心原则:详细记录排查过程和结果,定期进行复盘,不断完善问题处理流程。
- 详细记录:
记录问题现象、时间、影响范围、排查步骤、收集的数据、采取的措施等所有相关信息。
示例:创建专门的问题跟踪文档,记录每一步操作和观察到的结果。 - 问题分类与归因:
对问题进行分类(如性能问题、功能异常、基础设施故障等),分析根本原因。
示例:将支付接口响应慢的问题归因于数据库索引缺失导致的慢查询。 - 经验总结与分享:
总结问题排查过程中的经验教训,分享给团队成员,提高整体故障处理能力。
示例:在团队内部分享如何通过 APM 工具快速定位分布式系统中的性能瓶颈。 - 预防措施:
根据问题原因,制定预防措施,如增加监控指标、完善自动化测试、优化代码等。
示例:针对数据库慢查询问题,增加慢查询日志监控和自动报警机制。
三、线上问题排查的通用流程
面对线上问题,遵循标准化的排查流程可以提高效率,确保不遗漏关键步骤。以下是一个通用的线上问题排查流程。
3.1 问题确认与初步评估
第一步:确认问题现象和影响范围
- 收集信息:
通过监控系统、用户反馈、日志系统等渠道收集问题相关信息。
关键信息包括:问题发生时间、影响的用户群体、具体错误现象、错误频率等。 - 初步判断:
根据收集的信息,初步判断问题类型(资源问题、性能问题、功能问题等)。
评估问题的严重程度和影响范围,确定是否需要立即采取恢复措施。 - 示例:
用户反馈支付页面加载缓慢,通过监控发现支付接口的平均响应时间从 50ms 增加到 2 秒,错误率上升至 15%。
初步判断为性能问题,影响所有尝试支付的用户,属于严重问题,需要立即处理。
3.2 快速恢复服务
第二步:采取紧急恢复措施
- 启动应急预案:
根据问题类型和严重程度,启动相应的应急预案。
可能的恢复措施包括:服务降级、限流、切换到备用服务、重启应用等。 - 执行恢复操作:
按照既定的应急预案执行恢复操作,确保核心业务功能可用。
示例:针对支付接口性能问题,暂时关闭非核心的风险控制检查,提高处理速度。 - 验证恢复效果:
检查服务是否已恢复正常,监控关键指标是否回归正常水平。
示例:确认支付接口响应时间是否降至可接受范围,错误率是否下降。
3.3 详细数据收集
第三步:全面收集问题相关数据
- 监控数据分析:
收集系统资源(CPU、内存、磁盘、网络)使用情况的监控数据。
收集应用性能指标(响应时间、吞吐量、错误率)的监控数据。 - 日志分析:
收集应用日志、错误日志、业务日志等相关日志信息。
重点关注问题发生前后的日志记录,查找异常堆栈、错误信息等。 - 应用状态检查:
检查应用的运行状态,如线程池状态、连接池使用情况、缓存状态等。
示例:通过 JMX 或 Spring Actuator 端点查看应用内部状态。 - 示例:
收集支付接口的监控数据,发现数据库连接池使用率达到 100%,执行show processlist
发现大量等待锁的查询。
查看应用日志,发现频繁的数据库连接超时和死锁异常。
3.4 问题定位与分析
第四步:深入分析,定位问题根源
- 分析收集的数据:
综合监控数据、日志信息和应用状态,进行深入分析。
示例:分析数据库慢查询日志,发现一条未加索引的查询语句执行时间超过 2 秒。 - 假设验证:
根据分析结果,提出可能的原因假设,并设计验证方法。
示例:假设索引缺失是导致数据库查询慢的原因,通过执行EXPLAIN
语句验证查询执行计划。 - 逐步排除:
从可能性最高的假设开始,逐一排除或验证,缩小问题范围。
示例:先验证数据库索引问题,再检查应用代码逻辑,最后确认第三方服务是否正常。 - 示例:
通过分析数据库慢查询日志和执行计划,确认支付接口中的订单查询语句因缺少索引导致性能下降。
进一步检查应用代码,发现该查询在高并发场景下被频繁调用,加剧了性能问题。
3.5 问题修复与验证
第五步:实施修复措施并验证效果
- 制定修复方案:
根据问题根源,制定针对性的修复方案。
示例:为慢查询语句添加合适的索引,优化查询性能。 - 实施修复:
在生产环境或测试环境中实施修复措施。
对于紧急修复,可考虑在生产环境直接实施;对于复杂修复,应先在测试环境验证。 - 验证修复效果:
监控修复后的系统状态和性能指标,确认问题是否已解决。
示例:监控支付接口的响应时间和数据库连接池使用率,确认是否恢复正常。 - 示例:
在数据库中为订单查询字段添加索引,重新部署应用。
监控显示支付接口平均响应时间降至 80ms,数据库连接池使用率降至正常水平,错误率接近零。
3.6 复盘与预防措施
第六步:总结经验,制定预防措施
- 问题复盘:
组织团队对问题进行全面复盘,分析问题发生的根本原因、处理过程中的优点和不足。
示例:复盘支付接口性能问题,发现缺少索引是由于开发阶段未进行充分的性能测试。 - 记录与分享:
将问题分析过程、修复方案和经验教训整理成文档,分享给团队成员。
示例:编写《支付接口性能问题处理报告》,详细说明问题原因和解决方法。 - 制定预防措施:
根据复盘结果,制定针对性的预防措施,防止类似问题再次发生。
示例:加强开发阶段的性能测试,建立数据库索引审核机制,完善慢查询监控和报警。 - 持续改进:
完善监控系统和应急预案,提高系统的可观测性和容错能力。
示例:增加数据库慢查询自动报警功能,优化支付接口的缓存策略。
四、Java 线上问题的排查工具与技术(整体概括)
Java 线上问题排查的核心是“用对工具、精准定位”,围绕 JVM 状态诊断、日志集中分析、全链路性能监控、分布式追踪 四大核心场景,行业内已形成成熟的工具体系。以下从工具分类、核心能力、适用场景三个维度,对这部分内容进行整体概括,帮助快速建立工具认知框架:
一、工具整体分类与核心定位
Java 线上排查工具可分为 4 大类,每类工具聚焦不同问题场景,且常需组合使用(如用 Arthas 定位线程问题,再用 ELK 查关联日志):
工具类别 | 核心解决的问题 | 代表工具 | 核心价值 |
---|---|---|---|
JVM 监控与诊断工具 | JVM 内存泄漏、GC 异常、线程死锁、大对象问题 | jps/jstat/jmap/jstack、VisualVM、JConsole、MAT | 直接穿透应用层,获取 JVM 底层运行状态,是“内存/线程类问题”的根本定位手段 |
日志分析工具 | 分散日志集中管理、异常日志快速检索、日志趋势可视化 | Log4j/Logback(日志输出)、ELK Stack、Graylog、Splunk | 将“碎片化日志”转化为“可查询线索”,是排查功能异常、业务报错的核心依据 |
性能分析与 APM 工具 | 接口响应慢、CPU 使用率高、吞吐量下降、全链路性能瓶颈 | Arthas、Async Profiler、SkyWalking、Pinpoint、New Relic | 从“代码方法”到“跨服务链路”,分层定位性能瓶颈,解决“不知道慢在哪”的问题 |
分布式追踪工具 | 微服务架构下跨服务调用超时、链路数据不一致、责任归属不清 | Zipkin、Jaeger、SkyWalking(含追踪能力)、Dapr | 为分布式请求生成“全局 traceID”,串联多服务调用链路,定位跨服务问题根源 |
二、各类工具的核心能力与适用场景
1. JVM 监控与诊断工具:聚焦“JVM 底层问题”
这类工具是 Java 排查的“基础工具集”,直接与 JVM 交互,核心解决 “应用层看不到的底层问题”,比如内存泄漏、GC 频繁、线程阻塞等。
- 轻量级命令行工具(jps/jstat/jmap/jstack):
- 特点:无依赖、随 JDK 自带,适合服务器端快速执行(无需安装图形化工具);
- 核心能力:
- 快速查进程(jps)、看 GC 统计(jstat)、导堆快照(jmap)、抓线程栈(jstack);
- 典型场景:服务器 CPU 飙升时,用
jstack <pid>
抓线程栈,快速定位死循环/高 CPU 线程;内存溢出前,用jmap
导堆快照备用。
- 图形化监控工具(VisualVM、JConsole):
- 特点:可视化界面,操作直观,适合开发/运维实时监控;
- 核心能力:实时看内存/线程趋势、手动触发 GC、检测死锁、查看 JVM 参数;
- 典型场景:本地开发环境监控应用启动过程,或远程连接测试环境,观察 JVM 内存变化是否正常。
- 深度堆分析工具(MAT):
- 特点:专注“堆快照深度分析”,是内存泄漏的“终极定位工具”;
- 核心能力:自动识别泄漏嫌疑对象、展示对象引用链(GC Roots)、分析大对象占比;
- 典型场景:应用发生 OOM 后,导入堆快照文件,定位“哪些对象没被回收、为什么没被回收”(如静态集合持有过期数据)。
2. 日志分析工具:聚焦“日志线索整合”
日志是线上问题的“第一手线索”,但分布式环境下日志分散在多台服务器,这类工具的核心是 “把分散日志变集中、变可查、变可用”。
- 日志输出框架(Log4j 2、Logback + SLF4J):
- 定位:“日志生产端”工具,负责规范日志输出格式、级别、目的地;
- 核心能力:支持异步日志(避免日志阻塞业务)、日志轮转(防止磁盘占满)、关联 traceID(便于分布式追踪);
- 关键要求:生产环境日志必须包含“时间戳、traceID、线程ID、日志级别、业务参数”,否则后续无法有效查询。
- 集中式日志平台(ELK Stack、Graylog、Splunk):
- 定位:“日志消费端”工具,负责日志的收集、存储、查询、可视化;
- 核心差异:
- ELK:开源、灵活、生态完善,但配置复杂,适合中大型团队;
- Graylog:开源、配置简单,适合中小型团队快速上手;
- Splunk:商业工具,功能强(含 AI 诊断、合规审计),适合大型企业;
- 典型场景:用户反馈“支付失败”,通过 ELK 搜索“支付相关 traceID”,快速找到支付服务的 ERROR 日志,定位是“第三方接口超时”还是“数据库插入失败”。
3. 性能分析与 APM 工具:聚焦“性能瓶颈定位”
性能问题(如接口 RT 高、吞吐量低)的难点是“不知道瓶颈在代码、还是在数据库、还是在第三方服务”,这类工具的核心是 “分层拆解性能耗时,精准定位瓶颈点”。
- 轻量级性能诊断工具(Arthas、Async Profiler):
- 特点:无侵入(无需重启应用)、聚焦“代码层性能”,是线上性能问题的“快速定位工具”;
- 核心能力:
- Arthas:实时看 JVM 仪表盘、追踪方法调用耗时(trace 命令)、查看方法入参/返回值(watch 命令)、生成 CPU 火焰图;
- Async Profiler:更轻量的 CPU/内存采样工具,生成的火焰图更精准,适合高并发场景(性能开销<1%);
- 典型场景:订单接口 RT 从 50ms 升至 500ms,用 Arthas 的
trace
命令追踪,发现是“订单查询方法未走缓存,每次查数据库”导致。
- 全链路 APM 工具(SkyWalking、Pinpoint、New Relic):
- 特点:聚焦“分布式架构下的全链路性能”,从“单个接口”扩展到“跨服务链路”;
- 核心能力:
- 链路拓扑:可视化展示“网关→订单服务→库存服务→数据库”的调用关系;
- 耗时拆解:统计每个服务/每个方法的耗时占比(如“库存服务占总耗时 60%”);
- 异常告警:当接口 RT 超阈值、错误率上升时,自动触发告警;
- 典型场景:微服务架构下,用户下单响应慢,通过 SkyWalking 看到“库存服务调用 Redis 超时”,进而定位是 Redis 集群负载过高。
4. 分布式追踪工具:聚焦“跨服务问题定位”
微服务架构下,一个请求会经过多个服务,这类工具的核心是 “用全局 traceID 串联多服务调用链路,明确问题归属”。
- 代表工具:Zipkin(轻量开源)、Jaeger(云原生友好)、SkyWalking(APM+追踪一体化);
- 核心逻辑:
- 请求进入系统时,生成唯一 traceID;
- 每个服务调用时,通过 HTTP 头/RPC 元数据传递 traceID;
- 每个服务将“调用耗时、状态”上报给追踪平台;
- 平台通过 traceID 聚合所有服务的调用数据,生成完整链路图;
- 典型场景:用户下单后“订单创建成功但库存未扣减”,通过 traceID 查看链路,发现“订单服务调用库存服务时,网络超时导致库存服务未收到请求”,明确是网络层问题。
三、工具选择与组合使用原则
按问题场景选工具:
- 内存泄漏/OOM:先⽤ jmap 导堆快照,再用 MAT 分析;
- 线程死锁/CPU 高:用 jstack 抓线程栈,或 Arthas 的
thread -b
定位; - 分布式接口超时:用 SkyWalking/Zipkin 查链路,再用 ELK 查对应服务日志;
- 代码层性能慢:用 Arthas 的
trace
或 Async Profiler 生成火焰图。
工具组合优于单一工具:
例如排查“支付接口慢”:- 先用 APM 工具(如 SkyWalking)看全链路耗时,发现“支付服务调用数据库慢”;
- 再用 Arthas 连接支付服务,
trace
数据库查询方法,确认是“SQL 未走索引”; - 最后用 ELK 搜索该 SQL 相关日志,查看是否有大量重复执行,验证问题影响范围。
兼顾“轻量”与“精准”:
- 线上紧急问题:优先用轻量工具(jstack、Arthas)快速止血;
- 复杂慢性问题(如内存泄漏):再用重量级工具(MAT、Splunk)深度分析。
通过以上工具体系,可覆盖 Java 线上从“JVM 底层”到“分布式链路”的绝大多数问题场景,核心是理解“每类工具的定位”,并根据实际问题灵活组合,避免“用错工具导致排查效率低下”。
以下通过 5 个典型线上问题场景,结合具体工具和操作步骤,演示 Java 线上问题排查的实践过程,帮助理解工具的实际应用:
场景 1:应用频繁 Full GC,导致服务卡顿
问题现象:用户反馈系统每隔几分钟就卡顿一次,监控显示 Full GC 频繁(每 2 分钟一次),每次耗时 3-5 秒。
排查目标:定位 Full GC 频繁的原因(内存泄漏/堆配置不合理/大对象)。
实践步骤:
用 jstat 确认 GC 状态
执行命令:jstat -gcutil <pid> 1000 10
(每 1 秒输出一次 GC 统计,共 10 次)
输出结果示例:S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 50.00 30.00 98.00 90.00 85.00 120 5.200 15 28.500 33.700
分析:老年代(O)使用率 98%,FGC 次数 15 次,FGCT 总耗时 28.5 秒,说明老年代内存不足,导致频繁 Full GC。
用 jmap 分析堆内存分布
执行命令:jmap -histo:live <pid> | head -20
(查看存活对象Top 20)
输出结果示例:num #instances #bytes class name ---------------------------------------------- 1: 500000 80000000 com.example.UserSession 2: 20000 16000000 [B 3: 15000 9600000 java.lang.String
分析:
UserSession
实例达 50 万个,占用 80MB 内存,怀疑是会话未及时清理。用 MAT 深度分析堆快照
- 导出堆快照:
jmap -dump:format=b,file=heap.hprof <pid>
- 用 MAT 打开
heap.hprof
,查看“Dominator Tree”:
发现UserSession
对象被com.example.SessionManager
的静态变量sessionMap
持有,且大部分UserSession
的lastAccessTime
是 24 小时前(已过期)。 - 结论:会话管理未实现过期清理机制,导致老年代被占满,触发频繁 Full GC。
- 导出堆快照:
解决措施:
在SessionManager
中添加定时任务,清理 30 分钟未活跃的UserSession
,重启应用后 Full GC 频率降至每 2 小时一次,服务卡顿消失。
场景 2:接口响应突然变慢(从 50ms 升至 1s)
问题现象:订单查询接口响应时间突增,监控显示接口 RT 从 50ms 升至 1s 以上,无报错日志。
排查目标:定位接口内部哪个步骤耗时增加。
实践步骤:
用 Arthas 追踪方法耗时
执行命令:trace com.example.OrderService queryOrder "{params, returnObj, costTime}" -n 5
(追踪queryOrder
方法,输出前 5 次调用的参数、返回值和耗时)
输出结果示例:`---ts=2023-10-01 10:00:00;thread_name=http-nio-8080-exec-1;id=1;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@123456 `---[1020ms] com.example.OrderService:queryOrder() +---[980ms] com.example.OrderDao:selectById() // 数据库查询耗时占比 96% +---[5ms] com.example.CacheService:get() `---returnObj=Order(id=123, ...)
分析:
OrderDao.selectById()
耗时 980ms,是性能瓶颈。检查数据库慢查询
查看数据库慢查询日志(假设 MySQL,已开启slow_query_log
),发现 SQL:
SELECT * FROM order WHERE user_id = 123;
(执行时间 970ms)
用EXPLAIN
分析:user_id
字段未建索引,导致全表扫描(表数据量 100 万行)。解决措施:
为order
表的user_id
字段添加索引,重新执行 SQL 耗时降至 10ms,接口 RT 恢复至 30ms。
场景 3:线程死锁导致服务无响应
问题现象:用户提交订单后无响应,应用日志停止输出,top
命令显示 Java 进程 CPU 使用率 10%(正常),但线程数异常高(500+)。
排查目标:确认是否存在线程死锁。
实践步骤:
用 jstack 抓线程栈
执行命令:jstack <pid> > thread.log
,查看thread.log
:
发现死锁提示:Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00007f1234567890 (object 0x000000076ab12345, a java.lang.Object), which is held by "Thread-2" "Thread-2": waiting to lock monitor 0x00007f123456abcd (object 0x000000076ab54321, a java.lang.Object), which is held by "Thread-1"
分析:
Thread-1
和Thread-2
互相持有对方需要的锁,导致死锁。定位死锁代码
从线程栈中找到具体代码行:Thread-1
栈信息:at com.example.OrderService.updateStatus(OrderService.java:156)
(持有锁 A,等待锁 B)Thread-2
栈信息:at com.example.OrderService.updateAmount(OrderService.java:203)
(持有锁 B,等待锁 A)
查看代码发现:两个方法分别以“锁 A→锁 B”和“锁 B→锁 A”的顺序获取锁,导致高并发下死锁。
解决措施:
统一锁获取顺序(均按“锁 A→锁 B”获取),重启应用后死锁消失,服务恢复响应。
场景 4:分布式链路调用超时(微服务架构)
问题现象:用户下单后提示“系统繁忙”,APM 工具显示“订单服务→库存服务”调用超时(5 秒未响应)。
排查目标:确定超时是库存服务自身慢,还是网络问题,还是下游依赖问题。
实践步骤:
用 SkyWalking 查看全链路追踪
在 SkyWalking 控制台搜索该请求的traceID
,链路图显示:- 订单服务→库存服务:耗时 5000ms(超时)
- 库存服务内部:
checkStock
方法耗时 4900ms
分析:超时发生在库存服务内部,非网络问题。
用 ELK 查库存服务日志
在 Kibana 中搜索service:stock-service AND traceID:xxx
,发现日志:
[ERROR] [checkStock] 调用 Redis 超时,key=stock:123, cost=4900ms
分析:库存服务查询 Redis 超时,导致整体调用超时。检查 Redis 状态
执行redis-cli info stats
查看 Redis 状态,发现total_commands_processed
突增,instantaneous_ops_per_sec
达 10 万+(远超 Redis 承载能力),且存在大量KEYS *
命令(耗时操作)。
定位:某定时任务每 10 秒执行一次KEYS *
扫描全量键,导致 Redis 阻塞。解决措施:
将KEYS *
替换为SCAN
分批扫描,Redis 性能恢复,库存服务调用耗时降至 20ms,下单流程正常。
场景 5:应用内存泄漏(OOM 前预警)
问题现象:监控显示应用老年代内存持续增长(每天增加 100MB),无明显下降,预计 3 天后会 OOM。
排查目标:找到内存泄漏的对象及原因。
实践步骤:
对比不同时间点的堆快照
- 第一天导出堆快照:
jmap -dump:format=b,file=heap1.hprof <pid>
- 第二天同一时间导出堆快照:
jmap -dump:format=b,file=heap2.hprof <pid>
- 用 MAT 对比两个快照(“Compare Heap Dumps”功能),发现
com.example.LogFile
实例从 100 个增至 10000 个,内存占用增加 90MB。
- 第一天导出堆快照:
分析
LogFile
对象引用链
在 MAT 中查看LogFile
的“Path To GC Roots”,发现其被com.example.LogManager
的files
集合持有,且LogFile
的inputStream
未关闭(文件句柄泄漏)。
代码定位:LogManager
打开日志文件后,仅读取内容但未调用close()
方法,导致LogFile
对象无法被 GC 回收。解决措施:
在LogFile
类中添加try-with-resources
自动关闭流,修复后老年代内存不再增长,避免 OOM 发生。
总结:实践排查的核心思路
- 先看监控定方向:通过 APM、GC 监控、系统指标(CPU/内存)快速锁定问题大类(性能/内存/线程/分布式)。
- 工具组合找线索:命令行工具(jstack/jmap)快速定位,图形化工具(MAT/Arthas)深度分析,日志平台(ELK)关联上下文。
- 从现象到代码:通过工具定位到具体方法/SQL/依赖,再结合代码逻辑找到根本原因(如锁顺序错误、资源未释放、索引缺失)。
每个问题的排查都是“现象→工具→数据→结论”的循环,熟练掌握工具的核心命令和分析方法,能大幅提升排查效率。