面试追问地图
| 主问题 | 必讲关键点 | 下一层追问 |
|---|---|---|
| 通用排障 | 影响、止损、分层、证据、复盘 | 为什么先保现场、何时重启 |
| CPU 高 | 进程、线程、栈、代码热点 | GC 高还是业务计算高 |
| 线程池耗尽 | 活跃数、队列、阻塞点 | 扩大线程池为何可能更糟 |
| 内存上涨 | 堆/非堆/native、引用链 | 泄漏与正常缓存如何区分 |
| Full GC | 分配、晋升、回收效果 | 大对象、元空间、晋升失败 |
| 接口超时 | 分位延迟、Trace、池化资源 | 单实例/全局、重试放大 |
| 慢 SQL | 慢日志、计划、扫描、锁 | 参数不同为何计划不同 |
| Redis 延迟 | 慢命令、大 Key、fork、网络 | 单分片热点如何迁移 |
| MQ 积压 | 生产/消费速率、失败消息 | 临时扩容后的顺序和幂等 |
| 发布故障 | 变更对比、灰度、回滚 | 数据库变更无法回滚怎么办 |
| 死锁排查 | 线程 dump、锁顺序、等待图 | 死锁 vs 活锁、jstack 分析 |
排障题必须给出“下一条命令或下一张图看什么”,避免只说笼统的检查日志和监控。
一、通用排障方法
线上问题的通用排查流程?
难度 🔴
确认现象 → 判断影响 → 先止损 → 分层定位 → 验证根因 → 修复与复盘- 确认现象:时间范围、错误码、受影响接口、用户和机房。
- 判断影响:全部请求还是部分请求,单实例还是集群。
- 先止损:回滚、降级、限流、扩容、切流或关闭非核心功能。
- 分层定位:客户端、网关、应用、缓存、数据库、MQ、下游、基础设施。
- 验证根因:用日志、指标、Trace、复现或对照实验形成证据链。
- 修复复盘:区分临时恢复和长期治理。
排障时先保留现场,避免一开始就重启服务导致关键证据丢失。
如何快速判断问题在哪一层?
| 现象 | 优先检查 |
|---|---|
| 所有接口都慢 | CPU、GC、线程池、网络、公共下游 |
| 单接口慢 | Trace、业务日志、SQL、远程调用 |
| 单实例异常 | 实例日志、机器资源、配置差异 |
| 集群整体异常 | 流量、依赖服务、数据库、缓存 |
| 错误率突然上涨 | 发布变更、配置变更、下游故障 |
| 延迟上涨但错误率稳定 | 慢 SQL、锁竞争、连接池、资源饱和 |
排障为什么要同时看日志、指标和 Trace?
- 日志:解释具体请求发生了什么。
- 指标:判断问题规模、趋势和资源瓶颈。
- Trace:定位调用链中耗时或报错的节点。
三者结合才能从“哪里异常”走到“为什么异常”。
二、CPU 与线程问题
Java 进程 CPU 飙高怎么排查?
top 找进程
↓
top -H -p <pid> 找高 CPU 线程
↓
将线程 ID 转为十六进制
↓
jstack <pid> 中匹配 nid
↓
结合代码、火焰图和调用频率定位常见原因:
- 死循环或高频空轮询。
- 正则表达式回溯。
- 大量序列化、压缩或加密计算。
- 频繁 GC。
- 锁竞争导致线程自旋。
- 流量突增或批处理任务同时执行。
线程池耗尽有什么表现?
- 活跃线程数达到最大值。
- 队列持续增长或拒绝任务。
- 接口延迟上涨,CPU 不一定很高。
- 大量线程停在远程调用、数据库或锁等待。
处理顺序:
- 找出线程在等待什么。
- 检查下游超时、连接池和慢 SQL。
- 必要时限流、降级或隔离故障依赖。
- 不要只通过扩大线程池掩盖阻塞问题。
如何排查死锁?
jstack查看Found one Java-level deadlock。- 分析线程持有的锁和等待的锁。
- 统一加锁顺序,缩小锁粒度。
- 避免锁内执行网络调用或长耗时操作。
- 必要时使用带超时的
tryLock。
三、内存与 GC
内存持续上涨怎么排查?
先区分:
- Java 堆。
- 元空间。
- 直接内存。
- 线程栈。
- native 内存或 mmap。
常用步骤:
- 查看堆、GC 和进程 RSS 趋势。
- 使用
jstat判断各代增长和 GC 频率。 - 使用
jmap或诊断工具导出堆快照。 - 用 MAT 分析大对象、引用链和支配树。
- 检查缓存、集合、ThreadLocal、监听器和类加载器。
频繁 Full GC 怎么排查?
常见原因:
- 老年代空间不足。
- 大对象直接进入老年代。
- 元空间不足触发回收。
- 内存泄漏导致对象无法释放。
- 堆设置不合理或晋升失败。
不能只调大堆。应结合 GC 日志确认对象分配速率、晋升情况、停顿原因和回收效果。
OOM 发生后要保留什么信息?
- OOM 类型和完整异常栈。
- 堆转储文件。
- GC 日志。
- 当时的流量、发布和配置变更。
- 进程内存、容器限制和系统日志。
建议提前配置 OOM 自动导出堆快照,但要预留磁盘空间并保护敏感数据。
四、接口超时与依赖故障
接口突然变慢怎么排查?
难度 🔴
- 查看延迟分位数、错误率和流量是否同时变化。
- 通过 Trace 拆分网关、应用、SQL、缓存和远程调用耗时。
- 检查线程池、连接池和队列是否饱和。
- 检查慢 SQL、锁等待、缓存未命中和下游超时。
- 核对近期发布、配置、数据量和流量变化。
重试为什么可能放大故障?
下游变慢时,如果每层都立即重试,会增加下游负载并占用更多线程,形成重试风暴。
治理方式:
- 设置明确的连接和读取超时。
- 只对可重试错误重试。
- 限制次数,采用指数退避和随机抖动。
- 保证写操作幂等。
- 配合熔断、限流和隔离。
连接池耗尽怎么处理?
排查:
- 活跃连接、空闲连接和等待线程数。
- 是否存在连接泄漏。
- SQL 是否变慢或事务持有连接过久。
- 数据库最大连接数是否达到上限。
连接池大小不是越大越好,过大会把压力转移到数据库。HikariCP 的连接数公式、参数体系与 maxLifetime 陷阱见MySQL。
五、数据库、缓存与消息队列
慢 SQL 的排查路径?
慢查询日志 → EXPLAIN → 扫描行数/索引 → 锁等待 → SQL 与表结构优化重点检查:
- 是否命中合适索引。
- 是否出现全表扫描、回表过多或临时表。
- 返回数据是否过大。
- 是否存在大事务和锁等待。
- 数据分布变化是否导致执行计划变化。
Redis 延迟突然上涨怎么排查?
- 慢命令和大 Key。
- 热 Key 导致单分片过载。
- 持久化 fork 或磁盘抖动。
- 网络和连接数。
- 集中过期或缓存穿透。
- 集群迁移、故障转移和客户端重试。
MQ 消息积压怎么处理?
- 判断生产速度、消费速度和积压增长趋势。
- 检查消费端错误、慢依赖、线程池和批量参数。
- 临时增加消费者或分区,但先确认顺序性和下游承载能力。
- 对失败消息隔离,避免单条坏消息阻塞。
- 消费端必须保证幂等,并监控消费延迟。
六、发布与复盘
发布后错误率上涨怎么办?
- 立即对比发布前后指标。
- 判断是否只影响新版本实例。
- 满足回滚条件时优先回滚。
- 检查数据库变更、配置兼容和缓存格式。
- 修复后重新灰度,不直接全量发布。
什么是有效告警?
有效告警应做到:
- 与用户影响或系统容量相关。
- 有明确阈值、持续时间和告警级别。
- 包含服务、实例、时间和排查入口。
- 能聚合重复告警,避免告警风暴。
- 有负责人和处理手册。
故障恢复后为什么还要复盘?
恢复只解决当前问题,复盘需要解决系统性缺陷:
- 监控为什么没有提前发现。
- 降级和回滚为什么没有自动生效。
- 测试和压测为什么没有覆盖。
- 相同模式是否存在于其他服务。