云计算百科
云计算领域专业知识百科平台

项目排查类场景案例

中高级 Java 项目场景题:排查与解决方案

适用场景:中高级 Java 开发面试中,面试官常问“线上遇到 CPU 飙高、接口变慢、Full GC、慢 SQL、MQ 积压,你怎么排查?” 回答核心:不要一上来猜原因,要按 现象确认 -> 影响范围 -> 分层排查 -> 定位根因 -> 临时止损 -> 长期优化 来讲。


场景题分类目录

  • 一、接口性能与服务稳定性类:接口变慢、线程池打满、长尾延迟、504、健康检查、Tomcat 线程、服务雪崩、下游隔离

  • 二、JVM、内存与线程问题类:Full GC、频繁 GC、堆外内存、线程数上涨、OOM、Metaspace

  • 三、MySQL 与数据库问题类:慢 SQL、连接池、锁等待、死锁、主从延迟、分库分表、大事务

  • 四、Redis 与缓存问题类:Redis 变慢、缓存三大问题、内存打满、热点 key、缓存一致性

  • 五、MQ 消息队列问题类:消息积压、重复消费、消息丢失、顺序消费、死信队列、分区积压

  • 六、并发、幂等与分布式一致性类:分布式锁、重复提交、分布式事务、库存、状态机、余额订单一致性、超级 SIM 计费去重

  • 七、发布、配置与微服务架构类:发布故障、灰度兼容、配置中心、注册中心、多机房

  • 八、批处理、任务与运维故障类:定时任务、大数据导入、日志磁盘打满

  • 九、线上问题复盘类:线上事故复盘表达


一、接口性能与服务稳定性类

1. CPU 飙高,接口变慢怎么排查?

面试官常见问法

线上某个服务 CPU 突然飙到 100%,接口响应也变慢了,你会怎么排查?

回答思路

我会先确认 CPU 飙高是单台机器问题,还是整个集群都高。 如果是单台机器高,优先看这台机器上的 Java 进程和线程;如果是整个集群都高,再结合流量、发布、依赖调用一起排查。

排查步骤

  • 先看监控

    • CPU 是瞬时升高还是持续升高

    • 接口 RT、QPS、错误率是否同时异常

    • 是否刚好有发布、定时任务、流量突增

  • 定位 Java 进程

  • top

    找到 CPU 占用最高的 Java 进程 PID。

  • 定位高 CPU 线程

  • top -Hp <pid>

    找到占用 CPU 最高的线程 ID。

  • 线程 ID 转十六进制

  • printf "%x\\n" <tid>

  • 导出线程栈

  • jstack <pid> > thread.log

    在 thread.log 中搜索十六进制线程 ID,查看线程正在执行哪段代码。

    常见根因

  • 代码死循环或递归退出条件异常

  • 大量 JSON 序列化、加密、压缩等 CPU 密集型操作

  • 正则表达式回溯严重

  • 频繁 Full GC 导致 CPU 被 GC 线程占用

  • 锁竞争严重,大量线程自旋或上下文切换

  • 突发流量导致线程池任务暴增

  • 解决方案

    如果是代码死循环,就修复循环条件并补充边界测试。 如果是大对象序列化或复杂计算,就优化算法、减少重复计算,必要时做异步化。 如果是 GC 导致 CPU 高,就继续分析 GC 日志和堆快照。 如果是流量突增,就先限流、扩容,再做容量评估。

    面试回答模板

    我会先看监控确认 CPU 是单机高还是集群都高,同时看接口 RT、QPS、错误率有没有同步变化。 如果是 Java 进程 CPU 高,我会用 top 找进程,再用 top -Hp 找具体线程,把线程 ID 转成十六进制后,用 jstack 定位线程栈。 通过线程栈判断是死循环、复杂计算、锁竞争,还是 GC 线程占用 CPU。 定位根因后,短期先限流、摘流量或扩容止损,长期再修代码、优化算法、调整 JVM 或补充监控。

    记忆点

    top 找进程 -> top -Hp 找线程 -> jstack 看代码 -> 判断业务线程还是 GC 线程




    2. 线上接口突然变慢怎么排查?

    面试官常见问法

    用户反馈接口很慢,平时 100ms,现在要 3 秒,你怎么排查?

    回答思路

    接口变慢不能先猜数据库问题,要先判断是单个接口慢,还是整个服务都慢。 然后沿调用链拆分:网关、应用、数据库、Redis、MQ、第三方接口。

    排查步骤

  • 看监控确认范围

    • 是单接口慢,还是所有接口慢

    • 是单实例慢,还是整个服务慢

    • QPS 是否突增

    • 错误率是否上升

  • 看链路耗时

    • 网关耗时

    • 应用内部耗时

    • MySQL 查询耗时

    • Redis 耗时

    • RPC 或 HTTP 下游接口耗时

  • 看应用指标

    • CPU、内存、GC

    • 线程池活跃数、队列长度、拒绝次数

    • Tomcat 线程数是否打满

  • 看依赖指标

    • MySQL 是否有慢 SQL、锁等待、连接池耗尽

    • Redis 是否有慢命令、大 key、网络抖动

    • 下游服务是否超时

  • 常见根因

  • 慢 SQL 或数据库锁等待

  • Redis 大 key 或缓存命中率下降

  • 下游接口超时,把业务线程拖住

  • 线程池或 Tomcat 连接数打满

  • JVM Full GC

  • 新版本发布引入性能问题

  • 突发流量超过系统容量

  • 解决方案

    短期先止损,比如限流、降级、扩容、回滚、摘除异常实例。 长期根据根因处理,比如优化 SQL、增加缓存、设置超时时间、隔离线程池、补充熔断降级、优化 JVM 参数。

    面试回答模板

    我会先判断是单个接口慢,还是整个服务慢;是单台机器慢,还是所有实例都慢。 然后看链路追踪,把耗时拆成网关、应用、数据库、缓存和下游服务几个部分。 如果慢在应用层,就看 CPU、GC、线程池和日志;如果慢在数据库,就看慢 SQL、执行计划和锁等待;如果慢在下游,就看超时、重试和熔断配置。 线上处理时会先止损,比如限流、降级、扩容或回滚,之后再做根因修复。

    记忆点

    先定范围 -> 再拆链路 -> 找最慢一段 -> 先止损再优化




    3. 线程池打满怎么排查?

    面试官常见问法

    业务线程池队列堆积,甚至触发拒绝策略,你怎么处理?

    回答思路

    线程池打满要先判断是流量太大,还是任务执行太慢。 然后看线程都卡在哪里,是数据库、Redis、下游接口、锁,还是本地计算。

    排查步骤

  • 看线程池指标

    • corePoolSize

    • maximumPoolSize

    • activeCount

    • queueSize

    • rejectCount

    • task 耗时

  • 导出线程栈

  • jstack <pid> > thread.log

  • 分析线程状态

    • RUNNABLE:可能在执行计算、IO 或数据库驱动

    • WAITING:可能在等锁、队列或条件变量

    • BLOCKED:锁竞争

    • 大量线程卡在同一个下游调用:下游慢或超时配置不合理

  • 常见根因

  • 请求量突增

  • 任务执行耗时变长

  • 下游接口慢,线程一直等待

  • 数据库慢 SQL

  • 锁竞争严重

  • 队列设置过大,问题暴露太晚

  • 线程池参数配置不合理

  • 解决方案

  • 给下游调用设置合理超时时间

  • 增加熔断、降级、限流

  • 按业务类型隔离线程池

  • 优化慢任务或改异步处理

  • 调整线程池参数和队列长度

  • 增加线程池监控和拒绝告警

  • 面试回答模板

    我会先看线程池的活跃线程数、队列长度和拒绝次数,判断是流量上来了,还是任务变慢了。 然后用 jstack 看线程都卡在哪里,如果大量线程卡在数据库或下游接口,就继续查对应依赖;如果是锁竞争,就看锁对象和临界区代码。 处理上短期可以限流、扩容或降级,长期会做线程池隔离、超时控制、熔断降级和任务耗时优化。

    记忆点

    看活跃数和队列 -> jstack 看线程卡哪 -> 下游慢就隔离和超时




    4. 服务雪崩怎么处理?

    面试官常见问法

    某个下游服务变慢,导致你的服务也被拖垮,你怎么处理?

    回答思路

    雪崩处理的第一原则是先止损,不要让故障继续扩散。

    常见根因

  • 下游接口响应慢

  • 调用方没有设置超时

  • 失败后大量重试

  • 所有业务共用同一个线程池

  • 没有限流、熔断、降级

  • 解决方案

  • 设置合理超时时间

  • 限制重试次数,避免重试风暴

  • 使用熔断,失败率高时快速失败

  • 对非核心业务降级

  • 按业务隔离线程池

  • 核心链路做限流保护

  • 建立依赖服务监控和告警

  • 面试回答模板

    如果出现雪崩,我会先止损,比如限流、降级、熔断、摘流量或临时关闭非核心功能。 然后看是不是某个下游服务变慢,导致线程池、连接池被占满。 长期会给所有远程调用设置超时和熔断,限制重试次数,并按核心和非核心业务做线程池隔离,避免一个依赖拖垮整个服务。

    记忆点

    先止损,再定位;超时、熔断、限流、隔离是核心




    5. 接口偶发超时,但重试又成功怎么排查?

    面试官常见问法

    某个接口不是一直慢,而是偶发超时,用户重试后又成功了,你怎么排查?

    回答思路

    偶发超时要重点看 P95、P99、慢请求日志和依赖抖动。 平均 RT 正常不代表系统没问题,可能只有少量请求被慢 SQL、GC、锁等待或下游超时拖住。

    排查方向

  • 看慢请求发生时间是否集中

  • 对比 P95、P99 和平均 RT

  • 查链路追踪中慢请求卡在哪一段

  • 看是否发生 Young GC、Full GC 或线程池排队

  • 看数据库是否有瞬时锁等待或慢 SQL

  • 看下游接口是否偶发抖动

  • 看是否存在连接池获取连接超时

  • 面试回答模板

    我会先看慢请求的时间分布和 P99 指标,因为偶发超时通常平均 RT 看不出来。 然后通过 trace 把慢请求单独拉出来,分析它是卡在应用线程池、数据库、Redis,还是下游接口。 如果重试能成功,说明可能是瞬时抖动,比如连接池等待、GC、锁等待或下游超时。 解决上会补充超时控制、重试退避、熔断降级和慢请求日志。

    记忆点

    偶发超时看 P99,单独分析慢请求链路




    6. 接口 P99 很高,但平均 RT 正常怎么分析?

    面试官常见问法

    接口平均响应时间正常,但是 P99 很高,说明什么?

    回答思路

    P99 高说明少量请求很慢,用户体验可能已经受影响。 这种问题通常是长尾请求,要按请求特征拆分,比如参数、用户、数据量、路由实例、依赖调用。

    常见根因

  • 某些大客户或大数据量请求特别慢

  • 某个实例负载异常

  • 部分请求命中慢 SQL

  • 缓存未命中后回源很慢

  • 下游接口长尾延迟

  • 线程池排队造成少量请求等待很久

  • 面试回答模板

    我会把 P99 慢请求样本捞出来,按接口参数、用户类型、数据量、实例 IP 和链路耗时做聚合。 如果发现慢请求集中在某类参数,可能是数据量问题;如果集中在某台机器,可能是单机负载或 GC;如果集中在某个依赖,就继续查数据库、Redis 或下游服务。 优化时要重点治理长尾,比如限制分页大小、优化大客户查询、隔离慢依赖、设置超时。

    记忆点

    平均值看整体,P99 看长尾




    7. QPS 不高,但接口还是很慢可能是什么原因?

    面试官常见问法

    系统流量不高,但接口很慢,你会怎么排查?

    回答思路

    QPS 不高还慢,通常不是容量不够,而是单次请求耗时长或资源被占住。

    常见根因

  • 慢 SQL 或锁等待

  • 远程接口超时

  • 连接池太小或连接泄漏

  • 线程池队列积压

  • GC 停顿

  • 单次请求处理数据量太大

  • 日志打印过多或同步 IO 阻塞

  • 面试回答模板

    QPS 不高但接口慢,我会先看链路耗时,确认慢在应用内部还是外部依赖。 这种情况一般不是简单扩容能解决,而是某些资源被长期占用,比如数据库连接、业务线程、锁或者下游调用。 我会重点查慢 SQL、锁等待、线程栈、连接池等待和 GC 日志,找到请求到底在等什么。

    记忆点

    QPS 不高还慢,重点看等待和单请求耗时




    8. 接口大量 504 怎么判断是网关问题还是服务问题?

    面试官常见问法

    线上大量 504,怎么判断问题在网关还是后端服务?

    回答思路

    504 通常表示网关等待后端超时。 要对比网关日志、后端 access log、应用日志和链路追踪。

    排查步骤

  • 看网关是否成功转发到后端

  • 看后端是否收到请求

  • 看后端处理耗时是否超过网关超时时间

  • 看网关到服务之间是否有网络异常

  • 看服务实例是否健康、线程是否打满

  • 面试回答模板

    我会先从网关日志里拿到请求 ID,看请求有没有转发到后端。 如果后端没收到,可能是网关路由、注册中心或网络问题;如果后端收到了但处理时间超过网关超时,就是服务慢。 接着会看后端线程池、数据库、Redis、下游调用和 GC,确认服务为什么没及时返回。 短期可以扩容、限流或调大超时,但长期还是要优化后端耗时。

    记忆点

    后端没收到查网关,后端收到了查服务耗时




    9. 服务健康检查正常,但业务接口不可用怎么排查?

    面试官常见问法

    健康检查接口正常,但真实业务接口大量失败,说明什么?

    回答思路

    健康检查正常只能说明进程还活着,不代表业务依赖可用。 要看业务接口依赖的数据库、缓存、MQ、下游服务是否异常。

    常见根因

  • 健康检查太简单,只返回 ok

  • 数据库连接池耗尽

  • Redis 或下游服务不可用

  • 某个业务线程池打满

  • 配置错误导致业务逻辑失败

  • 依赖权限或 token 过期

  • 面试回答模板

    我不会只看健康检查,因为很多健康检查只是证明应用进程存活。 我会看业务接口的错误日志和链路追踪,确认是哪个依赖失败。 如果业务依赖不可用,可以把健康检查分成存活检查和就绪检查,就绪检查要覆盖核心依赖状态。 这样服务不可用时,注册中心或网关可以及时摘流量。

    记忆点

    进程活着不等于业务可用




    10. 线上大量 Connection reset 或 Broken pipe 怎么分析?

    面试官常见问法

    日志里大量 Connection reset、Broken pipe,怎么排查?

    回答思路

    这类异常通常和连接被对端关闭有关,要结合客户端、服务端、网关和超时配置分析。

    常见根因

  • 客户端超时主动断开

  • 网关超时关闭连接

  • 服务端响应太慢

  • 连接池中的连接已经失效

  • 网络抖动或负载均衡切连接

  • 大响应体传输过程中客户端取消

  • 面试回答模板

    我会先看异常发生在调用方还是被调用方,再结合请求耗时和超时配置判断。 如果服务端还没写完响应,客户端或网关已经超时关闭连接,就可能出现 Broken pipe。 如果复用连接时对端已经关闭,可能出现 Connection reset。 解决上要统一网关、客户端、服务端超时时间,优化慢接口,并检查连接池保活配置。

    记忆点

    连接被对端关了,重点查超时和慢响应




    11. Tomcat 线程数打满但 CPU 不高说明什么?

    面试官常见问法

    Tomcat 线程打满了,但是 CPU 不高,可能是什么问题?

    回答思路

    CPU 不高说明线程大概率不是在计算,而是在等待。 要用线程栈看线程等待的是数据库、Redis、下游接口、锁还是连接池。

    常见根因

  • 下游接口响应慢

  • 数据库连接池获取连接慢

  • SQL 锁等待

  • Redis 连接池耗尽

  • synchronized 锁竞争

  • 外部 IO 阻塞

  • 面试回答模板

    Tomcat 线程打满但 CPU 不高,我会优先怀疑线程都阻塞在 IO 或锁等待上。 我会导出 jstack,看大量业务线程是不是卡在数据库、Redis、HTTP 调用或锁上。 如果是下游慢,要设置超时、熔断和线程池隔离;如果是数据库连接池等待,要继续查慢 SQL、大事务和连接池配置。

    记忆点

    CPU 不高线程满,多半是在等资源




    12. 下游服务变慢导致上游线程池被拖垮怎么隔离?

    面试官常见问法

    一个非核心下游变慢,把你的核心接口也拖慢了,怎么设计?

    回答思路

    核心是隔离,不同业务、不同依赖不要共用同一批线程和连接资源。

    解决方案

  • 核心和非核心业务线程池隔离

  • 不同下游使用独立连接池

  • 设置超时、熔断和降级

  • 非核心功能失败时快速返回默认值

  • 限制重试次数,避免重试风暴

  • 面试回答模板

    我会先看线程池和连接池是不是被某个下游调用占满。 如果核心接口和非核心接口共用线程池,一个下游慢就会拖垮所有业务。 解决上会按业务或依赖拆分线程池和连接池,并设置超时、熔断、降级。 非核心依赖异常时快速失败或返回兜底结果,保护核心链路。

    记忆点

    隔离资源,非核心失败不能拖垮核心



    二、JVM、内存与线程问题类

    13. Full GC 频繁怎么排查?

    面试官常见问法

    线上服务频繁 Full GC,接口卡顿,你怎么排查?

    回答思路

    Full GC 频繁要先看 GC 日志和内存曲线,判断是老年代空间不足、对象存活太久,还是存在内存泄漏。

    排查步骤

  • 看监控

    • Full GC 次数

    • Full GC 耗时

    • 堆内存使用率

    • Full GC 后内存是否明显下降

  • 查看 GC 日志

  • jstat -gcutil <pid> 1000

    观察年轻代、老年代、元空间使用情况。

  • 导出堆快照

  • jmap -dump:format=b,file=heap.hprof <pid>

  • 使用 MAT 或 VisualVM 分析

    • 哪些对象占用内存最多

    • 哪些对象数量异常

    • 谁持有这些对象引用

    • 是否有集合、缓存、ThreadLocal 没释放

  • 常见根因

  • 本地缓存没有过期策略,数据持续增长

  • 大集合一次性加载过多数据

  • ThreadLocal 使用后没有 remove

  • 查询结果过大,导致大对象进入老年代

  • JVM 堆配置不合理

  • 动态代理或反射生成类过多导致元空间压力

  • 解决方案

    如果是内存泄漏,就根据引用链修复代码。 如果是缓存无上限,就设置最大容量和过期时间。 如果是大查询,就改成分页、游标或流式处理。 如果是堆配置不合理,就结合实际对象存活情况调整 JVM 参数。

    面试回答模板

    我会先看 Full GC 的频率和耗时,再看 Full GC 之后堆内存能不能降下来。 如果降不下来,我会优先怀疑内存泄漏,导出 heap dump 用 MAT 分析大对象和引用链。 如果能降下来但很快又涨上去,就看是不是大对象太多、缓存过大、批量查询数据太多,或者堆设置偏小。 解决上会针对根因处理,比如限制缓存大小、修复 ThreadLocal、分页处理大数据,并结合 GC 日志调整 JVM 参数。

    记忆点

    看 GC 后内存降不降 -> 不降查泄漏 -> 能降查大对象和堆配置




    14. 内存没有明显上涨,但频繁 GC 怎么排查?

    面试官常见问法

    堆内存看起来没有持续上涨,但 GC 很频繁,可能是什么原因?

    回答思路

    频繁 GC 不一定是内存泄漏,也可能是对象创建太快,年轻代压力大。

    常见根因

  • 瞬时对象创建过多

  • 年轻代配置太小

  • 大量临时集合、字符串拼接、JSON 对象

  • 接口 QPS 突增

  • 日志或序列化产生大量临时对象

  • 面试回答模板

    我会先看 GC 类型,如果是 Young GC 频繁但 Full GC 不多,通常说明短生命周期对象创建太快。 这时会结合接口 QPS、对象分配速率和代码热点,检查是否有大量临时集合、JSON 转换或字符串处理。 优化上可以减少不必要对象创建、复用对象、优化序列化,并结合实际情况调整年轻代大小。

    记忆点

    内存不涨但 GC 频繁,重点看对象分配速率




    15. 堆外内存溢出怎么定位?

    面试官常见问法

    Java 堆不高,但进程内存一直涨,最后被系统 kill,怎么排查?

    回答思路

    堆不高但进程内存高,要看堆外内存、直接内存、线程栈、Metaspace 和 native 内存。

    常见根因

  • Netty DirectByteBuffer 泄漏

  • NIO 直接内存使用过多

  • 线程数太多导致线程栈占用大

  • Metaspace 增长

  • JNI 或本地库内存泄漏

  • 面试回答模板

    我会先区分 JVM 堆内存和进程 RSS,如果堆不高但 RSS 很高,就怀疑堆外内存。 可以开启 Native Memory Tracking,用 jcmd VM.native_memory 看 native 内存分布。 如果是 Netty 或 NIO 场景,会重点查 DirectByteBuffer 和引用释放。 同时看线程数量、Metaspace 和本地库使用情况。

    记忆点

    堆不高 RSS 高,重点查堆外和线程栈




    16. 线程数持续上涨,最后服务不可用怎么排查?

    面试官常见问法

    服务线程数一直涨,最后创建线程失败,怎么办?

    回答思路

    线程数持续上涨通常是线程池使用不规范、定时任务重复创建线程,或者连接没有释放。

    常见根因

  • 每次请求都 new Thread

  • 线程池没有复用或没有关闭

  • 定时任务重复创建线程池

  • 第三方 SDK 创建大量线程

  • 阻塞调用导致线程长期不释放

  • 面试回答模板

    我会先看线程数量曲线,再用 jstack 看线程名称和线程栈。 如果线程名称集中在某个业务前缀或第三方 SDK,就能定位来源。 代码层面要禁止请求内直接创建线程,统一使用受控线程池,并设置线程池监控。 如果线程长期阻塞,还要继续查它卡在数据库、HTTP 调用还是锁等待。

    记忆点

    线程数上涨,看线程名和线程栈定位来源




    17. 线上 OOM 后 dump 文件太大怎么处理?

    面试官常见问法

    线上 OOM 了,dump 文件很大,直接拷贝和分析都很困难,怎么办?

    回答思路

    先保留现场,但也要尽快恢复服务。 大 dump 可以在服务器本地压缩、抽样分析,或者用更高配置机器分析。

    处理方案

  • 配置 OOM 自动 dump

  • dump 后先压缩保存

  • 业务先重启或摘流量恢复

  • 使用 MAT、VisualVM 或 Eclipse Memory Analyzer 分析

  • 先看 histogram、大对象和引用链

  • 如果 dump 太大,在高内存机器上分析

  • 面试回答模板

    线上 OOM 我会先确认是否已经生成 heap dump,并保留 GC 日志和应用日志。 如果 dump 很大,会先压缩保存,必要时放到更高内存的机器上用 MAT 分析。 分析时先看对象直方图和 dominator tree,找到占用最大的对象和引用链。 同时线上先通过重启、摘流量或扩容恢复服务。

    记忆点

    OOM 先保现场,再恢复服务,dump 看大对象和引用链




    18. Metaspace OOM 怎么分析?

    面试官常见问法

    线上报 OutOfMemoryError: Metaspace,怎么排查?

    回答思路

    Metaspace 存的是类元数据,常见原因是动态生成类太多或类加载器泄漏。

    常见根因

  • CGLIB 动态代理大量生成类

  • 反射或表达式引擎动态生成类

  • 热部署导致类加载器无法回收

  • Groovy、JSP、脚本引擎生成类过多

  • Metaspace 配置过小

  • 面试回答模板

    我会先看 Metaspace 使用曲线和类加载数量是否持续上涨。 如果类数量一直增加,就怀疑动态生成类过多或类加载器泄漏。 可以通过 dump 和类加载统计看哪些类数量异常。 解决上要修复动态生成类逻辑,避免重复生成,必要时调整 MaxMetaspaceSize,但参数调整不是根本方案。

    记忆点

    Metaspace OOM 看类数量和类加载器



    三、MySQL 与数据库问题类

    19. 慢 SQL 怎么排查和优化?

    面试官常见问法

    线上接口慢,最后发现是 SQL 慢,你怎么定位和优化?

    回答思路

    慢 SQL 不能只说加索引,要先通过慢查询日志定位 SQL,再用 EXPLAIN 看执行计划。

    排查步骤

  • 开启或查看慢查询日志

    • SQL 执行时间

    • 扫描行数

    • 返回行数

    • 执行频率

  • 使用 EXPLAIN

  • EXPLAIN SELECT …

    重点看:

  • type 是否是 ALL

  • key 是否使用索引

  • rows 扫描行数是否过大

  • Extra 是否出现 Using filesort、Using temporary

  • 是否出现回表、索引失效、隐式转换

  • 常见根因

  • 查询条件没有索引

  • 联合索引顺序不合理

  • 对索引字段使用函数导致索引失效

  • 字段类型不一致导致隐式转换

  • like '%xxx' 无法使用普通索引

  • 深分页导致扫描大量数据

  • select * 导致回表和网络传输变多

  • 大事务或锁等待导致 SQL 变慢

  • 解决方案

  • 为高频查询设计合适索引

  • 使用覆盖索引减少回表

  • 调整联合索引顺序,遵守最左前缀原则

  • 避免在索引字段上使用函数或隐式转换

  • 深分页改成基于 ID 的延迟关联或游标分页

  • 减少 select *,只查询必要字段

  • 拆分大事务,减少锁持有时间

  • 面试回答模板

    我会先从慢查询日志里定位具体 SQL,然后用 EXPLAIN 看执行计划。 重点看有没有走索引、扫描行数大不大、有没有文件排序和临时表,以及是否存在锁等待。 如果是索引问题,就根据查询条件和排序字段设计联合索引;如果是深分页,就改成基于主键的分页;如果是锁等待,就继续排查事务范围和更新顺序。 优化后还要对比执行计划和接口 RT,确认优化确实生效。

    记忆点

    慢日志定位 SQL -> EXPLAIN 看计划 -> 索引、排序、锁等待逐个排




    20. 数据库连接池打满怎么排查?

    面试官常见问法

    线上报获取数据库连接超时,连接池满了,你怎么排查?

    回答思路

    连接池满不一定是连接数太小,更多时候是连接被占用太久。 要看慢 SQL、大事务、连接泄漏和数据库响应时间。

    排查步骤

  • 看连接池指标

    • 活跃连接数

    • 空闲连接数

    • 等待连接数

    • 获取连接超时次数

  • 看数据库状态

    • 当前连接数

    • 慢 SQL

    • 锁等待

    • CPU、IO、磁盘

  • 看应用日志

    • 是否有连接泄漏提示

    • 是否大量请求卡在获取连接

    • 是否某个接口突然调用量升高

  • 看事务代码

    • 事务中是否调用远程接口

    • 事务是否包含大量循环操作

    • 是否存在大批量更新

  • 常见根因

  • 慢 SQL 占用连接时间过长

  • 大事务长时间不提交

  • 事务中调用外部接口

  • 数据库锁等待

  • 连接泄漏

  • 流量突增,连接池容量不足

  • 解决方案

    短期可以扩容连接池或应用实例,但不能只靠调大连接数。 长期要优化慢 SQL、缩短事务范围、避免事务内远程调用、修复连接泄漏,并做好连接池监控。

    面试回答模板

    我会先看连接池的活跃连接、等待线程和超时次数,确认是不是连接都被占满了。 然后看数据库慢 SQL 和锁等待,判断连接是被慢查询占住,还是被大事务占住。 如果代码里事务范围太大,比如事务中调用远程接口,就要把远程调用移出事务。 短期可以扩容和限流止损,长期要优化 SQL、缩短事务、修复连接泄漏。

    记忆点

    连接池满 = 连接被占太久,重点查慢 SQL、大事务、锁等待




    21. MySQL 大量锁等待怎么排查?

    面试官常见问法

    线上 MySQL 出现大量锁等待,接口都卡住了,你怎么查?

    回答思路

    锁等待要先找谁在等锁、谁持有锁,再看对应 SQL 和事务。

    排查步骤

    SHOW PROCESSLIST;
    SHOW ENGINE INNODB STATUS;

    重点看:

  • 当前阻塞 SQL

  • 持锁事务执行了多久

  • 是否有大事务未提交

  • 是否更新顺序不一致

  • 是否索引缺失导致锁范围扩大

  • 面试回答模板

    我会先用 SHOW PROCESSLIST 和 InnoDB 状态看当前锁等待,找到被阻塞的 SQL 和持锁事务。 然后看持锁事务是不是执行时间过长,或者 SQL 没走索引导致锁住了大量记录。 短期可以评估是否 kill 掉异常长事务,恢复业务。 长期要优化索引、缩短事务范围,避免事务里做远程调用,并统一更新顺序。

    记忆点

    先找谁等锁,再找谁持锁




    22. MySQL 死锁怎么定位和避免?

    面试官常见问法

    线上出现死锁异常,你怎么定位?怎么避免?

    回答思路

    死锁不是单纯数据库问题,本质是多个事务互相等待对方持有的锁。 要看死锁日志中的两个事务和对应 SQL。

    排查步骤

    SHOW ENGINE INNODB STATUS;

    重点看最近一次死锁信息:

  • 哪两个事务发生死锁

  • 各自执行了什么 SQL

  • 持有什么锁,等待什么锁

  • 是否更新顺序不一致

  • 是否索引缺失导致锁范围过大

  • 面试回答模板

    我会先通过 InnoDB status 查看最近一次死锁详情,找到两个事务分别执行的 SQL。 然后分析它们的加锁顺序,如果两个事务以不同顺序更新相同资源,就容易互相等待。 解决上会统一资源更新顺序、缩短事务范围、补充合适索引,并让业务对死锁异常做有限重试。 因为 InnoDB 检测到死锁后会回滚其中一个事务,所以业务侧要能接受并重试。

    记忆点

    死锁看事务加锁顺序,业务要能重试




    23. 主从延迟导致读不到刚写入的数据怎么解决?

    面试官常见问法

    数据刚写入主库,马上查从库查不到,怎么处理?

    回答思路

    这是典型主从延迟问题。 核心是关键读走主库,普通读走从库。

    解决方案

  • 写后短时间内读主库

  • 对一致性要求高的接口强制读主库

  • 根据业务流水号或上下文做读写路由

  • 监控主从延迟,延迟过高时降级为读主

  • 优化大事务,减少主从复制压力

  • 面试回答模板

    我会先确认是不是读写分离导致读到了从库。 如果业务要求写后立即可见,比如下单后查订单详情,就应该读主库,或者在写后一段时间内强制读主。 如果是普通列表查询,可以接受短暂延迟就读从库。 同时要监控主从延迟,延迟过高时自动切到主库或做降级。

    记忆点

    写后立即读,优先读主库




    24. 分库分表后跨库查询怎么处理?

    面试官常见问法

    分库分表后,用户要按非分片字段查询数据,怎么办?

    回答思路

    分库分表后最怕跨库聚合和非分片键查询。 设计时要尽量让核心查询都带分片键。

    常见方案

  • 查询条件尽量带分片键

  • 建立冗余索引表或映射表

  • 使用搜索引擎承接复杂查询

  • 宽表或数据同步到查询库

  • 后台报表走离线数仓

  • 跨库查询做分页和结果合并,但控制使用场景

  • 面试回答模板

    分库分表后,我会尽量避免在线接口做全库扫描。 如果必须按非分片字段查询,可以建立映射表,比如手机号到用户 ID,再根据用户 ID 路由到具体分片。 复杂条件查询可以同步到 ES 或查询库,报表类需求走离线数仓。 跨库聚合可以做,但要限制场景和数据量,不能成为核心高频链路。

    记忆点

    核心链路带分片键,非分片查询靠映射表或查询库




    25. 表数据量过大,查询和写入都变慢怎么治理?

    面试官常见问法

    单表几千万甚至上亿数据,查询和写入变慢,怎么处理?

    回答思路

    先区分是索引问题、冷热数据问题,还是单表容量已经到瓶颈。 不要一上来就分库分表,先做低成本治理。

    解决方案

  • 优化索引和 SQL

  • 清理无用索引,降低写入成本

  • 冷热数据分离,历史数据归档

  • 按时间或业务维度分表

  • 高频查询建立查询宽表

  • 最后再考虑分库分表

  • 面试回答模板

    我会先看慢 SQL 和执行计划,确认是不是索引不合理。 如果表里大量历史数据很少访问,可以先做冷热分离和归档,减小在线表规模。 如果写入慢,还要检查是否有太多二级索引影响写入。 当单表确实成为瓶颈时,再按业务维度做分表或分库分表,并提前设计分片键和迁移方案。

    记忆点

    先优化和归档,最后才分库分表




    26. 大事务导致数据库抖动怎么排查和优化?

    面试官常见问法

    某个接口事务执行很久,导致数据库抖动,怎么办?

    回答思路

    大事务会长时间持锁、占用连接,还会影响主从复制和回滚成本。

    常见根因

  • 事务中批量更新大量数据

  • 事务中调用远程接口

  • 事务中做复杂计算

  • 事务范围过大

  • 未命中索引导致锁范围扩大

  • 面试回答模板

    我会先看事务执行时间、锁等待和连接占用情况。 如果事务里有远程调用或大量循环处理,会把这些逻辑移到事务外。 批量数据处理会拆成小批次提交,降低锁持有时间和回滚成本。 同时检查更新条件是否命中索引,避免锁范围被放大。

    记忆点

    事务只包数据库核心操作,越短越好




    27. 数据库 CPU 飙高但慢 SQL 不明显怎么分析?

    面试官常见问法

    MySQL CPU 很高,但慢查询日志里没有明显慢 SQL,怎么查?

    回答思路

    没有慢 SQL 不代表没有问题,可能是大量短 SQL 高频执行,把 CPU 打满。

    常见根因

  • 高频简单查询没有缓存

  • SQL 执行很快但调用次数过多

  • 大量排序、函数计算或隐式转换

  • 连接风暴

  • 执行计划突变

  • 慢查询阈值设置过高

  • 面试回答模板

    我会先看数据库 QPS、TPS、活跃连接和 CPU 使用情况。 如果慢 SQL 不明显,可能是大量短 SQL 高频执行导致 CPU 高,这时要看 SQL 统计和 TOP SQL。 也会检查慢查询阈值是不是过高,以及执行计划是否发生变化。 解决上可以增加缓存、合并查询、减少轮询、优化索引,并限制异常流量。

    记忆点

    没有慢 SQL,也可能是短 SQL 太多



    四、Redis 与缓存问题类

    28. Redis 变慢怎么排查?

    面试官常见问法

    Redis 平时很快,突然访问变慢,你会怎么排查?

    回答思路

    Redis 慢要从慢命令、大 key、热点 key、网络、内存、持久化几个方向看。

    排查步骤

  • 看 Redis 监控

    • QPS

    • CPU

    • 内存

    • 连接数

    • 命中率

    • 网络流量

  • 查看慢命令

  • slowlog get

  • 排查大 key

  • redis-cli –bigkeys

  • 排查热点 key

    • 看业务访问日志

    • 看 Redis 代理层统计

    • 看 key 的访问分布

  • 常见根因

  • 使用了 keys、大范围 hgetall、smembers 等慢命令

  • 大 key 读写导致网络和 CPU 压力变大

  • 热点 key 被大量请求集中访问

  • 内存打满触发淘汰

  • RDB 或 AOF rewrite 带来阻塞

  • 客户端连接数过多

  • 网络抖动

  • 解决方案

  • 禁止线上使用 keys,改用 scan

  • 大 key 拆分,避免一次返回大量数据

  • 热点 key 加本地缓存或多副本

  • 设置合理过期时间和内存淘汰策略

  • 优化持久化策略

  • 加连接池监控

  • 面试回答模板

    我会先看 Redis 的 CPU、内存、连接数、网络和慢命令。 如果慢命令里有 keys、hgetall、smembers 这类命令,就先改命令使用方式。 如果是大 key,就拆 key 或分页读取;如果是热点 key,就用本地缓存、多副本或热点隔离。 同时还要看是否内存打满、持久化阻塞或者网络抖动。

    记忆点

    慢命令 -> 大 key -> 热点 key -> 内存和网络




    29. 缓存穿透、击穿、雪崩怎么解决?

    面试官常见问法

    如果大量请求打到数据库,Redis 没有挡住,你怎么分析?

    回答思路

    先判断是哪一种缓存问题:

  • 穿透:查不存在的数据,每次都打到数据库

  • 击穿:热点 key 失效,大量请求同时查数据库

  • 雪崩:大量 key 同时失效,数据库压力暴增

  • 解决方案

    缓存穿透

    常见原因:请求不存在的 ID,缓存和数据库都没有。 解决方案:

  • 缓存空值,设置较短过期时间

  • 使用布隆过滤器提前过滤不存在的数据

  • 做参数校验,拦截非法 ID

  • 缓存击穿

    常见原因:热点 key 过期瞬间,大量请求同时回源。 解决方案:

  • 热点 key 设置逻辑过期

  • 加互斥锁,只允许一个线程回源重建缓存

  • 热点数据提前预热

  • 核心热点 key 不设置物理过期,由后台刷新

  • 缓存雪崩

    常见原因:大量 key 同一时间过期,或者 Redis 整体不可用。 解决方案:

  • 过期时间加随机值

  • 多级缓存

  • Redis 高可用

  • 限流、降级

  • 缓存预热

  • 面试回答模板

    我会先判断是穿透、击穿还是雪崩。 如果是查询不存在的数据导致缓存穿透,就缓存空值或者用布隆过滤器。 如果是热点 key 失效导致击穿,就用互斥锁或逻辑过期,保证只有一个线程回源。 如果是大量 key 同时过期导致雪崩,就给过期时间加随机值,同时做好限流、降级和缓存预热。

    记忆点

    穿透查不到 -> 击穿热点过期 -> 雪崩大量过期




    30. Redis 内存突然打满怎么排查?

    面试官常见问法

    Redis 内存突然涨满,业务开始报错,你怎么处理?

    回答思路

    先看是 key 数量暴增,还是某些大 key 暴增。 再看是否有过期策略失效或缓存写入异常。

    排查步骤

  • 看 Redis 内存、key 数量和淘汰次数

  • 用 bigkeys 查大 key

  • 抽样分析 key 前缀分布

  • 检查是否有 key 没设置过期时间

  • 检查是否有异常缓存写入

  • 面试回答模板

    我会先看 Redis 的 used_memory、key 数量、淘汰次数和内存碎片率。 然后按 key 前缀统计,看是哪类业务 key 暴增,再用 bigkeys 找大 key。 如果发现 key 没有过期时间,就补 TTL;如果是大 key,就拆分或限制写入大小。 短期可以扩容或清理异常 key,长期要加 key 规范、TTL 检查和容量告警。

    记忆点

    内存打满看 key 数量、大 key、TTL




    31. Redis 热点 key 导致单节点压力过高怎么解决?

    面试官常见问法

    某个热点 key 被大量访问,Redis 单节点 CPU 很高,怎么办?

    回答思路

    热点 key 的问题是请求集中到一个 key、一个分片,普通集群分片也分散不了。

    解决方案

  • 本地缓存热点数据

  • 热点 key 拆成多个副本 key

  • 请求随机读不同副本

  • 对热点接口限流

  • 热点数据提前预热

  • 核心热点数据可以放 CDN 或边缘缓存

  • 面试回答模板

    我会先确认是不是某个 key 的访问量远高于其他 key。 如果是热点 key,单纯 Redis 集群不一定能解决,因为同一个 key 还是落在一个分片。 可以用本地缓存减少 Redis 访问,或者把热点 key 做多副本,读取时随机打散。 同时要对热点接口做限流和预热,避免瞬时流量把 Redis 打满。

    记忆点

    热点 key 要打散读流量




    32. Redis 缓存和数据库数据不一致怎么解决?

    面试官常见问法

    更新数据库后缓存没更新,导致用户读到旧数据,怎么设计?

    回答思路

    常用策略是先更新数据库,再删除缓存。 复杂场景要结合延迟双删、消息补偿和过期时间兜底。

    常见方案

  • 先更新数据库,再删除缓存

  • 删除失败写入重试队列

  • 缓存设置合理 TTL

  • 高并发下使用延迟双删降低脏数据概率

  • 通过 Canal 或 MQ 订阅变更删除缓存

  • 面试回答模板

    我一般不会同时更新数据库和缓存,而是更新数据库成功后删除缓存,让后续请求重新加载。 如果删除缓存失败,要有重试或消息补偿,缓存本身也要设置 TTL 兜底。 对于并发读写导致的短暂不一致,可以根据业务接受程度使用延迟双删。 如果一致性要求更高,可以通过 binlog 订阅来异步删除缓存。

    记忆点

    更新数据库后删缓存,失败要补偿,TTL 兜底



    五、MQ 消息队列问题类

    33. MQ 消息积压怎么排查?

    面试官常见问法

    线上 MQ 消息突然积压几十万,你怎么处理?

    回答思路

    MQ 积压本质是生产速度大于消费速度。 先看是生产突增,还是消费变慢,再看消费者卡在哪里。

    排查步骤

  • 看 MQ 指标

    • 生产 TPS

    • 消费 TPS

    • 消息堆积量

    • 消费延迟

    • 消费者实例数

  • 看消费者日志

    • 是否有大量异常重试

    • 是否消费失败

    • 是否卡在数据库或下游接口

  • 看消费逻辑

    • 是否单线程消费

    • 是否批量消费太小

    • 是否每条消息都做远程调用

    • 是否有幂等控制

  • 常见根因

  • 突发流量导致生产速度暴涨

  • 消费者实例数不足

  • 消费逻辑变慢

  • 下游接口或数据库变慢

  • 消费失败后不断重试

  • 分区或队列数量限制了并行度

  • 解决方案

  • 临时扩容消费者实例

  • 提高消费线程数或批量消费数量

  • 对非核心逻辑降级

  • 优化数据库和下游调用

  • 做好消费幂等,避免重复消费造成数据问题

  • 必要时增加 topic 分区或队列数量

  • 面试回答模板

    我会先看生产 TPS 和消费 TPS,确认是生产突增还是消费变慢。 如果消费变慢,就看消费者日志和线程栈,判断是卡在数据库、下游接口,还是消费逻辑本身太重。 短期可以扩容消费者、提高并发、临时降级非核心逻辑,先把积压降下来。 长期要优化消费逻辑、增加幂等控制、调整分区数量,并补充消费延迟告警。

    记忆点

    生产快还是消费慢 -> 消费者卡哪 -> 扩容并发先消峰




    34. 消息重复消费怎么保证幂等?

    面试官常见问法

    MQ 消息可能重复投递,消费者怎么保证不重复处理?

    回答思路

    MQ 通常只能保证至少一次投递,所以消费者必须幂等。

    常见方案

  • 使用业务唯一 ID 去重

  • 数据库唯一索引防重复插入

  • 消费记录表记录处理状态

  • Redis SET NX 做短期去重

  • 状态机控制重复状态流转

  • 面试回答模板

    我会默认 MQ 可能重复投递,所以消费者一定要做幂等。 常见做法是用消息里的业务唯一 ID 建消费记录,处理前先判断是否已经处理过。 涉及数据库写入时,用唯一索引兜底;涉及状态变更时,用状态机限制只能从合法状态变更。 这样即使消息重复消费,也不会造成重复扣款、重复发货或重复通知。

    记忆点

    MQ 防不住重复,消费者必须幂等




    35. 消息丢失怎么排查?怎么保证可靠投递?

    面试官常见问法

    业务方说消息丢了,你怎么排查?怎么防止?

    回答思路

    消息丢失要分三段看:生产端、Broker、消费端。

    排查步骤

  • 生产端是否发送成功

  • Broker 是否持久化成功

  • 消费端是否拉取到消息

  • 消费成功前是否提前 ack

  • 是否进入死信队列

  • 是否被过滤条件过滤

  • 面试回答模板

    我会按生产端、Broker、消费端三段排查。 生产端要确认发送结果,失败要重试或落本地消息表;Broker 侧要看消息是否持久化、是否进入死信队列;消费端要看是否提前 ack 或消费异常。 为了可靠投递,核心消息会使用本地消息表或事务消息,消费者处理成功后再确认,并通过补偿任务扫描未完成消息。

    记忆点

    消息可靠性分生产、Broker、消费三段




    36. 消息顺序消费怎么保证?

    面试官常见问法

    订单状态消息必须按顺序消费,怎么保证?

    回答思路

    顺序消费的核心是同一业务 ID 的消息进入同一个队列,并由同一个消费者串行处理。

    解决方案

  • 生产端按订单 ID 选择同一个队列或分区

  • 消费端单队列串行消费

  • 业务状态机防止乱序更新

  • 消费失败时不能跳过关键消息

  • 对乱序消息可以暂存或重试

  • 面试回答模板

    如果要保证订单状态顺序,我会让同一个订单 ID 的消息路由到同一个队列或分区。 消费端对同一个队列串行消费,这样天然有序。 但只靠 MQ 顺序还不够,业务层还要用状态机校验,比如订单不能从已取消变成已支付。 如果出现乱序消息,就暂存、重试或丢到异常队列人工处理。

    记忆点

    同一业务 ID 进同一队列,业务状态机兜底




    37. 消息消费失败一直重试,阻塞后续消息怎么办?

    面试官常见问法

    某条消息一直消费失败,导致后面的消息都消费不了,怎么处理?

    回答思路

    不能让毒消息一直阻塞主链路,要设置重试上限和死信队列。

    解决方案

  • 设置最大重试次数

  • 超过次数进入死信队列

  • 记录失败原因和上下文

  • 后台支持人工补偿或重新投递

  • 消费逻辑区分可重试异常和不可重试异常

  • 面试回答模板

    我会先看失败消息是否是数据问题、代码异常还是下游临时不可用。 如果是下游临时异常,可以有限次数重试;如果是参数错误这类不可恢复异常,就不应该无限重试。 超过重试次数后进入死信队列,记录失败原因,后续通过补偿任务或人工处理。 这样可以避免一条异常消息阻塞整个消费链路。

    记忆点

    失败要有限重试,毒消息进死信队列




    38. Kafka 或 RocketMQ 某个分区积压严重怎么分析?

    面试官常见问法

    MQ 只有某个分区或队列积压很严重,其他分区正常,怎么排查?

    回答思路

    单分区积压通常是消息分布不均、热点 key 或该分区消费者异常。

    常见根因

  • 分区键不均匀,热点业务 ID 集中到一个分区

  • 某个消费者实例异常

  • 单条消息处理特别慢

  • 顺序消费导致并行度受限

  • 该分区数据量远高于其他分区

  • 面试回答模板

    我会先对比各分区的生产量、消费量和 lag。 如果只有一个分区积压,重点看分区键是否导致热点数据都打到同一个分区。 也会看负责该分区的消费者是否异常,线程是否卡住。 解决上可以优化分区键、增加分区、拆分热点业务,或者优化该分区的消费逻辑。

    记忆点

    单分区积压,多半是分布不均或消费者卡住



    六、并发、幂等与分布式一致性类

    39. 分布式锁失效导致重复处理怎么排查?

    面试官常见问法

    定时任务或消息消费中,同一条数据被多个实例重复处理,你怎么解决?

    回答思路

    重复处理要先判断是锁没生效、锁过期太早、任务重入,还是业务没有做幂等。

    常见根因

  • 锁 key 设计不合理,不同业务用了同一个 key

  • 锁过期时间太短,业务没执行完锁就释放了

  • 没有校验锁 value,误删了别人的锁

  • Redis 主从切换导致锁状态不一致

  • 业务没有幂等,重复请求直接造成重复数据

  • 解决方案

  • 锁 key 加业务唯一标识

  • 锁 value 使用唯一 requestId

  • 删除锁时用 Lua 脚本保证原子性

  • 合理设置锁过期时间,必要时使用看门狗续期

  • 核心业务必须做幂等,比如唯一索引、状态机、去重表

  • 面试回答模板

    我会先看重复处理的数据是否有相同业务 ID,然后排查分布式锁的 key、过期时间和释放逻辑。 如果锁过期时间太短,业务没执行完其他实例就会拿到锁;如果释放锁时没有校验 value,可能误删别人的锁。 解决上会用唯一 requestId 作为锁 value,用 Lua 脚本释放锁,并结合业务幂等兜底,比如唯一索引或状态机控制。 因为分布式锁只能减少并发冲突,不能替代业务幂等。

    记忆点

    锁防并发,幂等防重复




    40. 接口重复提交导致重复下单怎么解决?

    面试官常见问法

    用户快速点击两次提交按钮,产生了两笔订单,你怎么处理?

    回答思路

    重复提交不能只靠前端按钮置灰,后端必须做幂等。

    常见方案

  • 前端按钮防重复点击

  • 请求带幂等 token

  • 后端使用 Redis 保存 token,处理成功后删除或标记已使用

  • 数据库加唯一索引兜底

  • 订单状态机控制状态流转

  • 解决方案示例

  • 用户进入下单页时,后端生成唯一 token 返回前端

  • 用户提交订单时带上 token

  • 后端用 Redis SET NX 或 Lua 脚本校验 token

  • 第一次请求可以继续处理

  • 后续重复请求直接返回“请勿重复提交”或返回第一次处理结果

  • 数据库层用唯一索引保证同一业务单号只能插入一次

  • 面试回答模板

    我不会只依赖前端防重复点击,因为请求可能重试,也可能绕过前端。 后端会基于业务唯一号或幂等 token 做控制,比如 Redis SET NX 保证同一个 token 只处理一次。 同时数据库加唯一索引兜底,避免并发情况下插入重复订单。 如果订单有状态流转,还会用状态机限制非法状态变更。

    记忆点

    前端防抖只是体验,后端幂等才是核心,数据库唯一索引兜底




    41. 分布式事务不一致怎么解决?

    面试官常见问法

    订单创建成功了,但是库存扣减失败,数据不一致怎么办?

    回答思路

    先看业务是否必须强一致。 大多数互联网业务可以接受最终一致,通常用本地消息表、可靠消息、事务消息或 TCC。

    常见方案

  • 本地事务 + 本地消息表

  • MQ 事务消息

  • TCC

  • Saga

  • 对账补偿

  • 本地消息表方案

  • 创建订单和插入消息表放在同一个本地事务里

  • 事务提交后,后台任务扫描消息表发送 MQ

  • 库存服务消费消息扣减库存

  • 消费成功后更新消息状态

  • 失败则重试

  • 定时任务做补偿和对账

  • 面试回答模板

    我会先判断业务是否要求强一致。 如果可以接受最终一致,我更倾向用本地消息表或事务消息。 比如创建订单时,把订单记录和待发送消息放在同一个本地事务里,保证订单成功一定有消息。 库存服务消费消息时要做幂等,失败可以重试,最后通过补偿任务和对账保证最终一致。

    记忆点

    本地事务保证消息不丢,消费幂等保证重复不怕,对账补偿保证最终一致




    42. 接口超时但数据成功了,怎么处理?

    面试官常见问法

    调用第三方接口超时了,但后来发现第三方其实处理成功了,你怎么保证数据一致?

    回答思路

    接口超时不代表失败,可能是成功、失败或处理中。 这种场景不能简单重试,必须有查询和补偿机制。

    解决方案

  • 请求使用全局唯一业务流水号

  • 超时后不要立即判定失败

  • 调用查询接口确认最终状态

  • 重试时保证幂等

  • 定时任务扫描处理中记录并补偿

  • 与第三方做对账

  • 面试回答模板

    我会把超时状态单独标记为处理中,而不是直接当失败。 每次请求都带唯一业务流水号,第三方也基于这个流水号做幂等。 如果调用超时,就通过查询接口确认最终状态;查询不到再进行有限次数重试。 同时用定时任务扫描处理中数据,做补偿和对账,保证最终一致。

    记忆点

    超时不等于失败,先查状态,再幂等重试,最后补偿对账




    43. 库存超卖怎么解决?

    面试官常见问法

    高并发下多个用户同时下单,库存被扣成负数了,怎么解决?

    回答思路

    库存问题核心是并发扣减和幂等。 数据库层要保证扣减原子性,业务层要防重复扣减。

    常见方案

  • 数据库条件更新

  • UPDATE product
    SET stock = stock – 1
    WHERE id = ? AND stock > 0;

    根据影响行数判断是否扣减成功。

  • Redis 预扣库存

  • MQ 异步削峰

  • 分布式锁控制并发

  • 订单幂等和防重复消费

  • 面试回答模板

    我会优先用数据库条件更新保证原子扣减,比如 stock > 0 才允许扣减,并根据影响行数判断是否成功。 在高并发秒杀场景,可以先用 Redis 预扣库存,再通过 MQ 异步落库削峰。 同时订单创建和消息消费都要做幂等,避免重复请求或重复消费导致重复扣库存。 分布式锁可以作为一种方案,但高并发下要注意性能和锁粒度。

    记忆点

    条件更新防超卖,Redis 预扣抗高并发,MQ 削峰,幂等兜底




    44. 乐观锁更新失败很多怎么优化?

    面试官常见问法

    高并发下使用版本号乐观锁,但大量更新失败,怎么办?

    回答思路

    乐观锁适合冲突少的场景。 如果冲突特别高,就要减少热点竞争或换方案。

    解决方案

  • 增加有限重试和随机退避

  • 降低同一记录并发更新频率

  • 热点数据拆分,比如库存分段

  • 改成数据库原子条件更新

  • 高冲突场景考虑悲观锁或队列串行化

  • 面试回答模板

    乐观锁失败多说明同一条数据竞争很激烈。 我会先加有限重试和随机退避,但不能无限重试,否则会放大数据库压力。 如果是库存这类热点,可以做库存分段或用原子条件更新。 如果业务必须严格串行,也可以通过队列把同一业务 ID 的请求串行化处理。

    记忆点

    乐观锁适合低冲突,高冲突要拆热点或串行化




    45. 状态机并发流转错乱怎么设计?

    面试官常见问法

    订单状态被并发更新,出现状态错乱,怎么避免?

    回答思路

    状态流转必须有明确的前置状态约束,不能只按 ID 更新。

    解决方案

  • 定义状态机和合法流转路径

  • SQL 更新时带上当前状态条件

  • 使用版本号防并发覆盖

  • 重复请求返回当前最终状态

  • 异常状态进入补偿流程

  • 面试回答模板

    我会先定义订单状态机,明确哪些状态可以流转到哪些状态。 更新时 SQL 不只带订单 ID,还要带当前状态,比如只有待支付才能更新为已支付。 如果影响行数为 0,说明状态已经变化,需要查询当前状态再决定返回成功、失败还是补偿。 这样可以避免并发请求把已取消订单又改成已支付。

    记忆点

    状态更新必须带前置状态




    46. 用户余额扣减成功,但订单状态更新失败怎么处理?

    面试官常见问法

    扣款成功了,但订单状态没更新成功,数据不一致怎么办?

    回答思路

    如果在同一个数据库,优先放同一个本地事务。 如果跨服务,就要用最终一致方案和补偿。

    解决方案

  • 同库操作放在一个本地事务

  • 跨服务使用事务消息或本地消息表

  • 每一步操作都要幂等

  • 失败状态记录清楚

  • 定时任务补偿和对账

  • 面试回答模板

    如果余额和订单在同一个库,我会放在同一个本地事务里保证一致。 如果是两个服务,就不能靠本地事务解决,需要用事务消息、本地消息表或 TCC。 扣款成功后订单更新失败,要记录中间状态,通过补偿任务继续更新订单或发起退款。 同时所有补偿动作都要幂等,避免重复扣款或重复退款。

    记忆点

    跨服务一致性靠状态、消息、补偿、对账




    47. 超级 SIM 必达消息重复计费怎么排查和解决?

    面试官常见问法

    你在超级 SIM 项目里遇到过什么真实线上问题?如果必达消息套包额度消耗不准,你怎么排查?

    项目背景

    超级 SIM 统一平台里有一个必达消息组合套包计费能力。 业务规则是同一个客户、同一个套包、同一个计费周期内,同一个手机号只能计费一次。 平台需要在高并发批量发送必达消息时,实时判断手机号是否已经计费,并刷新客户的可用额度。

    问题现象

    运营侧反馈某些客户的套包额度消耗偏快。 排查账单明细后发现,少量手机号在同一个计费周期内出现了重复计费记录。 这个问题不是每次都出现,主要集中在批量发送、MQ 重试或者多个批次同时触达同一批手机号的时候。

    排查过程

  • 先查计费明细表

    • 按 tenantId + packageId + billingCycle + phone 分组统计

    • 发现部分手机号确实存在多条计费明细

    • 说明不是前端展示问题,而是后端重复入账

  • 再查接口日志和 MQ 消费日志

    • 发现同一个手机号可能在短时间内被多个批次同时发送

    • MQ 消费失败后会触发重试

    • 同一手机号可能被多个线程或多个消费者同时进入计费逻辑

  • 再看原来的去重逻辑

  • 原逻辑大概是:

    查 Redis 是否已计费 -> 写 MySQL 明细 -> 扣减套包额度 -> 更新 Redis 去重标记

    这个流程的问题是 Redis 判断和数据库写入不是原子的。 高并发下,两个线程可能同时判断 Redis 中不存在该手机号,然后都继续写明细和扣额度。

  • 最后检查数据库约束

    • 明细表虽然记录了手机号和计费周期

    • 但没有严格的唯一索引兜底

    • 并发穿透 Redis 后,数据库也没有挡住重复数据

  • 根因分析

  • Redis 去重判断和写入不是原子操作

  • 高并发或 MQ 重试时,同一手机号可能重复进入计费逻辑

  • 数据库缺少唯一索引兜底

  • 消费端幂等不足,重复消息可能导致重复扣减额度

  • 把缓存当成了最终幂等依据,而缓存不适合做最终一致性保障

  • 解决方案

  • Redis 位图或布隆过滤器只做前置过滤

    • 用于快速判断手机号是否可能已经计费

    • 主要作用是减少数据库压力

    • 不作为最终是否计费的唯一依据

  • 数据库明细表增加唯一约束

  • 唯一键可以设计为:

    tenant_id + package_id + billing_cycle + phone_hash

    这样即使多个线程并发进入数据库,同一个手机号在同一个计费周期内也只能插入一条明细。

  • 调整计费顺序

  • 改成:

    先插入计费明细 -> 插入成功后扣减额度 -> 更新 Redis 位图/布隆过滤器

    如果插入明细成功,说明这是第一次计费,再扣减套包额度。 如果出现唯一键冲突,说明该手机号已经计费过,直接跳过扣减。

  • MQ 消费端增加业务幂等

    • 每条消息带 messageId 或 requestNo

    • 消费前判断该消息是否处理过

    • 避免 MQ 重试导致同一批数据重复处理

  • 增加对账和补偿

    • 定时按数据库计费明细汇总实际去重手机号数量

    • 对比套包额度消耗

    • 发现不一致时生成差异记录

    • 通过补偿任务修正额度或账单

  • 核心伪代码

    try {
    insertBillingDetail(tenantId, packageId, cycle, phoneHash);
    decreasePackageQuota(packageId, 1);
    updateRedisDedupMark(cycle, phoneHash);
    } catch (DuplicateKeyException e) {
    // 已经计费过,不再扣减额度
    return;
    }

    面试回答模板

    我在超级 SIM 统一平台里处理过一个必达消息套包重复计费的问题。 业务规则是同一个客户、同一个套包、同一个计费周期内,同一个手机号只能计费一次。线上现象是部分客户额度消耗偏快,查账单明细后发现少量手机号存在重复计费。

    我先按租户、套包、计费周期、手机号维度查明细,确认是后端重复入账。然后看 MQ 消费日志,发现高并发批量发送和 MQ 重试时,同一个手机号可能被多个消费者同时处理。继续看代码发现,原来的逻辑是先查 Redis 是否存在,再写数据库和扣额度,这个判断和写入不是原子的。并发情况下两个线程可能都判断 Redis 不存在,然后都进入扣减逻辑。

    后面我们把方案调整为:Redis 位图或布隆过滤器只做前置过滤,真正的幂等由数据库唯一索引兜底。明细表增加 tenantId + packageId + billingCycle + phoneHash 唯一约束,只有插入明细成功才扣减额度。如果插入时出现唯一键冲突,就说明这个手机号已经计费过,直接跳过。

    同时 MQ 消费端增加业务幂等,避免消息重复投递导致重复处理。最后再通过定时任务做明细和额度对账,保证最终一致。

    记忆点

    缓存只做前置过滤,数据库唯一索引兜底,MQ 消费幂等,对账补偿保证最终一致


    七、发布、配置与微服务架构类

    48. 线上发布后大量报错怎么处理?

    面试官常见问法

    刚发布完新版本,接口大量 500,你怎么处理?

    回答思路

    发布后大量异常,优先考虑新版本引入问题。 先止损,再定位。

    排查步骤

  • 看错误率、RT、QPS 是否在发布后变化

  • 对比新旧版本实例错误情况

  • 查看异常日志和调用链

  • 判断是否是配置、SQL、兼容性或空指针问题

  • 评估是否需要回滚

  • 常见根因

  • 配置漏发或配置错误

  • 数据库字段或表结构不兼容

  • 代码空指针

  • 依赖版本冲突

  • 缓存 key 格式变化不兼容

  • 灰度验证不充分

  • 解决方案

  • 立即回滚或摘除新版本实例

  • 如果是配置问题,快速修复配置

  • 如果是数据兼容问题,补兼容逻辑

  • 后续完善灰度发布、自动化回归和发布检查清单

  • 面试回答模板

    发布后大量报错,我会先判断问题是否和新版本强相关。 如果新版本实例错误率明显更高,会优先回滚或摘除新版本流量,先恢复服务。 然后再看异常日志和链路追踪,定位是配置、代码、数据库兼容,还是缓存兼容问题。 复盘时会补充灰度验证、发布检查清单和回滚预案。

    记忆点

    发布后异常,先回滚止损,再定位根因




    49. 灰度发布时新老版本数据不兼容怎么解决?

    面试官常见问法

    灰度发布时,新版本写的数据老版本读不了,怎么避免?

    回答思路

    灰度发布要求接口、数据库、缓存和消息都要向前兼容、向后兼容。

    解决方案

  • 数据库变更先加字段,后切代码,最后删旧字段

  • 新字段允许为空并设置默认值

  • 消息体新增字段不能影响老消费者

  • 缓存 key 变更要兼容旧格式

  • 发布采用先兼容、再切换、最后清理的节奏

  • 面试回答模板

    灰度发布时不能假设所有实例同时升级成功。 我会保证新老版本在一段时间内可以同时运行,比如数据库先加字段但不删除旧字段,接口新增字段不影响老版本解析。 缓存和 MQ 消息也要做兼容,避免新版本写入后老版本读失败。 等所有实例升级并稳定后,再清理旧逻辑。

    记忆点

    灰度发布核心是双版本兼容




    50. 配置中心推错配置导致线上故障怎么防止?

    面试官常见问法

    配置中心一推错配置,线上全挂了,怎么治理?

    回答思路

    配置变更和代码发布一样有风险,也需要灰度、校验、回滚和审计。

    解决方案

  • 配置变更审批

  • 配置格式和取值范围校验

  • 灰度推送,先小流量验证

  • 配置版本管理和一键回滚

  • 关键配置加保护阈值

  • 推送后自动观察错误率和 RT

  • 面试回答模板

    我会把配置发布当成一次线上变更来管理。 配置中心要支持格式校验、审批、灰度发布和版本回滚,不能直接全量推送。 对线程池大小、限流阈值、开关类配置要设置合理边界,避免填错导致服务不可用。 发布后还要观察核心指标,异常时快速回滚配置。

    记忆点

    配置也是发布,要灰度、校验、回滚




    51. 服务注册中心异常,服务还能不能正常调用?

    面试官常见问法

    注册中心挂了,微服务之间还能调用吗?

    回答思路

    要看客户端是否有本地服务列表缓存。 通常已发现的服务还能调用,但新实例注册和故障实例摘除会受影响。

    面试回答模板

    如果注册中心异常,已经拉取过的服务列表通常会缓存在客户端,所以短时间内服务之间还能调用。 但新服务注册、下线实例摘除、服务列表更新会受影响。 这时要避免频繁重启服务,因为重启后可能拿不到服务列表。 长期要保证注册中心高可用,并在客户端保留本地缓存和容错策略。

    记忆点

    注册中心挂了,存量调用可能还能跑,服务变更会受影响




    52. 多机房部署时数据一致性和流量切换怎么考虑?

    面试官常见问法

    系统做多机房部署,怎么保证数据一致性?机房故障怎么切流?

    回答思路

    多机房要先明确是同城双活、异地多活,还是主备容灾。 不同架构对一致性和延迟的要求不一样。

    关键考虑

  • 用户按地域或用户 ID 路由到固定机房

  • 核心写操作避免多机房同时写同一份数据

  • 跨机房同步要考虑延迟和冲突

  • 故障切流要有预案和演练

  • 缓存、MQ、数据库都要考虑容灾

  • 切流后要做数据校验和回切方案

  • 面试回答模板

    我会先区分是主备还是双活。 如果是主备,正常流量走主机房,备机房同步数据,故障时切到备机房。 如果是双活,要尽量按用户维度做流量分片,避免两个机房同时写同一份数据。 跨机房同步一定会有延迟,所以要设计冲突处理、对账补偿和切流演练。

    记忆点

    多机房先定架构,核心是路由、同步、冲突和切流



    八、批处理、任务与运维故障类

    53. 定时任务重复执行怎么解决?

    面试官常见问法

    服务部署多台机器后,定时任务被多台机器同时执行了,怎么办?

    回答思路

    多实例部署时,本地定时任务天然会重复执行,需要做任务调度协调。

    解决方案

  • 使用分布式任务调度框架,比如 XXL-JOB、ElasticJob

  • 使用分布式锁保证同一时间只有一个实例执行

  • 任务本身做幂等

  • 对执行结果做状态记录,避免重复处理

  • 长任务要考虑锁续期和失败重试

  • 面试回答模板

    如果服务多实例部署,本地 @Scheduled 会在每台机器都执行。 简单场景可以用 Redis 分布式锁控制同一时间只有一个实例执行,但任务必须做幂等。 更规范的做法是使用 XXL-JOB 这类调度平台,支持分片、失败重试、日志和告警。 如果任务执行时间较长,还要考虑锁过期和续期问题。

    记忆点

    多实例定时任务一定会重复,调度框架或分布式锁,加幂等兜底




    54. 大批量数据导入导致系统变慢怎么处理?

    面试官常见问法

    用户上传几十万行 Excel,导入时系统变慢甚至 OOM,你怎么优化?

    回答思路

    大批量导入要避免一次性读入内存、避免单条插入、避免长事务。

    常见问题

  • Excel 一次性加载到内存导致 OOM

  • 单条插入数据库效率低

  • 一个大事务持有锁太久

  • 校验逻辑逐条查库

  • 导入过程占满业务线程池

  • 解决方案

  • 使用流式读取 Excel

  • 分批校验、分批入库

  • 批量插入或批量更新

  • 大事务拆成小事务

  • 导入任务异步化,返回任务 ID

  • 独立线程池处理导入任务

  • 记录导入进度和失败明细

  • 面试回答模板

    大文件导入我不会同步阻塞接口处理。 一般会上传后生成导入任务,异步处理,并返回任务 ID 给前端查询进度。 处理时采用流式读取,避免一次性加载到内存;数据库操作按批次提交,避免单条插入和大事务。 同时导入线程池要和核心业务线程池隔离,防止导入任务拖慢正常接口。

    记忆点

    流式读取、分批入库、异步任务、线程池隔离




    55. 日志量突然暴增导致磁盘打满怎么处理?

    面试官常见问法

    线上服务磁盘满了,发现是日志打满的,你怎么处理?

    回答思路

    磁盘满要先快速释放空间恢复服务,再定位日志暴增原因。

    排查步骤

  • 查看磁盘

  • df -h
    du -sh *

  • 找大文件

  • find /path -type f -size +1G

  • 查看日志是否大量重复异常

  • 检查日志级别是否被调成 DEBUG

  • 检查是否有异常堆栈在循环打印

  • 解决方案

  • 临时清理或压缩历史日志

  • 调整日志级别

  • 修复循环异常打印

  • 配置日志切割和保留天数

  • 增加磁盘使用率告警

  • 核心错误日志限频

  • 面试回答模板

    我会先用 df -h 确认磁盘使用率,再用 du 或 find 找到大日志文件。 短期先清理历史日志或压缩日志,恢复服务可用性。 然后看是不是日志级别被调成 DEBUG,或者某个异常在循环打印。 长期要配置日志滚动、保留天数、磁盘告警,并对高频错误日志做限频。

    记忆点

    先释放空间,再查日志暴增原因,最后加切割和告警



    九、线上问题复盘类

    56. 线上问题复盘怎么讲?

    面试官常见问法

    你们线上出过问题吗?最后怎么复盘的?

    回答结构

  • 问题背景

  • 影响范围

  • 发现方式

  • 排查过程

  • 根因分析

  • 临时止损

  • 长期优化

  • 复盘改进

  • 面试回答模板

    线上问题复盘我一般会按时间线讲。 先说明什么时间发现、影响了哪些接口和用户,然后讲我们通过监控、日志、链路追踪怎么一步步定位。 定位后先采取了什么止损措施,比如回滚、限流、扩容或降级。 最后再讲长期方案,比如修复代码、补充监控告警、完善灰度发布、增加压测和回归用例。

    记忆点

    背景 -> 影响 -> 排查 -> 根因 -> 止损 -> 长期优化


    补充高频场景题


    场景题万能回答公式

    面试中遇到不会的场景题,可以按这个结构组织语言:

  • 先确认现象

    • 是哪个接口、哪个服务、哪台机器、什么时间开始异常

  • 再判断影响范围

    • 单机还是集群

    • 单接口还是全服务

    • 部分用户还是全部用户

  • 按链路拆分

    • 网关

    • 应用

    • 数据库

    • Redis

    • MQ

    • 下游服务

  • 结合工具定位

    • CPU:top、top -Hp、jstack

    • 内存:jmap、MAT、GC 日志

    • SQL:慢查询日志、EXPLAIN

    • 线程池:活跃线程、队列长度、拒绝次数

    • Redis:slowlog、bigkeys

    • MQ:生产 TPS、消费 TPS、积压量

  • 先止损

    • 限流

    • 降级

    • 熔断

    • 扩容

    • 回滚

    • 摘流量

  • 再长期优化

    • 修复代码

    • 优化 SQL

    • 调整 JVM

    • 增加缓存

    • 线程池隔离

    • 补充监控告警

    • 完善压测和发布流程


  • 高频关键词

    面试回答时可以自然带上这些关键词:

    • 先看监控,不先猜原因

    • 先判断影响范围

    • 沿调用链拆分耗时

    • 先止损,再定位根因

    • 短期恢复服务,长期治理问题

    • 线程栈看线程卡在哪里

    • GC 后内存降不下来,怀疑内存泄漏

    • 慢 SQL 先看执行计划

    • 连接池满通常是连接被占用太久

    • 分布式锁不能替代业务幂等

    • 超时不等于失败,要查最终状态

    • MQ 积压本质是生产速度大于消费速度

    • 雪崩治理靠超时、限流、熔断、降级、隔离

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 项目排查类场景案例
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!