Skip to content

第七章 线上故障排查与治理(第163-184题) ​

163. 凌晨 Java 服务 CPU 100%,接口全超时(CPU 飙高排查) ​

【考察内容】CPU 飙高排查是线上问题第一高频题

【题目】凌晨线上 Java 服务 CPU 持续 100%,所有接口超时,告警响成一片。你作为值班工程师,怎么一步步定位到具体线程和代码行?给出完整排查命令链(top、top -Hp、jstack、日志核对)。

【参考答案】标准四步法:

  1. 定位进程:top 查看 CPU 占用最高的进程,确认是 Java 进程(记 PID);
  2. 定位线程:top -Hp <PID> 查看该进程内 CPU 最高的线程,记下 TID;
  3. 转十六进制:printf "%x\n" <TID> 得到十六进制 nid;
  4. 定位代码:jstack <PID> | grep -A 30 <nid> 查看该线程堆栈——锁定具体类和方法;再回到日志核对(题干点名的第四段命令链):拿栈顶类名/方法名去 grep 业务日志与 access 日志(grep -E '类名|方法名' app.log,或按 TraceID 反查这一批请求的入参与耗时),确认「是哪个接口、哪类入参」在烧 CPU——只有栈、没有日志,定位到方法也说不清是谁触发的,处置就没有抓手。 常见根因:
  • 死循环/正则回溯(消耗型);
  • 频繁 Full GC(GC 线程 CPU 高,配合 jstat -gcutil 看 GC 频率);
  • 锁竞争/自旋(大量线程 BLOCKED 重试);
  • 大量序列化/加解密(业务代码密集计算);
  • 兜底:Arthas thread -n 3 直接列出最忙的 3 个线程(免手工换算)。
  1. 容量估算与阈值: 单实例按 4C8G 估算,业务线程池常见 200–500;若 CPU 持续 100% 且 P99>1s,按“每核可承载约 X QPS”反推——先用压测基线(如单核 500–2000 QPS,视接口复杂度)估算缺口。告警线:CPU>80% 持续 1–3 分钟;单线程>80% 一核连续 2 个采样周期即可疑死循环。jstack/jmap 现场保留窗口建议 ≥15 分钟流量特征。
  2. 失败与降级: 定位期间先止血:网关限流/摘流(摘流量、但保留进程与现场:留 1 台不重启以便取 jstack/dump,可它必须先摘出负载均衡——该机 CPU 已 100% 还挂在 LB 后面,1/N 用户会继续超时;「保留现场」不等于「保留流量」)→ 非核心接口降级返回兜底 → 可回滚则先回滚。若 dump/jstack 本身导致更卡,改用 Arthas 低频采样或 kill -3 输出到文件。止血失败且影响面扩大:启动预案切换只读/排队页,并行拉内核/DBA。

【原理溯源】

  • 为什么要 top → top -Hp 两层? top 只看到进程级 CPU,Java 进程可能有几百线程,必须下钻到线程。top -Hp 把该 PID 下每个线程(LWP)的 CPU 占用拆开,找到真正烧 CPU 的那一个(或几个)。
  • 为什么 TID 要转十六进制? Linux 内核用十进制 LWP 标识线程,HotSpot 的 jstack 输出里 nid(native thread id)是十六进制。不转换就 grep 不到。printf "%x\n" 就是做这个映射。
  • jstack 堆栈里在看什么? 看两件事:① 线程状态(RUNNABLE=在执行;BLOCKED=等锁;WAITING=等资源);② 栈顶方法——RUNNABLE 且栈顶落在业务代码/正则/序列化,就是计算型热点;栈顶落在 Object.wait / 连接池,是等待型(CPU 通常不高)。
  • GC 型 CPU 高和代码型怎么区分? 代码型:业务线程 RUNNABLE 占 CPU;GC 型:GC task thread / G1 Conc / G1 Young 等 GC 线程占 CPU,且 jstat -gcutil 显示 YGC/FGC 频率异常。两者处置完全不同——前者改代码,后者看内存/GC(见 165 题)。
  • 为什么正则会烧 CPU? 典型是灾难性回溯(backtracking):嵌套量词如 (a+)+ 在不匹配时指数级尝试。输入稍长就把单核打满。定位到 Pattern.matcher 栈顶即可确认。
  • Arthas thread -n 3 为什么快? 它直接读线程 CPU 时间并排序,免去 top 转十六进制再 grep jstack 的手工链路,且不用重启、可在线 attach。

【选型判断树】

CPU 100%,先分清"谁在烧":
├─ GC 线程占 CPU(jstat FGC/YGC 频繁)
│   → 走 Full GC 排查(165 题):回收效果差=泄漏;分配过快=调新生代
├─ 单个业务线程 RUNNABLE 占满一核
│   → 死循环 / 正则回溯 / 密集计算
│   → jstack 定位栈顶代码行
├─ 多个业务线程 RUNNABLE 且互相 BLOCKED
│   → 锁竞争 / 自旋重试
│   → 看锁持有者,减锁粒度或改无锁结构
└─ 大量线程 WAITING 但 CPU 仍高
    → 可能是大量短任务切换 / 序列化
    → 抽样多次 jstack,看栈顶是否聚在同一方法

判断口诀: 先分 GC 还是业务;业务里再分单线程死循环 vs 多线程锁竞争。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“CPU 100% 排查核心是三层下钻:进程→线程→代码行”
0:30–1:30命令链top → top -Hp → printf %x → jstack grep nid → 回到日志核对(栈顶类名 grep 业务/access 日志或按 TraceID 反查入参),每步一句为什么
1:30–2:30堆栈怎么读RUNNABLE/BLOCKED/WAITING 分别指向计算/锁/等待
2:30–3:30根因分类死循环、GC、锁竞争、序列化——各自怎么确认
3:30–4:30工具兜底Arthas thread -n;不重启在线诊断
4:30–5:00收尾“先止血(摘流量/重启)再定位,但必须留现场”

【关键数字】

参数经验值说明
top 采样默认 3s 刷新,可用 top -d 1突发问题用 1s
printf 转换printf "%x\n" <TID>十进制→十六进制对应 jstack 的 nid
jstack 采样连续 2–3 次,间隔 5–10s区分瞬时热点与持续热点
危险线程占比单线程持续 >80% 一核典型死循环
Arthas thread -n直接出 Top N 忙线程免手工换算
正则回溯输入 >1KB 时可能秒级到分钟级灾难性回溯

【追问链】(三层)

L1|“top 里 CPU 高但 jstack 看不到对应线程,为什么?” → 可能是:① GC 线程(名字不是业务线程,要搜 GC/G1);② 线程已结束/状态切换,jstack 与 top 不是同一瞬间;③ 内核态 CPU(如频繁 GC 的安全点、syscall)。多次采样 + 看 GC 线程 + jstat 交叉验证。

L2|“为什么不能只重启?” → 重启只恢复可用,现场没了,根因还在。正确顺序:先摘流量/限流止血 → 保留现场(jstack、jstat、日志、必要时 dump)→ 再重启恢复 → 事后分析。生产应配 HeapDumpOnOutOfMemoryError 和持续线程采样。

L3|“Arthas 和 jstack 比,优势在哪、风险在哪?” → 优势:在线 attach、不用重启;thread -n/-b 直接排序;watch/trace 动态看入参和耗时;jad 反编译确认代码版本。风险:attach 本身有短暂开销;watch 表达式写错可能影响性能;严格环境需审批。jstack 更“轻”,适合快速第一眼。

【评分标准】

档位答案特征
60 分能说出 top → jstack,知道要找线程
80 分完整命令链 + 十六进制转换原因;能区分代码型 / GC 型 / 锁型
95 分能读懂堆栈状态与栈顶含义;说出正则回溯、安全点等机制;给出 Arthas 替代路径;有“先止血留现场再重启”的纪律

【关联题】

  • 同一知识簇: 第 165 题(Full GC)、第 167 题(内存泄漏)、第 168 题(死锁)、第 172 题(服务 Hang)、第 181 题(内存上涨)
  • 工具延伸: Arthas thread / watch / trace
  • 场景呼应: 第 225 题(生产库 CPU 100%)

【自测】

  1. top -Hp 拿到的 TID 是 12345,jstack 里该搜什么? 参考答案:搜十六进制 3039(12345 的 hex),即 nid。
  2. 判断:CPU 100% 一定是死循环。 参考答案:错。还可能是频繁 Full GC、锁自旋、密集序列化/加解密、正则回溯等。
  3. 如何快速区分“业务线程烧 CPU”和“GC 线程烧 CPU”? 参考答案:jstack 里看线程名是否为 GC task thread / G1 等;同时用 jstat 看 YGC/FGC 频率。GC 型要走内存/GC 排查,不是改业务代码。

164. 服务报 OutOfMemoryError,堆被什么东西占满了(OOM 排查) ​

【考察内容】OOM 排查是线上问题必考题

【题目】线上服务频繁报 OutOfMemoryError,重启后过几小时又 OOM。怎么确认是堆溢出、还是元空间/直接内存溢出?怎么拿到并分析堆 dump,定位是哪些对象、哪段代码在占用内存?

【参考答案】

  1. 应急:先保留现场(jmap -dump:format=b,file=heap.hprof <PID> 或配置 -XX:+HeapDumpOnOutOfMemoryError 自动 dump)→ 重启恢复服务(先保可用);

  2. 分析 dump:用 MAT/Eclipse MAT 或 jvisualvm 打开:

    • Leak Suspects(泄漏嫌疑):自动分析最大的对象与 GC Roots 引用链;
    • 按 Retained Heap(保留集)排序,看大对象;
    • 对比多个 dump(不同时间点):找出持续增长的对象(泄漏特征);
  3. 常见泄漏点:

    • 静态集合持有对象不放(static Map/List 当缓存);
    • 未关闭的资源(连接、流、线程池);
    • ThreadLocal 未 remove(线程池复用线程,值不释放);
    • 监听器/回调未反注册;
    • 大对象缓存无淘汰策略;
  4. 区分泄漏 vs 峰值:

    • 内存泄漏:dump 之间对象持续增长,GC 后仍不下降;
    • 内存峰值:某次大请求/大任务短时占满(排查大集合、批处理),GC 后能恢复;
  5. 配置预防:-Xmx 合理设置、GC 日志、内存监控告警(堆使用率 80% 告警)。

  6. 容量估算: 堆经验值:4C8G 服务 -Xmx 常取物理内存 50–70%(8G 即 4–5.6G,实践常配 4–5G),元空间 256–512MB;堆 dump 体积≈活跃对象大小,8G 堆 dump 可能 6–10G 文件。告警:老年代使用率>80% 或 Full GC 后仍>70%。对象增长速率:对比两次 jmap -histo(间隔 5–10 分钟),某类实例数/字节数线性上涨即可判定泄漏嫌疑。

  7. 失败与降级: OOM 高发实例先摘流再 dump;dump 失败/超时则用不带 :live 的 jmap -histo 粗定位——:live 会先触发一次 Full GC 来统计存活对象,堆正要耗尽时这一步最可能失败、也会把服务彻底拖死;只有堆尚有余量、想近似看 live 集时才用 :live。另外 dump 前先 df -h 校验目标分区剩余空间(本条自估 6–10G,与日志同盘时会把盘写满、制造二次故障),dump 完核对文件大小并 gzip 校验。业务侧降级:关掉大导出/大报表/大缓存构建;必要时临时 -Xmx 扩容争取时间,但必须同步排泄漏。若 DirectBuffer/Metaspace 型,堆 dump 无用——切 NMT/类加载排查,避免空转。

【原理溯源】

  • OOM 有几种,为什么要先分类型? 异常信息不同,根因完全不同:
    • Java heap space:堆内对象太多/太大;
    • Metaspace:类加载过多(动态代理、热部署、重复 ClassLoader);
    • Direct buffer memory:NIO/Netty 直接内存未释放;
    • unable to create new native thread:线程数超限(与堆无关,是 OS/ulimit)。 不分类型就 dump 堆,可能完全找错方向。
  • 为什么 dump 要“先留再重启”? OOM 现场只存在于当前进程内存。重启后对象图消失。-XX:+HeapDumpOnOutOfMemoryError 在 OOM 瞬间自动 dump,是最省事的保现场手段。注意 dump 会 STW,大堆可能停数秒到数十秒——可接受,因为反正要挂了。
  • MAT 的 Retained Heap 是什么? 某对象的保留集 = 若该对象被 GC,能一并释放的内存总和(独占支配的子图)。比 Shallow Heap(对象自身大小)有用得多——静态 Map 本身不大,但 Retained 可能是几个 G。
  • Path to GC Roots 为什么要 exclude weak/soft? 弱/软引用在内存紧张时会被回收,不构成“泄漏持有”。真正要找的是强引用链:静态字段、ThreadLocal value、未关闭资源。排除弱软后剩下的路径才是必须改代码的持有者。
  • 为什么 ThreadLocal 会泄漏? ThreadLocalMap 的 key 是对 ThreadLocal 的弱引用,value 是强引用。线程池线程长期复用不销毁 → ThreadLocal 对象被 GC 后 key 变 null,但 value 仍挂在 map 里 → 越积越多。根治是用完 remove()。

【选型判断树】

报 OOM,先读异常类型:
├─ Java heap space
│   ├─ 重启后几小时又满 → 疑似泄漏 → 自动 dump + MAT 支配树
│   └─ 特定请求后突然满 → 峰值/大对象 → 查批处理、大查询、大集合
├─ Metaspace
│   → 排查动态代理/热部署/重复 ClassLoader;调 MaxMetaspaceSize 只是缓解
├─ Direct buffer memory
│   → NMT + 排查 Netty/ByteBuffer 未 release;调 MaxDirectMemorySize
└─ unable to create native thread
    → 查线程数暴涨原因(线程池配置、每请求一线程);调 ulimit 是下策

判断口诀: 先看异常字符串分类型,再决定 dump 堆还是查元空间/直接内存/线程。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“OOM 排查第一步是分类型,第二步是保现场,第三步才是分析”
0:30–1:30分类型heap / Metaspace / Direct / native thread 四类
1:30–2:30保现场HeapDumpOnOutOfMemoryError + jmap;先 dump 再重启
2:30–3:30MAT 分析Leak Suspects → Retained Heap → Path to GC Roots
3:30–4:30典型泄漏静态集合、ThreadLocal、未关闭资源、无界缓存
4:30–5:00收尾“泄漏 vs 峰值看 GC 后是否回落、多 dump 是否持续增长”

【关键数字】

参数经验值说明
自动 dump-XX:+HeapDumpOnOutOfMemoryErrorOOM 瞬间自动 dump
dump 命令OOM 场景用 jmap -dump:format=b,file=heap.hprof <PID>(不带 live);堆尚有余量才考虑 :live:live 会先触发一次 Full GC,堆正要耗尽时最可能失败并拖死服务;dump 前先 df -h 校验目标分区
dump 耗时小堆秒级;大堆(>8G)可能数十秒会 STW
告警线堆使用率持续 >80%提前介入,别等 OOM
MAT 关键视图Leak Suspects / Dominator Tree / Histogram对比多份 dump
ThreadLocal 修复finally 中 remove()线程池场景必做

【追问链】(三层)

L1|“Retained Heap 和 Shallow Heap 差在哪?” → Shallow 是对象头+字段本身大小;Retained 是该对象被释放后能连带回收的全部内存。找泄漏看 Retained——一个静态 Map 的 Shallow 可能只有几 KB,Retained 却可能是整块缓存的几个 G。

L2|“为什么不直接调大 -Xmx?” → 若是泄漏,调大只是推迟 OOM,且更大的堆会让单次 GC 标记范围更大、STW 可能更长。必须先定位谁在持有引用。若是峰值且业务合理,才考虑扩容堆或优化单次加载量。

L3|“dump 文件几个 G,MAT 打不开怎么办?” → ① 用服务器上的 jmap -histo(或 jcmd <PID> GC.class_histogram)先看对象计数粗定位(jhat 自 JDK 9 已移除,且打不开 GB 级 hprof);② MAT 配置大内存(MemoryAnalyzer.ini 改 -Xmx);③ 用 jmap -histo(不带 live;:live 会先 Full GC,堆紧时不可靠)看 Top 类;④ 云上可传对象存储后在分析机打开。生产更推荐自动 dump + 离线分析流水线。

【评分标准】

档位答案特征
60 分知道 jmap dump、MAT 打开
80 分先分 OOM 类型;知道先保现场再重启;能说 Leak Suspects / Retained Heap
95 分讲清 ThreadLocal 弱引用 key 强引用 value 机制;用多 dump 对比区分泄漏与峰值;知道 Path to GC Roots 排除弱软;有自动 dump 与告警预防

【关联题】

  • 同一知识簇: 第 165 题(Full GC)、第 167 题(内存泄漏定位)、第 181 题(内存上涨不 OOM)
  • 对比: 直接内存泄漏看 NMT;元空间看类加载
  • 预防: 缓存用 Caffeine/Guava 自动淘汰(缓存章)

【自测】

  1. OOM 信息是 Metaspace,该 dump 堆吗? 参考答案:堆 dump 帮助有限。应排查类加载(动态代理/热部署/重复 ClassLoader),并确认 MaxMetaspaceSize。
  2. ThreadLocal 泄漏的根因一句话? 参考答案:线程池复用线程不销毁,ThreadLocalMap 的 key 弱引用被 GC 后 value 仍强引用滞留,不 remove 就堆积。
  3. 判断:OOM 后先分析再重启,以免现场丢失。 参考答案:顺序反了。应先自动/手动 dump 保留现场,再重启恢复服务;分析可以离线做。

165. 监控显示 Full GC 每 2 分钟一次,RT 飙升(Full GC 排查) ​

【考察内容】GC 排查是 JVM 调优高频题

【题目】监控告警:Full GC 每 2 分钟一次,接口 RT 从 50ms 飙到 5s。Full GC 为什么导致 RT 飙升(STW)?怎么定位是内存泄漏、大对象、还是参数配置问题?从 GC 日志和堆分析怎么看?

【参考答案】

  1. 确认现象:jstat -gcutil <PID> 1000 观察 FGC 次数与 FGCT,确认 Full GC 频率与耗时;

  2. 分析原因(Full GC 通常是老年代/元空间满了,或大对象直接进老年代):

    • 对象分配过快(大量请求创建临时对象,晋升到老年代)——查是否有大流量 / 循环创建大对象;
    • 内存泄漏(老年代对象只增不减)——dump + MAT 分析(见第 164 题);
    • 大对象 / 缓存:缓存无界(本地缓存放太多)、大数组 / 大 List 直接进老年代;
    • 元空间满(类加载过多,-XX:MaxMetaspaceSize);
    • 晋升阈值问题(对象过早晋升);
  3. 调优手段:

    • 定位后优先修代码(泄漏 / 大对象 / 缓存淘汰),调参是辅助;
    • 调整堆大小(-Xmx/-Xms 一致)、新生代比例(-Xmn/-XX:NewRatio)、晋升阈值(-XX:MaxTenuringThreshold);
    • 换 GC 策略(G1 调 -XX:MaxGCPauseMillis;大堆用 ZGC 低停顿);
    • 排查外部因素:慢 SQL 导致连接持有大结果集、批量任务并发;
  4. 监控:GC 日志(-Xlog:gc*)、GC 耗时与 RT 曲线关联。

  5. 容量估算: Full GC 目标频率:健康系统 FGC 应接近 0(G1/CMS 日均个位数或更低);每 2 分钟一次属于严重异常。STW 预算:核心交易接口 P99 目标常 <200ms,单次 FGC STW>500ms 即直接打爆超时。新生代/老年代经验:对象多为短命时新生代可占堆 1/3–1/2;若 Young GC 频率>1 次/秒且 STW 可感知,先看分配速率。

  6. 失败与降级: GC 风暴中优先限流降 QPS,减少分配压力;关闭非核心异步任务/大批量 Job。确认泄漏后热修无法立即上时:临时扩堆+提前摘流发布。若为 CMS 碎片(或 G1 晋升失败)导致 Full GC,可临时切 G1/ZGC——但这不是「应急动作」:换收集器要改 JVM 参数并滚动重启,GC 风暴里重启等于把止血做成一次发布,止血窗口反而被拉长;应急仍按「限流降 QPS+摘流+(能不重启就)临时扩堆」,切 GC 归入当晚变更窗口的根治项并配回滚预案(需评估升级窗口),并保留 GC 日志供事后调优。

【原理溯源】

  • 为什么 Full GC 会导致 RT 飙升? Full GC 多数收集器是 STW(Stop-The-World):GC 期间所有应用线程暂停。RT = 排队 + 处理 + GC 暂停。一次 1–2 秒的 Full GC,意味着这段时间内所有请求都被冻结,P99 直接被打到秒级。
  • 为什么“频繁”比“单次耗时长”更危险? 每 2 分钟一次 × 2 秒 = 每小时停 60 秒,可用性直接损失约 1.7%。而且频繁 Full GC 说明老年代回收效果差(对象大量存活),通常预示内存泄漏或堆配置不合理。
  • 为什么“调参不能根治”? 参数只能改变 GC 触发的时机和代价,改不了“对象是否该存活”。如果是内存泄漏(引用没释放),堆再大也只是把 OOM 推迟。所以必须先用堆快照定位到“谁在持有引用”。
  • 为什么 jstat 是第一眼工具? 它开销极低(读的是 JVM 内部计数器,不触发 GC),可以在生产环境持续观察,先确认“是否真频繁、单次多久、回收效果如何”,再决定是否要 dump(dump 会 STW,代价高)。

【选型判断树】

Full GC 频繁,先分类根因(关键看“回收效果”):

├─ Full GC 后老年代占用明显下降(如降 50%+)
│  → 不是泄漏,是“对象分配 / 晋升过快”
│  → 手段:调大新生代、调晋升阈值、减少大对象、降低分配速率
├─ Full GC 后老年代几乎不降(< 10%)
│  → 内存泄漏特征
│  → 手段:dump + MAT 支配树找持有者 → 改代码(根治)
├─ 元空间满(Metaspace OOM)
│  → 类加载过多(动态代理 / 热部署)
│  → 手段:调 MaxMetaspaceSize + 排查重复类加载
└─ 大对象直接进老年代
   → 大数组 / 大 List / 无界缓存
   → 手段:拆大对象、给缓存设上限与淘汰策略

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“Full GC 问题的排查路径是:先用 jstat 确认现象 → 再用 GC 日志和堆快照定位根因 → 最后区分是代码问题还是参数问题”
0:30–1:30讲清危害STW 导致 RT 飙升;用“2 分钟一次 × 2 秒 = 每小时停 60 秒”量化影响
1:30–3:00根因分类分配过快 / 内存泄漏 / 大对象 / 元空间,每类给判断依据
3:00–4:30处置代码优先、调参其次;G1/ZGC 选型依据
4:30–5:00收尾“一句话:Full GC 是症状不是病因,必须找到是哪个对象在制造压力”

【关键数字】

参数经验值说明
观察命令jstat -gcutil <PID> 1000每秒一次,开销极低
健康线Full GC 频率 < 每天几次;单次 STW < 200ms超过需关注
危险线Full GC ≥ 每 5 分钟 1 次,或单次 STW > 1s需立即处置;正文按“每 2 分钟一次”判严重异常,两处口径一致
堆 dumpjmap -dump:format=b,file=heap.hprof <PID>(:live 仅堆有余量时用)会 STW,大堆可能数秒;先摘流再 dump
推荐配置-XX:+HeapDumpOnOutOfMemoryErrorOOM 时自动 dump,无需人工介入
新生代比例-XX:NewRatio 默认 2(老:新 = 2:1)或用 -Xmn 直接指定
G1 目标停顿-XX:MaxGCPauseMillis=200默认 200ms
大堆建议> 16GB 考虑 ZGC停顿 < 10ms

【追问链】(三层)

L1|“Full GC 后内存只降了 5%,说明什么?” → 说明绝大部分对象都还活着,是内存泄漏的典型特征。下一步必须 dump 堆快照,用 MAT 的支配树(Dominator Tree)找到持有大量对象的根引用,再回到代码定位。

L2|“G1 和 CMS 有什么区别?该怎么选?” → G1 是分区(Region)化的,可预测停顿(按 MaxGCPauseMillis 目标挑选回收 Region),且是标记-整理,不会像 CMS 那样碎片化。CMS 是并发标记-清除,有碎片问题,且 JDK 14 已移除。新项目默认 G1;大堆(>16G)且停顿敏感用 ZGC。

L3|“线上真出 Full GC,你按什么顺序操作?” → ① jstat 确认频率与耗时(低开销);② 看 GC 日志确认是哪种 GC、回收效果;③ 如果回收效果差 → 低峰期 dump + MAT 分析(或开 HeapDumpOnOutOfMemoryError 等它自己 dump);④ 定位到代码后先热修复 / 降级止血;⑤ 根治 + 加 GC 监控告警。

【评分标准】

档位答案特征
60 分知道 Full GC 会 STW 导致 RT 飙升,能说出 jstat
80 分能分类根因(分配过快 / 泄漏 / 大对象 / 元空间)并给出对应手段;知道“调参不能根治泄漏”
95 分能用“回收效果”区分泄漏与分配过快;说出 MAT 支配树这个具体工具;给出 G1/CMS/ZGC 的选型依据与参数;有“先低开销观察、再高代价 dump”的顺序意识

【关联题】

  • 同一知识簇(JVM 线上排查): 第 163 题(CPU 100%)、第 164 题(OOM)、第 167 题(内存泄漏)、第 169 题(线程池打满)、第 172 题(服务 Hang)、第 181 题(内存上涨排查)
  • 工具延伸: 第 168 题(死锁排查,jstack)
  • 国企 / 金融版: 第 223 题(现场分析 GC 日志)、第 225 题(生产库 CPU 100% 应急)

【自测】

  1. jstat 和 jmap 的开销差别在哪?生产上先用哪个? 参考答案: jstat 读 JVM 内部计数器,开销极低、不触发 GC;jmap dump 会 STW,大堆可能停顿数秒。所以先用 jstat 确认现象,再决定是否需要 dump。
  2. 判断:把 -Xmx 调大就能解决 Full GC 频繁。 参考答案: 错。若根因是内存泄漏,调大堆只是推迟 OOM;若根因是分配过快,调大堆可能略缓解但会增加单次 GC 耗时(标记扫描范围更大)。
  3. Full GC 每 2 分钟一次、每次 2 秒,可用性损失多少? 参考答案: 每小时停 60 秒,可用性损失约 1.7%;若按 99.99% 目标(全年 52.6 分钟),这个频率一天就吃掉 24 分钟、2.2 天耗尽全年预算(当天可用性只有 98.33%)。

166. 平时 100ms 的接口,现在 60% 请求超过 1s(接口超时排查) ​

【考察内容】接口超时是全链路排查最高频题

【题目】某核心接口平时 P50 100ms,今天开始 60% 的请求超过 1s,但服务没重启、也没发版。可能的原因有哪些(依赖变慢、DB 慢查询、GC、资源竞争、网络)?你按什么顺序排查、用什么工具?

【参考答案】按“现象确认→分层定位→根因→修复”:

  1. 确认范围:单接口还是全站?(全站=基础设施问题);超时分布(P50/P99);是否有发布/流量变化时间点;

  2. 调用链定位:SkyWalking/Zipkin 看请求在哪个环节耗时最长(自身逻辑/DB/Redis/下游服务/MQ);

  3. 分层排查:

    • 应用层:线程池是否打满(等待线程池队列=并发超限)、锁等待(jstack 看 BLOCKED)、GC 停顿;
    • 中间件:Redis 慢查询/大 key、连接池耗尽;
    • 数据库:慢 SQL(慢日志+EXPLAIN)、锁等待(processlist 看 Waiting);
    • 下游:依赖服务 RT 飙升(调用链看下游耗时);
    • 机器:CPU/内存/磁盘 IO/带宽/网络(丢包、TCP 重传);
  4. 常见根因:慢 SQL、下游变慢、线程池/连接池打满、GC、热点 key、网络;

  5. 修复:对应优化+压测验证+监控告警(RT 阈值告警,防再次发生)。

  6. 容量估算: 先画延迟预算:网关→应用→缓存→DB→下游,假设正常 100ms=网关5+应用20+缓存5+DB50+下游20。60% 请求>1s 说明是共享资源排队(DB/连接池/线程池/下游)而非单接口逻辑突变。超时配置经验值:内部 RPC 超时应 < 上游超时(避免上游已断下游仍占资源);连接池等待>200ms 即应告警。排查样本:按接口/实例/下游三维交叉,定位是否局部。

  7. 失败与降级: 应急:对慢接口限流或超时快速失败(fail-fast);非核心依赖降级/熔断;必要时扩容应用或只读副本。根因未明时禁止盲目调大超时——会放大排队、拖垮线程池。保留:慢查询日志、链路 Trace、线程池队列深度,供事后定位。

【原理溯源】

  • 为什么“没发版也会变慢”? 性能是供需关系:要么需求涨了(流量、大 key、慢查询),要么供给降了(下游变慢、资源被抢、GC 变差、磁盘 IO 争用)。发版只是变更之一,不是唯一变量。
  • 为什么必须先看调用链而不是直接看代码? 超时可能发生在自身逻辑、DB、Redis、下游任一环。没有 Trace 分层,会陷入“代码没改怎么变慢”的错误假设。Trace 把 RT 拆成各 Span 耗时,直接指认瓶颈层。
  • 仍有约 40% 请求正常、60% 超过 1s 说明什么? 分布已双峰/长尾化:一部分请求仍快,另一批被某共同因素拖住——典型是连接池排队、锁等待、慢 SQL 命中、GC 停顿、热点分片。要按“超时请求的共同特征”(同一接口/同一参数/同一时段)聚类。
  • 线程池打满为什么表现为超时而不是报错? 任务进队列排队,等待时间叠加到 RT。只有队列满才触发拒绝策略。所以监控要看队列深度和活跃线程,不能只看错误率。
  • 连接池耗尽的因果链: 某慢 SQL/下游拖长连接持有时间 → 池中可用连接下降 → 新请求阻塞在 borrow → RT 整体抬升 → 看起来“全站变慢”,根因却可能只是一条 SQL。

【选型判断树】

接口变慢,先问范围与时间点:
├─ 刚有发布/配置变更 → 优先回滚验证(174/180 题)
├─ 全站都慢 → 基础设施:网络/磁盘/宿主机/网关/公共下游
└─ 单接口或部分接口慢
   ├─ 调用链显示某下游/DB Span 变长 → 查该依赖(慢SQL/慢服务/大key)
   ├─ 自身逻辑 Span 变长
   │   ├─ GC 停顿(jstat 对齐时间点)
   │   ├─ 锁等待(jstack BLOCKED)
   │   └─ 线程池/连接池排队(池监控 active=max)
   └─ 无明显 Span 变长但整体慢 → 网络抖动/丢包/TCP 重传

判断口诀: 先范围再时间点,再用 Trace 分层,最后对池/锁/GC/慢SQL逐项排除。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“超时排查本质是供需分析:需求涨了还是供给降了”
0:30–1:30现象确认范围、分布、时间点、是否变更
1:30–3:00调用链分层自身/DB/Redis/下游/Machine 五层,每层典型根因
3:00–4:00池与锁线程池排队、连接池耗尽的传导机制
4:00–5:00收尾“先 Trace 定层,再对症;修完必须压测+告警”

【关键数字】

参数经验值说明
超时设置原则> 下游 P99,< 上游容忍反向传导会雪崩
线程池危险活跃=最大 且队列持续涨已打满
连接池危险active=max 且 wait 时间长借不到连接
GC 对齐STW 时间点与超时点重叠证明是 GC
慢 SQL 阈值通常 100ms–1s(按业务)先看长尾
排查目标10 分钟内给分诊结论先止血再根治

【追问链】(三层)

L1|“P99 高但平均不高说明什么?” → 存在长尾请求。平均被大量快请求稀释,尾部用户体验已受损。常见原因:慢 SQL 偶发命中、GC 停顿、大请求污染、热点用户。要优化 P99 而不是平均。

L2|“超时时间怎么定?” → 链路自上而下:最外层用户超时最长,每往内一层递减,保证内层先超时,快速失败并释放资源。经验值:HTTP 客户端连接 200–500ms、读 1–3s(按业务);必须大于下游 P99,否则自己制造超时风暴。

L3|“调用链显示都正常,但用户仍超时,查什么?” → 查链路外因素:① 客户端到接入层网络/DNS/TLS;② 网关限流/排队;③ 容器 CPU throttle(cgroup 限制);④ 安全组/防火墙丢包;⑤ 前端资源加载。服务端指标“正常”不等于用户路径正常。

【评分标准】

档位答案特征
60 分能列出慢SQL、下游、GC 等几个原因
80 分有“范围→Trace→分层→根因”顺序;能说线程池/连接池传导
95 分供需分析框架;超时时间链式设置原则;P99 长尾洞察;能指出链路外因素

【关联题】

  • 同一知识簇: 第 165 题(GC)、第 169 题(线程池)、第 170 题(Redis)、第 172 题(Hang)、第 182 题(间歇抖动)
  • 方法论: 调用链/TraceID 关联;第 178 题(错误率分诊)
  • 上游: 慢 SQL 与 EXPLAIN(数据库章)

【自测】

  1. 接口超时,第一步看代码还是看调用链? 参考答案:看调用链。先确认耗时在哪一层,避免盲目改代码。
  2. 判断:线程池打满一定会报错。 参考答案:错。队列未满时任务排队,表现为 RT 上升;队列满才触发拒绝策略。
  3. 超时时间应该内层更长还是外层更长? 参考答案:外层更长、内层更短。内层先超时快速失败,避免资源被拖死。

167. 服务内存持续上涨,GC 后也不回落(内存泄漏) ​

【考察内容】内存泄漏定位是 JVM 排查进阶必考题

【题目】服务运行一周后堆内存从 2G 涨到 6G,Full GC 后也不回落,怀疑内存泄漏。怎么确认是泄漏而不是缓存/正常增长?怎么用 MAT/Arthas 定位到具体对象和代码位置?常见泄漏模式有哪些?

【参考答案】

  1. 判断泄漏:监控堆使用率——GC 后曲线持续上升不回落到基线=泄漏;若 GC 后回落=只是压力大;

  2. 定位步骤:

    • 连续 dump 两份堆快照(间隔一段时间,相同负载下);
    • MAT 打开:比较两份 dump(Histogram 对比)——找出增长最快的类;
    • 不想先拖 6G 大 dump 时,用 Arthas 在线取证(题干点名 MAT/Arthas 两条路都要答):dashboard/memory 看各区占用与 GC 趋势,thread -b 看阻塞,jmap -histo(不带 live)或 Arthas heapdump 前先看对象排行,vmtool --action getInstances --className com.x.Foo --limit 10 直接取实例、再用 ognl 看它的字段,确认「谁持有」再上 MAT 的 Path to GC Roots;
    • 对增长类右键 → Path to GC Roots(exclude weak/soft references)——看是谁持有它不放(静态字段、ThreadLocal、缓存、监听器);
    • 定位到代码后修复(释放引用/remove/加淘汰);
  3. 常见泄漏模式:静态集合当缓存(无界)、ThreadLocal 不 remove、连接/流未关闭、事件监听未注销、内部类持有外部类引用(匿名内部类/非静态内部类);

  4. 预防:

    • 代码审查关注静态集合/缓存生命周期;
    • 缓存用 Guava/Caffeine(自带淘汰)而非裸 HashMap;
    • 资源用 try-with-resources;
    • 上线前压测观察内存曲线、堆 80% 告警、定时 dump 基线对比。
  5. 容量估算: 内存曲线判据:GC 后回落值(老年代水位)应长期平稳;若每次 GC 后水位抬升 50–200MB 且呈线性,按“每小时涨幅×重启周期”推算何时 OOM。监控采样:heap used after GC、线程数、FD 数、连接数、本地缓存条目数。经验:线程数每涨 1MB 栈(-Xss 默认约 1MB)×N 线程也是堆外内存的主要来源(1000 线程≈1GB);ThreadLocal 缓存常见涨幅可达 MB/天量级。

  6. 失败与降级: 未定位前:定时摘流滚动重启(治标,换时间);关闭可疑功能开关(同步导出、调试日志、本地大缓存)。若泄漏在第三方 SDK:升级/替换或外挂隔离进程。重启策略必须滚动,且重启前尽量留下 histo/diff 证据。

【原理溯源】

  • 为什么“GC 后不回落”是泄漏金标准? Full GC 会回收所有不可达对象。若回收后老年代占用仍高于历史基线且逐次抬升,说明存在强引用链持续持有本应释放的对象——这就是泄漏的定义。缓存/高峰是“可达但可淘汰”,GC 后会下来。
  • 为什么要双 dump 对比而不是单份? 单份只能看到“现在谁大”,分不清是合法缓存还是泄漏。两份间隔 dump 做 Histogram diff,增量才是泄漏嫌疑。相同负载、相同流量阶段对比,减少噪声。
  • Path to GC Roots 在找什么? 从泄漏对象向上回溯到 GC Root(静态字段、活跃线程栈、JNI)。路径上第一个“不该长期持有”的节点就是代码缺陷点。排除 weak/soft 是因为它们内存紧张时会被回收,不构成硬泄漏。
  • 匿名内部类为什么容易泄漏? 非静态内部类/匿名类持有外部类 this 引用。若该内部类被静态集合或长生命周期监听器持有,外部类整棵对象图都无法回收——经典 Android/回调泄漏,后端同样存在(异步回调、事件总线)。
  • WeakReference 能根治吗? 只适合“可丢弃缓存”。若业务强依赖该对象(如会话、连接),弱引用会在 GC 时丢数据。根治是控制生命周期:明确谁创建谁释放,而不是把引用改弱。

【选型判断树】

内存涨,先看 GC 后是否回落:
├─ 回落到基线 → 非泄漏,是流量/缓存高峰
│   → 调容量或缓存上限,不是修"泄漏"
├─ 不回落且持续抬升 → 泄漏
│   ├─ 能稳定复现增长 → 双 dump Histogram diff → Path to GC Roots
│   └─ 增长很慢(天级)→ 定时基线 dump + 自动化对比告警
└─ 堆没涨但 RSS 涨 → 堆外(见 181 题)
    → NMT / 线程数 / 元空间 / 直接内存

判断口诀: 先问“GC 后回不回落”,再决定是容量问题还是引用问题。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“泄漏判定看 GC 后是否回落,定位靠双 dump 对比”
0:30–1:30判定方法监控曲线 + jstat;回落 vs 不回落
1:30–3:00定位步骤Arthas 在线取证(dashboard/vmtool getInstances)→ 双 dump → Histogram diff → Path to GC Roots
3:00–4:00典型模式静态集合、ThreadLocal、未关闭资源、监听器、内部类
4:00–5:00预防Caffeine、try-with-resources、压测基线、80% 告警

【关键数字】

参数经验值说明
双 dump 间隔数小时~1 天(同负载阶段)太短增量噪声大
告警线堆使用率 80%;GC 后低点逐日抬升泄漏早期信号
线程栈内存默认 -Xss 约 1MB1000 线程≈1GB
缓存框架Caffeine/Guava 必须设 maximumSize 或 expire裸 Map=无界
ThreadLocalfinally 中 remove线程池必做
MAT 入口Histogram → Group by package → diff先粗后细

【追问链】(三层)

L1|“缓存越跑越大算泄漏吗?” → 看有无淘汰与业务预期。有界缓存(Caffeine maximumSize)涨到上限后稳定,是正常;无界静态 Map 只增不减,即使业务叫“缓存”,本质也是泄漏。判据是是否可回落到基线,不是名字。

L2|“Arthas 能定位泄漏吗?” → 可以辅助:memory 看内存区域;vmtool --action getInstances 拿实例;jad 确认代码。但精确引用链仍靠 MAT。Arthas 适合现场快速确认“哪类对象多”,MAT 适合根因引用链。

L3|“修完泄漏后如何防止回归?” → ① 代码评审清单:静态集合、ThreadLocal、监听器注册/反注册、资源关闭;② 压测内存曲线门禁;③ 定时 heap 基线对比告警;④ 关键缓存强制有界框架;⑤ 故障演练中加入“内存缓慢增长”场景。

【评分标准】

档位答案特征
60 分知道 dump + MAT,或 Arthas 在线取实例(题干点名两条路)
80 分会用 GC 后回落判定;能说双 dump 对比与 Path to GC Roots
95 分讲清 ThreadLocal/内部类机制;区分缓存高峰与泄漏;有预防体系(有界缓存、评审清单、基线告警)

【关联题】

  • 同一知识簇: 第 164 题(OOM)、第 165 题(Full GC)、第 181 题(内存上涨)
  • 工具: MAT 支配树、Arthas memory
  • 代码规范: try-with-resources、有界缓存

【自测】

  1. 如何用一句话区分“内存压力大”和“内存泄漏”? 参考答案:Full GC 后占用回落=压力大;不回落且低点持续抬升=泄漏。
  2. Path to GC Roots 为什么要排除 weak/soft references? 参考答案:它们在内存紧张时可被回收,不构成必须修复的强持有;要找的是强引用链上的持有者。
  3. 判断:把静态缓存改成 WeakHashMap 就彻底解决泄漏。 参考答案:不彻底。只适合可丢弃数据;业务强依赖时会丢数据。根治是控制生命周期与有界淘汰。

168. 服务部分请求卡死,怀疑死锁(死锁排查) ​

【考察内容】并发故障定位

【题目】线上部分请求一直不返回(卡死),但服务没崩。怀疑发生了死锁。怎么确认(jstack 看什么)?怎么定位是哪两把锁互相等?解决与预防(锁顺序、超时、减少锁粒度)怎么做?

【参考答案】

  1. 现象:某些线程永久 BLOCKED,接口超时,重启恢复但复发;

  2. 确认:jstack <PID> 查看线程状态——发现大量 java.lang.Thread.State: BLOCKED 且互相等待(jstack 会直接打印 Found one Java-level deadlock 及参与线程和锁);或 jstack -l 看锁信息;

  3. 定位:死锁报告会列出两个线程各自持有的锁和等待的锁——定位到代码(两个锁获取顺序不一致,如 A 拿锁1等锁2、B 拿锁2等锁1);

  4. 解决:

    • 统一加锁顺序(所有线程按固定顺序获取多把锁);
    • 用 tryLock(timeout)(ReentrantLock 超时放弃,替代 synchronized 无限等待);
    • 减少锁嵌套(一把锁搞定/锁粒度拆分);
    • 数据库死锁类似(见 60 题:统一顺序/短事务);
  5. 预防:代码审查(多锁获取顺序)、压测并发场景、死锁监控(jstack 定时扫描/Arthas thread -b 找阻塞线程)。

  6. 容量估算与检测: 死锁在 jstack 中表现为 Found one Java-level deadlock 或环形 BLOCKED。线程池场景:若活跃线程≈max 且 BLOCKED 占比>50%,高度可疑锁等待。数据库死锁与 JVM 死锁要分开:InnoDB 会自动回滚一方并报错;Java 死锁则永久卡死。告警:同接口成功率骤降+线程数打满+无 GC 异常 → 优先怀疑锁。

  7. 失败与降级: 应急:重启卡住实例(先摘流);DB 死锁可 kill 持锁会话(需 DBA 流程)。临时缓解:减小事务/锁粒度、超时获取锁(tryLock)、热点资源分片。业务降级:关闭并发写热点功能入口,改为排队/异步。

【原理溯源】

  • 死锁的四个必要条件是什么? 互斥、持有并等待、不可剥夺、循环等待。破坏任一即可防死锁。工程上最常用:统一加锁顺序破坏循环等待;tryLock 超时部分破坏“无限等待”。
  • jstack 为什么能自动报死锁? JVM 通过对象监视器的持有者关系构建等待图,发现环即判定死锁,并打印 Found one Java-level deadlock,列出每个线程“locked <A> / waiting to lock <B>”。看到这段就是铁证,不必自己推。
  • BLOCKED 和 WAITING 有什么区别? BLOCKED:在 synchronized 入口排队等监视器;WAITING:在 Object.wait()/LockSupport.park() 上无限等,需要被唤醒。死锁典型是 BLOCKED 环;连接池耗尽更多是 WAITING(等 condition)。
  • 为什么 ReentrantLock 更利于规避死锁? synchronized 只能阻塞等,无法超时退出;ReentrantLock.tryLock(timeout) 可在拿不到锁时放弃并走降级/重试,切断无限等待。还可配合公平锁、可中断锁。
  • 数据库死锁与 JVM 死锁同构吗? 同构:都是多事务/多线程以不同顺序持有并等待资源形成环。DB 由引擎检测并牺牲一个事务回滚;JVM 不会自动牺牲,靠应用改代码。所以 DB 死锁有“自动解除+重试”,JVM 死锁会卡死到重启。

【选型判断树】

请求卡死,jstack 看状态:
├─ 打印 Found one Java-level deadlock
│   → 真死锁:统一锁顺序 / tryLock / 减锁嵌套
├─ 大量 BLOCKED 但无死锁报告
│   → 热点锁竞争(非环):减粒度、读写锁、无锁结构、异步
├─ 大量 WAITING/TIMED_WAITING
│   → 等资源:连接池/线程池/下游(见 172 题)
└─ RUNNABLE 且 CPU 高
    → 死循环/计算(见 163 题),不是死锁

判断口诀: 先搜 deadlock 关键字,再按 BLOCKED/WAITING/RUNNABLE 分流。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“死锁是循环等待,jstack 能直接报出来”
0:30–1:30四条件互斥、持有等待、不可剥夺、循环等待
1:30–2:30确认方法jstack deadlock 段落怎么读
2:30–3:30解决统一顺序、tryLock、减锁
3:30–4:30与 DB 对比同构但 DB 自动牺牲一个事务
4:30–5:00收尾“防死锁优先统一顺序,兜底 tryLock”

【关键数字】

参数经验值说明
确认命令jstack <PID>搜 deadlock
阻塞线程jstack -l / Arthas thread -b找最忙阻塞线程
tryLock 超时100ms–2s(按业务)超时走降级
采样多次 jstack 间隔 5–10s确认长期卡住
DB 死锁引擎自动回滚一个事务应用需重试

【追问链】(三层)

L1|“synchronized 和 ReentrantLock 在死锁场景差在哪?” → ReentrantLock 可 tryLock 超时中断等待,可公平排队,可响应中断;synchronized 只能无限阻塞,无法超时。要规避死锁优先 ReentrantLock。

L2|“jstack 没报死锁,但线程一直 BLOCKED,为什么?” → 可能是热点锁竞争而非循环等待(只有等待没有环),或死锁发生在 DB/其他进程。看锁持有者是否长期不释放(慢操作持锁),减临界区或改并发结构。

L3|“线上已死锁,怎么最快恢复?” → ① 先分清是哪种锁,再决定能不能「打断」:synchronized 监视器上的 BLOCKED 线程是叫不醒的——interrupt() 只置标志位,线程要等持锁方释放监视器才会走(实测 interrupt 后 state 仍是 BLOCKED),本题上文也写了「synchronized 无法超时退出」;能中断的只有 ReentrantLock.lockInterruptibly()、LockSupport.park/wait 这类 WAITING 线程以及 DB 会话(kill 数据库 session 有效)。拿不准就直接走 ②;② 先抓现场(连续 2–3 次 jstack,必要时 jmap -histo),再摘流量重启该实例——顺序反了现场就没了;③ 多实例只重启卡死的那台;④ 事后统一锁顺序根治。多实例时先摘故障实例,避免全量重启。

【评分标准】

档位答案特征
60 分知道 jstack,听说过死锁
80 分会读 deadlock 报告;能说统一顺序和 tryLock
95 分讲清四条件与破坏方式;区分 BLOCKED/WAITING;对比 DB 死锁;有应急摘流量+留现场流程

【关联题】

  • 同一知识簇: 第 163 题(CPU)、第 172 题(Hang)、第 169 题(线程池)
  • DB 版: 第 60 题(数据库死锁)
  • 并发控制: 锁优化、读写锁、无锁

【自测】

  1. jstack 输出哪一行是死锁铁证? 参考答案:Found one Java-level deadlock,以及随后的 locked / waiting to lock 列表。
  2. 破坏死锁最常用的条件是哪一个? 参考答案:循环等待——所有线程按相同顺序获取多把锁。
  3. 判断:synchronized 比 ReentrantLock 更容易做超时规避。 参考答案:错。ReentrantLock 支持 tryLock(timeout),synchronized 无法超时。

169. 线程池活跃线程=最大线程数,队列持续增长(线程池打满) ​

【考察内容】并发资源治理

【题目】监控显示某个线程池活跃线程数已等于最大线程数、任务队列持续增长、接口 RT 飙升。线程池为什么被打满(上游流量涨、任务变慢、下游阻塞)?应急怎么处理?根治怎么做?

【参考答案】

  1. 根因判断(线程池满=任务处理速度跟不上提交速度):

    • 任务本身变慢(依赖下游变慢/DB 慢/锁竞争);
    • 任务量突增(流量上涨);
    • 线程池参数不合理(核心线程过小/队列过小);
    • 任务中有阻塞(线程被阻塞在 IO/锁上,看似忙实际在等);
  2. 应急:限流入口(挡住新任务,让队列消化);排查并修复下游/慢 SQL(根因);必要时扩容实例;

  3. 根治:

    • 调参数(核心/最大线程数、队列长度——与任务类型匹配);
    • 优化任务执行(异步化、批量、减少锁);
    • 拒绝策略:生产用自定义策略(告警+落库/MQ 补偿),避免静默丢弃;
    • 监控:队列深度、拒绝次数、任务耗时告警;
  4. 关键认知:线程池满通常是“结果”不是“原因”——找到任务变慢的源头(Arthas thread 看线程在干什么:WAITING 在 IO?RUNNABLE 在计算?)。

  5. 容量估算: 线程池参数起点(IO 密集):核心线程≈CPU核数×(1+等待/计算)≈核数×2–10(等待/计算比≈9 时是 10 倍;要到 20 倍得比值≈19);队列长度按“可容忍排队时间×峰值TPS”估算(如可忍 500ms、峰值 2000 TPS → 队列≈1000,过大反而雪崩)。监控:queue remaining capacity、active、completed task、reject count。经验值:队列持续增长且 active=max 连续 1 分钟 → 必须介入。

  6. 失败与降级: 立即限流入口 QPS 到“处理能力×0.8”;应急期不要选 CallerRuns:本条第 1 项已判定根因多是下游变慢,CallerRuns 会把被拒任务搬回提交线程(Tomcat/RPC 线程)执行,实测能让调用线程被占 2s 以上,等于把线程池过载扩散成全站超时;应急只做「落 MQ 延迟补偿+快速失败告警」,禁止 Discard 静默丢。CallerRuns 的适用面是「不允许丢且提交方确有余量」的离线/批处理,不是过载时的降级手段。下游熔断,避免线程被慢调用占死。扩容应用实例可线性分摊线程池压力(注意 DB/下游是否成为新瓶颈)。

【原理溯源】

  • 线程池打满的供需等式: 稳态要求 完成速率 ≥ 提交速率。打满要么提交变多(流量),要么完成变慢(任务 RT 上升)。加线程只是提高并行度,若根因是下游慢/锁竞争,加线程会加剧资源争抢(更多线程抢同一连接池/锁),反而更糟。
  • 为什么无界队列危险? 无界队列下最大线程数几乎不生效(任务永远先入队),队列可耗尽堆内存导致 OOM,且故障被无限推迟。生产必须有界队列 + 明确拒绝策略。
  • 拒绝策略为什么不能用默认 AbortPolicy 静默抛异常? 任务被丢且可能只打日志,业务丢失。生产应自定义:告警 + 降级 + 落库/MQ 补偿,保证“丢得起且可追”。
  • WAITING vs RUNNABLE 指向什么根因? WAITING/TIMED_WAITING:线程在等 IO/锁/连接——查下游、连接池、锁;RUNNABLE 且 CPU 高:在计算——查慢逻辑、序列化、正则。分流后再深挖,避免盲目加线程。
  • CPU 密集 vs IO 密集怎么配线程? CPU 密集:线程数 ≈ 核数(再多只是切换开销);IO 密集:可更高(线程大部分时间在等),经验 核数 * (1 + 等待/计算),或直接压测定。错配是常见人为故障。

【选型判断树】

线程池满,先看任务在等什么(Arthas/jstack):
├─ 大量 WAITING 在下游/DB/Redis
│   → 根因在依赖:修慢SQL/扩下游/加超时熔断
│   → 应急限流,不要盲目加线程
├─ RUNNABLE 且 CPU 高
│   → 任务计算慢:优化算法/异步化/拆批
├─ 提交速率暴涨(流量)
│   → 接入层限流 + 扩容实例
└─ 参数不合理
    ├─ CPU 密集 → 线程≈核数,有界小队列
    └─ IO 密集 → 可高于核数,压测确定

判断口诀: 池满是结果;先问任务慢在哪,再决定限流还是扩依赖还是加机器。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“线程池满是供需失衡的结果,不是原因”
0:30–1:30根因四类任务变慢、流量涨、参数差、任务阻塞
1:30–2:30状态分流WAITING=等资源;RUNNABLE=在计算
2:30–3:30应急限流止血、修依赖、扩容
3:30–4:30根治有界队列、拒绝策略、监控
4:30–5:00收尾“先定位慢源,再谈加线程”

【关键数字】

参数经验值说明
CPU 密集线程≈ CPU 核数再多只增切换
IO 密集线程核数的 2–10 倍压测确定
队列必须有界无界会 OOM 且 max 失效
拒绝策略告警+落库/MQ禁止静默丢
危险信号active=max 且 queue 持续涨已打满
观测Arthas thread / 池监控看状态分布

【追问链】(三层)

L1|“队列用有界还是无界?” → 有界。无界会让最大线程数失效,任务堆积吃内存,故障从“拒绝”变成 OOM。有界+拒绝策略才能快速失败并暴露容量问题。

L2|“直接加最大线程数行不行?” → 若任务是 IO 等待且下游还有余量,适当加可以;若是下游慢/锁竞争/连接池耗尽,加线程会让更多线程卡在同一点,恶化争抢。必须先看线程在等什么。

L3|“拒绝的任务怎么不丢?” → 自定义拒绝策略:① 告警;② 可降级的直接降级返回;③ 必须做的写本地表/MQ 由补偿任务重试;④ 对实时性要求高的直接快速失败让上游重试。关键是可观测、可补偿,不是吞掉。

【评分标准】

档位答案特征
60 分知道加线程或扩机器
80 分能分流量/任务慢/参数三类;说有界队列和拒绝策略
95 分“池满是结果”的根因思维;WAITING/RUNNABLE 分流;CPU/IO 密集配参;自定义拒绝+补偿闭环

【关联题】

  • 同一知识簇: 第 166 题(超时)、第 172 题(Hang)、第 168 题(死锁)
  • 容量: 限流降级、大促保障(173)
  • 线程池原理: core/max/queue/拒绝策略

【自测】

  1. 为什么无界队列会让“最大线程数”失效? 参考答案:任务优先入队,队列不满就不会创建超过核心数的线程,max 形同虚设,且可能 OOM。
  2. 线程池满时第一反应应该是加线程吗? 参考答案:不是。先看任务在等什么(下游/锁/计算),常需限流或修依赖,盲目加线程会加剧争抢。
  3. CPU 密集型任务线程数大约配多少? 参考答案:约等于 CPU 核数;再多只是上下文切换开销。

170. 线上 Redis 报连接超时、命令超时(Redis 故障排查) ​

【考察内容】中间件故障定位

【题目】线上应用开始报 Redis 连接超时、命令超时,部分接口受影响。可能的原因有哪些(网络、大 key 阻塞、慢命令、连接池耗尽、Redis 高负载、主从切换)?你按什么顺序排查?

【参考答案】

  1. 现象分类:

    • 连接超时(连不上):Redis 宕机/网络不通/连接数打满(maxclients)/客户端连接池耗尽/主从切换与故障转移(failover 秒级不可写,表现为建连闪断,靠重试窗口收敛);
    • 命令超时(连上但命令慢):大 key 阻塞、慢命令(KEYS/大范围)、CPU 打满、网络抖动、fork 阻塞(RDB/AOF 重写);
  2. 排查步骤:

    • 监控看 Redis 节点 CPU/内存/网络/连接数/慢日志(SLOWLOG);
    • 慢日志:SLOWLOG GET 看是否有慢命令(大 key 操作、KEYS、批量);
    • 大 key 扫描:--bigkeys(阻塞与带宽问题);
    • 客户端侧:连接池参数(maxTotal 是否够)、是否有泄漏(借用未归还);
    • 网络:延迟/丢包(redis-cli --latency);
    • 集群:某个分片倾斜(热 key)导致该分片慢;
  3. 解决:大 key/热 key 治理(见缓存章)、慢命令改 scan/分页、连接池调优、Redis 扩容(内存/CPU)、客户端重试+熔断(Redis 不可用降级本地缓存/直连 DB 限流);

  4. 预防:监控告警(连接数 80%、慢日志、CPU)、压测容量。

  5. 容量估算: Redis 单机经验值:简单命令 8–10 万 QPS 起,复杂命令/大 value 显著下降。连接数:客户端连接池一般每实例每应用 20–50,总连接 < maxclients(默认 1 万)的 70%。超时排查维度:① 命令本身慢(O(N) 大 key);② 网络/带宽;③ 连接池耗尽;④ 持久化/主从同步阻塞。慢日志 SLOWLOG GET 阈值建议 10–50ms。

  6. 失败与降级: 应急:摘掉读 Redis 的非核心路径,改查 DB(要限流防击穿);本地缓存兜底热点;必要时主从切换。若大 key/热 key:拆分、本地缓存、只读副本。故障期间禁止全量重建缓存,避免雪上加霜。

【原理溯源】

  • 为什么先分“连接超时 vs 命令超时”? 根因层完全不同:连接超时在握手/建连阶段(网络、宕机、maxclients、客户端池);命令超时在执行阶段(慢命令、大 key、CPU、fork)。不分清会在错误方向排查。
  • Redis 单线程下慢命令为什么全局拖死? 命令在单线程内串行执行。一条 KEYS * 或大 hash HGETALL 会阻塞所有后续命令,表现为集群性命令超时,而不只是那一个客户端慢。
  • fork 为什么会阻塞? RDB/AOF 重写时 fork 子进程,父进程 COW(写时复制)。fork 瞬间要拷贝页表,大内存实例(几十 G)fork 可能阻塞百毫秒级;之后写入多时 COW 复制也会增加延迟与内存。
  • 连接池耗尽和服务端 maxclients 差在哪? 客户端池耗尽:应用侧借不到连接,报的是池超时,Redis 服务端连接数可能不高。maxclients 打满:服务端拒绝新连接,所有客户端建连失败。监控要两侧都看。
  • 热 key 为什么表现为部分超时? Cluster 下热 key 落在单个分片,该分片 CPU/带宽打满,只有打到该分片的命令慢——所以“部分接口”受影响,符合症状。

【选型判断树】

Redis 异常,先分类型:
├─ 连接超时
│   ├─ 服务端不可达 → 宕机/网络/安全组
│   ├─ 服务端连接满 → maxclients / 客户端泄漏
│   └─ 客户端池满 → maxTotal 调优 / 泄漏归还
└─ 命令超时
    ├─ SLOWLOG 有大命令 → 治理大key/KEYS/HGETALL
    ├─ CPU 高但无慢日志 → 热key分片倾斜 / 高QPS
    ├─ 与持久化时间重叠 → fork 阻塞
    └─ redis-cli --latency 高 → 网络问题

判断口诀: 连接还是命令;服务端还是客户端;慢命令还是热 key 还是网络。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“先分连接超时和命令超时,根因层不同”
0:30–1:30连接类宕机、网络、maxclients、客户端池、主从切换与故障转移
1:30–3:00命令类慢命令、大key、热key、fork、CPU
3:00–4:00工具SLOWLOG、--bigkeys、--latency、池监控
4:00–5:00收尾“降级:本地缓存兜底 + DB 限流保护”

【关键数字】

参数经验值说明
maxclients默认 10000按连接模型调整
SLOWLOG默认记录 >10msSLOWLOG GET
大 keyString >10KB;集合元素 >5000需拆分
连接池告警active/max >80%提前扩容
--latency持续 >1ms 需关注网络抖动
降级本地缓存 + DB 限流缓存不可用预案

【追问链】(三层)

L1|“只重启 Redis 能好吗?” → 可能暂时恢复,但若根因是大 key/慢命令/连接泄漏/容量不足,会复发。重启前保留 SLOWLOG、监控、客户端日志。应急可重启,但必须事后根治。

L2|“Redis 挂了业务怎么保?” → 多级缓存:本地缓存兜底热点;关键路径降级静态化;DB 层限流防止雪崩;非核心功能熔断。预案要在故障前演练,不能故障时现写。

L3|“怎么区分是客户端池问题还是服务端问题?” → 看两侧指标:客户端池 active/wait、建连错误;服务端 connected_clients、rejected_connections、CPU、SLOWLOG。服务端正常而客户端 wait 高=池配置/泄漏;服务端 rejected>0=maxclients 或服务端故障。

【评分标准】

档位答案特征
60 分知道看 Redis 日志/重启
80 分区分连接/命令超时;会用 SLOWLOG
95 分客户端池 vs maxclients;热key分片;fork 阻塞;降级预案(本地缓存+DB限流)

【关联题】

  • 同一知识簇: 大 key/热 key 治理、第 32 题(单线程)、缓存章故障
  • 联动: 第 166 题(超时)、第 172 题(Hang)
  • 预防: 监控告警、压测容量

【自测】

  1. 连接超时和命令超时排查方向有何不同? 参考答案:连接超时查网络/宕机/maxclients/客户端池;命令超时查慢命令/大key/热key/CPU/fork。
  2. 为什么一条 KEYS * 会导致大量接口超时? 参考答案:Redis 单线程串行执行,该命令阻塞期间其他命令全部排队。
  3. Redis 不可用时业务侧第一道防线是什么? 参考答案:本地缓存兜底热点 + DB 限流保护,非核心功能熔断降级。

171. 磁盘 100% 写满,服务开始报错(磁盘故障) ​

【考察内容】基础设施故障

【题目】监控告警:某台机器磁盘使用率 100%,服务开始报“No space left on device”。什么文件把磁盘写满了(日志、dump、数据文件)?怎么应急释放空间?长期怎么治理(日志轮转、容量监控)?

【参考答案】

  1. 磁盘空间满排查:

    • df -h 看挂载点使用率;
    • du -sh * / du 按目录定位大文件;
    • 常见大文件:日志文件(应用日志/GC 日志/Nginx 日志无轮转)、dump 文件(OOM 自动 dump)、临时文件(未清理)、Kafka/ES 数据目录(保留策略问题)、Docker 镜像/容器日志;
  2. 应急:清理可删文件(旧日志/临时文件/过期备份)→ 释放空间;配置日志轮转(logrotate:按大小/天切割+保留 N 份)根治;

  3. 磁盘 IO 高排查:

    • iostat -x 1 看 util/await(IO 饱和?);
    • iotop 定位进程:数据库(慢 SQL 全表扫/刷盘)、ES(索引/合并)、大文件读写、日志写太多;
    • 数据库 IO 高:慢 SQL/无索引/缓存命中率低(buffer pool 小);
  4. 根治:日志治理(级别/采样/轮转)、索引优化、冷数据迁移(归档到低频盘/OSS)、Kafka/ES 保留策略调整、SSD 扩容或分层存储;

  5. 监控:磁盘使用率 80% 告警、IO util 告警。

  6. 容量估算: 磁盘水位:>80% 告警,>90% 高危,>95% 随时可能写失败。日志增长估算:单实例应用日志常见 1–10GB/天,access log 更高;core dump 可能=堆大小。inodes 也要看(大量小文件会 inode 满而 df 显示仍有空间)。保留策略:热日志 3–7 天,冷日志对象存储,压缩后归档。

  7. 失败与降级: 立即止血:清安全可删文件(旧日志、临时文件、容器 dangling image);日志切换到其他盘/对象存储;必要时只保留 error 级。应用降级:关闭 debug 日志、关闭本地文件缓存写入。根因修复后加磁盘巡检与自动清理任务。

【原理溯源】

  • 磁盘满和 IO 高是两条线,为什么要分开? 空间满=容量问题,写失败报 No space left;IO 高=吞吐/延迟问题,服务变慢但不一定满。处置完全不同:前者删文件/扩容/轮转;后者找写放大源头(慢SQL、合并、日志风暴)。
  • 为什么“只删文件”会复发? 删的是存量,产生速率没变。若日志无轮转、dump 无清理策略、Kafka 保留过长,几小时后又满。必须同时降产生速率(级别/采样)+ 限存量(轮转/保留策略)。
  • 为什么删了文件空间不释放? 进程仍持有已删除文件的 fd(如日志句柄),空间直到进程释放 fd 或重启才回收。lsof | grep deleted 可见。这是经典坑。
  • iostat 的 util/await 怎么读? util 接近 100% 表示设备时间被 IO 占满;await 是平均等待时间,持续升高说明排队。注意多盘 RAID/SSD 上 util 100% 不一定无余力,要结合 await 和业务 RT。
  • 数据库为什么容易打满 IO? 无索引全表扫描、buffer pool 偏小导致频繁读盘、redo/脏页刷盘、大事务写放大。先看慢 SQL 与命中率,而不是直接换盘。

【选型判断树】

磁盘告警,先分空间还是 IO:
├─ 使用率 100%,报 no space
│   ├─ df 定位挂载点 → du 找大目录/大文件
│   ├─ 常见:日志/dump/临时文件/Kafka-ES数据/容器日志
│   └─ 应急删可删文件 + 配轮转;deleted 未释放则重启进程
└─ 使用率不高但 IO util 高、服务慢
    ├─ iostat -x 看 util/await
    ├─ iotop 找进程
    └─ DB:慢SQL/命中率;ES:merge;日志:写风暴

判断口诀: 先 df 分空间/IO,再 du/iotop 定位,最后轮转+保留策略根治。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“磁盘问题分空间满和 IO 高两条线”
0:30–1:30空间满df/du;常见大文件类型
1:30–2:30应急与坑删文件、deleted 未释放、轮转
2:30–3:30IO 高iostat/iotop;DB 慢SQL 关联
3:30–4:30根治级别采样、保留策略、冷数据、扩容
4:30–5:00收尾“80% 告警,不要等 100%”

【关键数字】

参数经验值说明
告警线使用率 80%留缓冲
df / dudf -h → du -sh *先挂载点后目录
logrotate按天或 100–500MB 切,保留 7–30 份按合规
iostatiostat -x 1看 util/await
Kafka/ES 保留按容量规划,禁无限保留数据目录易满
deleted 文件lsof | grep deleted重启才释放

【追问链】(三层)

L1|“为什么日志会打满磁盘?” → 无轮转 + 全量 debug 日志 + 异常堆栈在循环里反复打印 + 高 QPS 未采样。治理:级别、轮转、采样、集中采集本地少留。

L2|“磁盘 IO 高要不要直接换 SSD?” → 先找源头。若是无索引全表扫或日志风暴,换 SSD 只是推迟瓶颈且浪费成本。用 iotop/slowlog 定位写放大,优化 SQL/合并策略/日志后仍有需求再升配。

L3|“容器环境磁盘满常见原因?” → 容器日志(json-file 无上限)、镜像层堆积、emptyDir/临时卷、节点上多 Pod 共享盘。用 docker system prune、配置 log-opts max-size、节点容量监控与驱逐策略。

【评分标准】

档位答案特征
60 分知道 df/du 删文件
80 分分空间/IO 两条线;会 iostat;知道轮转
95 分deleted 未释放、DB IO 与慢SQL、日志风暴治理、容器日志;80% 告警体系

【关联题】

  • 同一知识簇: 第 176 题(日志)、第 165 题(GC 日志)、数据库慢 SQL
  • 联动: 第 172 题(Hang,IO 阻塞)
  • 预防: 容量监控、logrotate

【自测】

  1. df 和 du 分别看什么? 参考答案:df 看文件系统/挂载点使用率;du 统计目录/文件占用,用于定位大文件。
  2. 为什么删除大日志后空间可能没减少? 参考答案:进程仍持有该文件 fd,空间要等进程释放或重启。lsof|grep deleted 可确认。
  3. 磁盘 IO 高与空间满的排查分叉点是什么? 参考答案:空间满看容量(df/du/清理/轮转);IO 高看吞吐延迟(iostat/iotop/写放大源头)。

172. 服务既不报错也不响应,jstack 看到大片线程卡住(服务 Hang) ​

【考察内容】服务 Hang 是最棘手的线上问题之一

【题目】服务“假死”:不报错、不响应、不崩溃,健康检查都过不去。jstack 看到大量线程卡在同一个地方。可能是什么(锁等待、外部调用阻塞、IO 阻塞)?怎么快速定位卡点并恢复?

【参考答案】

  1. 先区分线程状态(jstack):

    • 大量 RUNNABLE 且 CPU 高:死循环/密集计算(见 CPU 排查);
    • 大量 BLOCKED:锁竞争/死锁(见死锁排查);
    • 大量 WAITING/TIMED_WAITING(在锁/条件变量上):常见——业务线程都在等待某个资源:连接池(DB/Redis/HTTP 连接池耗尽)、线程池队列(任务排队)、下游服务(同步调用无超时);
    • WAITING 在 socketRead:都在等下游/网络(下游不返回,超时未设置);
  2. 定位方法:

    • jstack 多次采样(间隔几秒),看线程是否长时间停留在同一位置;
    • 看“等待什么”:堆栈里的 Locked ownable synchronizers / waiting on 信息;
    • 结合调用链/连接池监控:连接池 active=最大=池耗尽;
  3. 常见根因与解决:

    • 连接池耗尽(DB/下游):请求全在等连接——查慢 SQL/泄漏/下游 RT,调池大小;
    • 下游无超时:同步调用没设 timeout,下游挂起拖死上游——所有远程调用必须设超时;
    • 线程死锁(见 168);
    • GC 长时间停顿(STW):看 GC 日志;
  4. 应急:Arthas thread -b(找阻塞线程)/thread -n(最忙线程);必要时摘流量/重启,恢复后分析根因。

  5. 容量估算与判据: Hang 的典型信号:进程在、端口在,但 RT 趋近超时或线程全部 WAITING/BLOCKED;健康检查若只探端口会漏检,应探“业务就绪”接口。jstack 大片 WAITING 常见栈:Object.wait、连接池、take()、ZK/注册中心、死锁环。判据:连续 3 次 jstack(间隔 5s)同一堆栈聚类 → 非瞬时抖动。

  6. 失败与降级: 先摘流防扩大;对 Hang 实例滚动重启;下游不可达则熔断降级走兜底数据。不要对 Hang 实例做重量级 dump(可能更卡),先 kill -3/轻量 jstack。恢复后必须复盘:是依赖、锁、还是资源泄漏导致。

【原理溯源】

  • 为什么“不报错”也是故障? 线程阻塞在等待时不会抛异常,进程仍存活,健康检查若只探活不探就绪会通过。用户侧表现为超时。这是“假死”——比崩溃更难发现,必须 readiness + RT 告警。
  • 为什么 jstack 状态能分流根因? 状态机直接映射行为:RUNNABLE=占用 CPU 执行;BLOCKED=等监视器锁;WAITING=无限等(wait/park/连接池);TIMED_WAITING=带超时等。大片同栈 WAITING 说明在等同一类资源。
  • socketRead 卡住意味着什么? 线程阻塞在读下游响应。若未设 SO_TIMEOUT,可以永久卡住。一个下游故障 + 无超时 = 上游线程池被慢慢抽干,级联雪崩。
  • 连接池耗尽的因果闭环: 下游慢 → 连接持有时间变长 → 池中空闲连接减少 → 新请求排队借连接 → 线程全部 WAITING 在池 → 服务假死。根因往往不在池参数,而在“为什么持有变长”。
  • 为什么必须所有远程调用设超时? 超时=快速失败,切断故障传导。没有超时的依赖是雪崩放大器。原则:连接超时短、读超时按下游 P99 设,且小于上游超时。

【选型判断树】

服务 Hang,jstack 按状态分流:
├─ RUNNABLE + CPU 高 → 死循环/计算(163 题)
├─ BLOCKED → 锁/死锁(168 题)
├─ WAITING 大片在同一连接池/condition
│   → 池耗尽:查持有者(慢SQL/下游/泄漏)
├─ WAITING 在 socketRead
│   → 下游无超时或下游挂:补超时+熔断
└─ 全体卡住且 GC 日志有长 STW
    → GC 停顿(165 题)

判断口诀: 状态分流 → 看栈顶等什么 → 对照池监控/下游/GC。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“Hang 是线程集体等待,用 jstack 状态分流”
0:30–2:00四态分流RUNNABLE/BLOCKED/WAITING/socketRead
2:00–3:00池耗尽闭环下游慢→持有长→池满→假死
3:00–4:00超时原则所有远程调用必须设超时
4:00–5:00应急Arthas thread -b;摘流量;留现场

【关键数字】

参数经验值说明
jstack 采样2–3 次,间隔 5–10s确认长期停留
连接超时200–500ms建连
读超时> 下游 P99,1–3s 常见必须设置
池危险active=max 且 wait 上升耗尽
Arthasthread -b 阻塞;thread -n 忙快速定位
健康检查liveness ≠ readinessHang 时 readiness 应失败

【追问链】(三层)

L1|“怎么确认是连接池耗尽?” → 线程堆栈大量 waiting on 连接池的 condition/await + 监控 active 连接接近 max + 借用等待时间飙升。三者同时出现即可确认。

L2|“为什么必须设超时?” → 超时=快速失败,防止故障无限传导。无超时时,下游挂起会逐步抽干线程池与连接池,把局部故障放大成全站雪崩。

L3|“应急选摘流量还是直接重启?” → 单实例:先摘流量(避免继续堆积),保留 jstack 后重启。多实例:只摘/重启故障实例,观察是否复发。重启是止血不是根治,必须回捞现场做根因。

【评分标准】

档位答案特征
60 分知道 jstack 看线程
80 分能按 RUNNABLE/BLOCKED/WAITING 分流;说出池耗尽
95 分socketRead 与无超时雪崩;池耗尽因果闭环;readiness 与 liveness 区别;应急留现场纪律

【关联题】

  • 同一知识簇: 第 163 题(CPU)、第 168 题(死锁)、第 169 题(线程池)、第 166 题(超时)
  • 工程原则: 超时、熔断、限流、降级
  • 探针: K8s liveness/readiness

【自测】

  1. 服务 Hang 时 jstack 最常见的线程状态是? 参考答案:WAITING/TIMED_WAITING(等连接池、下游、条件变量);也可能是 BLOCKED(锁)。
  2. 所有远程调用必须设置什么?为什么? 参考答案:必须设超时。实现快速失败,避免下游挂起拖死上游线程池,防止雪崩。
  3. liveness 和 readiness 在 Hang 场景分别应如何表现? 参考答案:进程活着 liveness 可通过;但不能接流量时 readiness 应失败,避免继续打到假死实例。

173. 双 11 前一周,负责人要做哪些准备(大促保障) ​

【考察内容】容量工程与保障体系

【题目】双 11 还有一周,你是技术负责人。从容量评估、压测、预案、监控、值班、回滚方案几个角度,列出你一周内必须完成的事项清单,并说明每项的验收标准。

【参考答案】

  1. 容量评估与压测:预估峰值流量(历史数据×增长系数),全链路压测找瓶颈(DB/缓存/网关/下游),验证容量达标;不达标的扩容/优化;

  2. 缓存预热:热点商品/数据提前加载缓存(多级缓存预热),配置 TTL 错峰;

  3. 限流降级预案:网关/应用限流阈值配置;降级清单(哪些功能可关、降级顺序);熔断阈值调整;

  4. 架构保障:核心链路检查(无单点、主从/集群就绪、MQ 容量、DB 连接池/线程池参数);秒杀等专项预案(URL 动态化、库存预扣);

  5. 数据保障:数据库备份验证、binlog 保留、对账任务就绪;

  6. 监控与值班:告警阈值设置(RT/错误率/积压/GC)、监控大盘、值班排班、应急预案文档(SOP:每个场景的处理步骤+联系人);

  7. 演练:故障演练(缓存挂/DB 慢/下游故障)、预案演练(限流触发/降级切换),确保预案可用;

  8. 上线窗口:大促前冻结发版(避免新代码风险)。

  9. 容量估算(大促准备): 按预估峰值 QPS 做压测:压测流量≥目标峰值×1.2–1.5;容量=单机能力×机器数×0.7(留安全水位)。核心接口 RT 预算、DB QPS、Redis QPS、MQ 吞吐都要有数。预案矩阵:每个依赖挂了怎么办(限流阈值、降级开关、扩容机器数)。演练项:全链路压测、故障注入、切换演练、回滚演练各至少 1 次——其中回滚/切换演练必须先于发版冻结完成(演练本身要发版),与【选型判断树】里「D-6 演练、D-5 起冻结」的顺序对齐;

  10. 失败与降级: 大促当天值班表+升级路径;预置开关:非核心页静态化、推荐/评论降级、写路径排队;限流阈值按分层配置(网关/应用/用户)。出问题优先保:下单、支付、登录;可弃:营销活动页、非核心推荐。

【原理溯源】

  • 为什么大促核心是“供需+故障面”双准备? 峰值是确定的高负载,故障是概率事件。容量工程解决“扛不扛得住”,预案演练解决“扛不住时怎么活”。只压测不演练,等于有灭火器但没人会用。
  • 为什么要缓存预热? 大促开始瞬间热点数据全量回源会打穿 DB(缓存雪崩)。预热把加载成本前移;TTL 错峰避免大量 key 同时过期。
  • 为什么冻结发版? 新代码是最大不确定因素。大促窗口以稳为主,任何变更(代码/配置)都可能引入新故障面。冻结用制度把“变更风险”清零。
  • 为什么预案必须文档化 SOP? 故障时人处于高压,靠记忆容易漏步骤。SOP 把“谁、在什么信号下、做什么、找谁”写死,值班可照做。验收标准是“演练能按 SOP 完成”。
  • 限流阈值怎么定? 压测得到的系统安全水位 × 安全系数(如 80%)。网关先限,应用兜底。降级清单按“核心交易 > 展示 > 推荐/通知”排序,先关低价值功能。

【选型判断树】

大促准备,按时间与风险排:
├─ D-7:容量评估 + 全链路压测 → 不达标先优化/扩容
├─ D-6:故障演练 + 预案演练 + 切换/回滚演练(必须真演练;回滚演练本身要发一次版,所以必须排在冻结之前)
├─ D-5:预案编写 + 限流降级阈值 + 监控告警
├─ D-5 起:发版冻结(大促前 3–7 天,见【关键数字】;冻结后只做开关验证与桌面推演,不再发版)
├─ D-1:缓存预热 + 确认冻结已生效 + 值班就位 + 备份确认
└─ 大促中:值班 SOP;异常按限流→降级→扩容顺序

判断口诀: 先压测定容量,再演练定预案,最后冻结降变更。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“大促=容量工程+故障工程,一周按 D-7 到 D-1 排”
0:30–2:00容量线评估、压测、预热、扩容
2:00–3:30故障线限流降级、监控、SOP、演练
3:30–4:30变更控制冻结发版、配置审批
4:30–5:00收尾“压测保证扛得住,演练保证扛不住时能活”

【关键数字】

参数经验值说明
峰值预估历史峰值 × 增长系数(1.5–3)按业务
限流阈值压测水位的 70–80%留缓冲
告警RT/错误率/队列/GC/连接池80% 线
演练至少覆盖缓存挂/DB慢/下游故障预案可用性
发版冻结大促前 3–7 天以稳为主
降级顺序非核心功能先关保交易

【追问链】(三层)

L1|“压测发现不达标怎么办?” → 顺序:① 优化瓶颈(慢SQL/缓存/参数);② 扩容(实例/DB/缓存);③ 限流保护(保核心)。不要只靠加机器掩盖架构问题。

L2|“为什么只压测不演练不够?” → 压测证明容量,演练证明“故障时人与系统能切换”。很多事故不是没容量,而是预案没人会用、降级开关失效、联系人找不到。

L3|“大促中错误率上升,先限流还是先降级?” → 若是过载:先限流挡住新流量,再降级非核心释放资源,最后扩容。若明确是某功能故障:先降级/关闭该功能。总原则是先保核心链路可用。

【评分标准】

档位答案特征
60 分能列压测、扩容、监控几项
80 分有时间线清单(预热/预案/值班/冻结);有验收标准意识
95 分容量+故障双线;强调演练与 SOP;限流阈值有依据;冻结发版的理由

【关联题】

  • 同一知识簇: 第 178 题(错误率分诊)、第 179 题(突发流量)、第 174 题(发布事故)
  • 上游: 限流、降级、熔断、缓存预热
  • 制度: 值班 SOP、故障演练

【自测】

  1. 大促前为什么必须冻结发版? 参考答案:新代码是最大不确定因素,冻结用制度清零变更风险,大促以稳为主。
  2. 限流阈值如何确定? 参考答案:压测得到安全水位后乘以安全系数(如 70–80%),网关先限应用兜底。
  3. 判断:压测通过就等于大促准备完成。 参考答案:错。还需预案演练、监控告警、值班 SOP、缓存预热、发版冻结。

174. 刚发布新版本,线上错误率飙升(发布事故处置) ​

【考察内容】发布工程与故障处置

【题目】下午 3 点发布新版本,3:05 监控显示错误率从 0.1% 飙到 20%。第一反应该做什么?回滚还是热修复?回滚怎么执行最快?如何确认回滚后恢复?事后怎么定位根因?

【参考答案】

  1. 先止血(回滚优先):立即回滚到上一个稳定版本(发布系统的快速回滚能力是关键)——比在线上“修代码”快且安全;

  2. 同时定位:看新版本变更点(代码 diff)、错误日志/调用链/监控(错误类型:异常、超时、OOM);

  3. 如果无法快速回滚(DB 结构变更等):评估灰度下线(新实例摘流量,旧实例顶流量)或功能开关关闭新逻辑;

  4. 复盘:事故原因分类——

    • 代码缺陷(空指针、并发问题、缓存键变更);
    • 依赖变更(下游接口变动、配置错误);
    • 数据问题(存量数据不兼容新逻辑);
    • 容量(新逻辑引入额外消耗);
  5. 恢复后:全量回归验证 → 修复缺陷 → 小流量灰度再发布(按本题第 7 条的 1%→5%→20%→50%→100%)→ 观察指标;

  6. 制度保障:发布系统必备——灰度发布、快速回滚、发布前后自动化检查(健康检查/冒烟测试)、变更审批与审计。

  7. 容量估算与分诊阈值: 错误率基线通常 <0.1%;发布后 5 分钟内错误率>1% 或核心接口失败率>5% → 立即回滚,不等“再观察一下”。灰度比例经验:1% → 5% → 20% → 50% → 100%,每档观察 5–15 分钟(看是否有延迟暴露)。回滚目标时间:<5 分钟完成全量回滚(依赖制品库秒级切换)。

  8. 失败与降级: 第一动作回滚,第二动作止血(限流/降级/摘流),第三动作留现场(日志、版本、diff、监控)。禁止在故障中“热改生产”。回滚后若仍异常,说明不是本次发布或有兼容数据问题——切预案。

【原理溯源】

  • 为什么“回滚优先”而不是热修? 热修需要定位+改代码+测试+再发布,时间不可控;回滚是已验证过的旧版本,分钟级恢复。故障中时间×影响面是核心代价,回滚最小化两者。
  • 为什么错误率 20% 而不是 100%? 可能是:部分实例已发布、部分代码路径才触发、特定数据才炸、灰度批次。这也提示回滚时要确认全部实例是否都回退。
  • 回滚的风险边界在哪? ① DB 结构变更(加列可回滚代码,删列不可);② 数据格式不兼容(新代码写了旧代码读不懂的数据);③ 配置/开关与代码强耦合。所以发布规范要求:DB 变更前向兼容、配置与代码解耦、可回滚设计。
  • 灰度为什么是 1%→5%→20%→50%→100%? 小流量先暴露问题,限制爆炸半径。每档要卡点观察(错误率/RT/业务指标),异常立即停。没有卡点的灰度只是慢速全量。
  • 为什么错误类型要先分类? 空指针/断言=代码逻辑;连接超时=依赖;OOM=资源;数据校验失败=不兼容存量。分类直接指向 diff 中该看哪块。

【选型判断树】

发布后错误率飙升:
├─ 能快速回滚(无破坏性 DB/数据变更)
│   → 立刻全量回滚 → 再定位根因
├─ 不能直接回滚
│   ├─ 新旧实例可并存 → 新实例摘流量,旧实例接
│   └─ 有功能开关 → 关闭新逻辑
└─ 回滚后仍错误
    → 可能配置/数据/下游问题,不是本次代码
    → 查变更清单(含配置、依赖版本)

判断口诀: 先问能不能回滚;能回就回,不能回就摘流量或关开关。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“发布事故第一原则:回滚优先止血”
0:30–1:30为什么回滚时间可控、版本已验证
1:30–2:30回滚边界DB/数据/配置兼容性
2:30–3:30不能回滚时摘流量、关开关
3:30–4:30根因与再发布diff 分类、灰度卡点
4:30–5:00制度发布系统:灰度/回滚/检查

【关键数字】

参数经验值说明
决策窗口发现后 5 分钟内应完成回滚决策时间就是影响
灰度批次1% → 5% → 20% → 50% → 100%每档卡点观察 5–15 分钟
观察指标错误率、RT、业务量、GC与基线对比
回滚确认全部实例版本一致 + 指标回落防残留
DB 原则结构变更前向兼容保证可回滚

【追问链】(三层)

L1|“回滚也有风险怎么办?” → 发布前设计可回滚:DB 只加不删、双写兼容、配置开关、数据格式版本化。不可回滚的变更必须单独审批+更严格灰度。

L2|“灰度怎么分批?” → 按实例比例、按用户、按地域。每批设置最小观察时间(如 5–15 分钟)和自动卡点(错误率超阈值停止放量)。不要无观察连续放量。

L3|“回滚后错误率没降,说明什么?” → 根因可能不在本次代码:配置被改、依赖服务故障、数据已被污染、基础设施问题。要拉完整变更清单(代码+配置+依赖+数据)对照排查。

【评分标准】

档位答案特征
60 分知道回滚
80 分回滚优先的理由;会说灰度再发布
95 分回滚边界(DB/数据);不能回滚的替代方案;发布系统能力清单;根因分类

【关联题】

  • 同一知识簇: 第 180 题(配置事故)、第 173 题(大促冻结)、第 178 题(错误率分诊)
  • 工程: 灰度发布、变更管理、冒烟测试
  • 制度: 发布审批与审计

【自测】

  1. 发布后错误率飙升,第一动作是什么? 参考答案:评估能否快速回滚,能则立刻回滚止血,再定位。
  2. 什么情况下不能简单代码回滚? 参考答案:破坏性 DB 变更、新旧数据格式不兼容、配置与代码强耦合且已下发。
  3. 灰度发布为什么要有卡点? 参考答案:限制爆炸半径。每批观察错误率/RT,异常立即停,避免慢速全量故障。

175. 应用启动从 30 秒变成 5 分钟,甚至起不来(启动问题) ​

【考察内容】应用生命周期治理

【题目】应用之前 30 秒启动完,最近变成 5 分钟,有时直接启动失败。可能的原因(依赖服务不可用、数据库连接慢、初始化任务重、配置错误、端口冲突)?怎么排查?启动顺序与依赖检查怎么设计?

【参考答案】

  1. 启动阶段拆解:进程启动 → 加载配置(配置中心拉取)→ 连接依赖(DB/Redis/MQ/注册中心)→ Bean 初始化 → 完成启动;

  2. 慢的常见原因:

    • 依赖连接超时重试:DB/Redis/MQ 不可达,启动时反复重试(配置连接超时短一点+快速失败);
    • 注册中心:拉取大量服务列表/配置(网络慢);
    • 初始化逻辑重:启动时加载大量数据/预热缓存/建连接池(建议异步化/懒加载);
    • 大 jar 加载/类扫描(组件扫描路径过大);
    • 容器环境:资源限制(CPU/内存不足)、磁盘慢;
  3. 启动失败:

    • 看启动日志第一个异常(配置错误、Bean 创建失败、端口占用、依赖连不上);
    • 常见:端口被占、数据库连接失败、配置缺失、版本不兼容;
  4. 启动顺序与依赖检查怎么设计(题干直问,不能只答「怎么变快」):分层启动=① 先做依赖探测(配置中心/DB/Redis/MQ 连通性,带短超时)→ ② 核心依赖不可达就 fail-fast,日志里明确写出是哪个依赖(不要静默重试到 5 分钟)→ ③ 非核心依赖改 @Lazy/异步初始化,失败则降级启动并记 warn(不阻塞进程)→ ④ 预热/建池/加载字典这类初始化任务挪到依赖就绪之后异步跑 → ⑤ 只有 readiness 通过才注册到注册中心、才接流量(否则半初始化的实例被上游当成可用,故障面更大)。配套手段:依赖快速失败(连接超时短)、懒加载非核心 Bean(@Lazy)、启动预热异步化、配置检查(启动自检)、健康检查(readiness 探针)、K8s startupProbe 必须大于最慢启动时间;

  5. 监控:启动耗时指标、失败告警。

  6. 容量估算: 启动健康基线:Spring Boot 单体 30s 内正常,>2 分钟需排查,>5 分钟基本异常。常见耗时:DB/Redis 连接与建表校验、Nacos/ZK 注册、健康检查聚合、懒加载被触发、证书/网络超时(默认超时 30s×N 次)。K8s 场景注意 startupProbe 必须大于最慢启动时间,否则会被 liveness 杀掉形成“启动-被杀”循环。

  7. 失败与降级: 发布中启动过慢:拉长就绪超时/放宽 probe 是缓解不是根治;紧急时回滚上一版本。将重依赖改为启动后异步预热;健康检查区分 liveness/readiness,避免未就绪就接流量。

【原理溯源】

  • 为什么启动变慢常是“依赖重试”? 连接超时设太长(如 30s×多个依赖×重试次数)会把启动时间线性放大。应:连接超时设短(1–3s)、重试有上限、非核心依赖快速失败。
  • 为什么要拆启动阶段打点? 不打点只知道“总耗时 5 分钟”,不知道卡在哪段。在配置拉取、各依赖连接、Bean 初始化前后打点,才能指向根因。
  • 懒加载为什么有效? 默认 Spring 容器启动时实例化所有 Bean。@Lazy 把非核心 Bean 推迟到首次使用,减少启动关键路径。代价是首次请求变慢,需权衡。
  • readiness 和 liveness 在启动期怎么用? 启动未完成时 readiness 失败,K8s 不把流量打进来;liveness 太灵敏会在启动慢时误杀进程。启动期应放宽 liveness 的 initialDelay。
  • 端口冲突/配置错误为什么表现为“起不来”? 启动是串行失败点:任一关键依赖失败即整体失败。看日志要找第一个 ERROR/Exception,后面往往是连锁反应。

【选型判断树】

启动慢/失败,先分"慢"还是"失败":
├─ 失败:看日志第一个异常
│   ├─ 端口占用 → 换端口/杀残留进程
│   ├─ 配置错误/缺失 → 校验配置中心
│   └─ 依赖连不上 → 网络/账号/依赖是否可用
└─ 慢:分阶段打点
    ├─ 配置拉取慢 → 配置中心/网络
    ├─ 依赖连接慢 → 超时太长/重试过多
    ├─ Bean 初始化慢 → 同步预热/全量加载 → 改异步/@Lazy
    └─ 类扫描慢 → 缩小扫描包路径

判断口诀: 失败看首异常;慢就分阶段打点。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“启动问题拆阶段,失败看首异常,慢看点位”
0:30–1:30阶段拆解配置→依赖→Bean→就绪
1:30–2:30常见慢因依赖超时重试、同步预热、类扫描
2:30–3:30失败因端口、配置、依赖、版本
3:30–4:30优化懒加载、异步预热、快速失败
4:30–5:00探针readiness/liveness 启动期配置

【关键数字】

参数经验值说明
依赖连接超时1–3s避免启动被拉长
重试次数有限次(如 3)快速失败
分阶段打点配置/DB/Redis/Bean定位慢点
@Lazy非核心 Bean推迟初始化
liveness启动期 initialDelay 放宽防误杀
目标启动时间稳定可预期监控告警

【追问链】(三层)

L1|“健康检查和就绪探针区别?” → liveness:进程是否活着,失败则重启容器;readiness:是否可接流量,失败则摘除。启动慢时 readiness 可失败,liveness 要放宽,否则会被反复重启。

L2|“预热缓存放启动里还是异步?” → 小而关键的可同步(确保接流量前就绪);大而全的异步,否则启动被拖长。更好的做法是独立预热任务或流量灰度渐进预热。

L3|“容器里启动特别慢,怎么排查?” → 看 CPU limit 是否 throttle、内存是否吃紧、镜像拉取时间、emptyDir 磁盘、节点负载。应用内打点 + 容器指标对照。

【评分标准】

档位答案特征
60 分知道看启动日志
80 分能拆阶段;说出依赖超时重试、懒加载
95 分分阶段打点方法论;readiness/liveness;容器资源限制;预热策略权衡

【关联题】

  • 同一知识簇: 第 172 题(Hang)、配置中心、K8s 探针
  • 优化: 懒加载、异步初始化
  • 预防: 启动耗时监控

【自测】

  1. 启动失败时日志应该重点看什么? 参考答案:第一个 ERROR/Exception,后面多为连锁反应。
  2. 为什么依赖连接超时设很长会拖慢启动? 参考答案:多个依赖×长超时×重试次数,时间线性叠加。应短超时、有限重试、快速失败。
  3. liveness 和 readiness 在启动慢场景如何配置? 参考答案:启动期放宽 liveness initialDelay 防误杀;readiness 在就绪前失败,避免过早接流量。

176. 排查问题时发现关键日志缺失,或日志把磁盘打满(日志问题) ​

【考察内容】日志治理与可观测性

【题目】两个问题:①线上排查问题时发现关键日志缺失(日志框架异步丢、级别配错、buffer 丢失);②某服务日志量异常把磁盘打满。分别怎么排查和治理?日志规范(分级、采样、脱敏)怎么定?

【参考答案】

  1. 日志缺失排查:

    • 日志级别(logback 动态调级别:线上 ERROR 会滤掉 INFO——确认是否级别问题);
    • 异步日志丢失(AsyncAppender 队列满丢弃——确认是否队列溢出);
    • 多实例只查了单台(日志在各自机器,需集中采集——确认是否采集链路问题);
    • 代码没打日志(无日志=没发生?还是没记录?结合监控指标佐证);
    • 日志被截断/轮转覆盖;
  2. 日志打满磁盘:见 171 题——日志级别、轮转(logrotate/Logback RollingFileAppender 按大小+保留份数)、采样(高 QPS 降采样)、集中采集到日志平台(本地少留);

  3. 最佳实践:

    • 关键路径必须打日志(入参出参、异常堆栈、耗时);
    • 日志格式统一(JSON+TraceID,便于检索与链路关联);
    • 日志分级:ERROR 必打,业务关键节点 INFO,调试用 DEBUG(线上关闭);
    • 日志平台(ELK)集中存储检索,本地只保留短期;
  4. 监控:日志采集延迟、本地磁盘告警、ERROR 量告警。

  5. 容量估算: 日志量经验:单应用 INFO 混打可达数十 GB/天;按“每请求日志字节×QPS×86400”估算。磁盘预留:日志分区只放日志,保留 ≥30% 空间;采集带宽与索引存储单独估算。采样策略:debug 不上生产常态,异常时动态打开短窗口(如 10 分钟)。

  6. 失败与降级: 日志打满磁盘:立刻调级+清理+外投;日志缺失:从网关 access、Trace、MQ、审计日志交叉重建时间线。业务降级不应依赖“日志一定在”,关键路径要有监控指标替代日志。

【原理溯源】

  • 异步日志为什么会丢? AsyncAppender 有界队列,写入速度跟不上时按策略丢弃(默认丢 TRACE/DEBUG/INFO)。高峰期关键日志可能被丢。解法:队列调大只缓解;关键日志用同步或单独 Appender;监控丢弃计数。
  • 为什么线上常只留 ERROR? 控制量与磁盘,但会丢失业务上下文。更好:关键业务节点 INFO 必打 + 高频路径采样 + 异步但监控丢弃。级别是“量与可排查性”的权衡。
  • TraceID 为什么是日志治理核心? 分布式一次请求跨多服务,没有统一 TraceID 无法串起链路。JSON 结构化 + TraceID 让日志可检索、可关联 APM,从“翻文件”变成“查链路”。
  • 为什么 ERROR 也要限流/聚合? 异常风暴(同一堆栈每秒上万条)会打满磁盘、拖慢日志写入、淹没真正的新错误。应:相同异常合并计数、限流采样、聚合告警。
  • 脱敏为什么必须做? 日志里手机号/身份证/银行卡/token 是合规红线。在日志框架层统一脱敏,而不是靠开发者自觉。

【选型判断树】

日志问题,先分缺失还是爆盘:
├─ 缺失
│   ├─ 级别滤掉 → 动态调级或补关键 INFO
│   ├─ 异步丢弃 → 查队列与丢弃计数;关键日志同步
│   ├─ 只查单机 → 上日志平台按 TraceID 查
│   └─ 轮转覆盖 → 调整保留策略/上平台
└─ 爆盘
    ├─ 无轮转 → logrotate / RollingFileAppender
    ├─ 量太大 → 降级采样、关 DEBUG、聚合异常
    └─ 本地留太久 → 集中采集,本地只留短期

判断口诀: 缺失查级别/异步/采集;爆盘查轮转/采样/集中化。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“日志问题两面:缺失影响排障,爆盘影响可用”
0:30–2:00缺失四因级别、异步丢、单机、没打
2:00–3:00爆盘治理轮转、采样、集中
3:00–4:00规范分级、JSON+TraceID、脱敏
4:00–5:00收尾“关键路径可追、量可控、敏感信息合规”

【关键数字】

参数经验值说明
轮转按天或 100–500MB保留 7–30 份
本地保留3–7 天其余上 ELK
级别线上关 DEBUG关键节点 INFO
异步队列监控丢弃次数不能默默丢
TraceID全链路必须带关联 APM
脱敏手机号/身份证/token框架层统一

【追问链】(三层)

L1|“为什么关键日志用异步可能反而找不到?” → 高峰期队列满会丢弃低级别日志;若关键信息打在 INFO 且未保护,会被丢。关键路径可同步,或用单独高优先级 Appender,并监控丢弃。

L2|“日志都打 INFO 全量行不行?” → 不行。高 QPS 下磁盘与 IO 打满,还增加应用开销。应分级+采样:错误与关键节点全量,高频成功路径采样或聚合。

L3|“如何验证日志治理有效?” → 演练制造故障,检查:能否按 TraceID 拉全链路、磁盘是否稳定、丢弃计数是否为 0、敏感字段是否已脱敏。用“故障可排查”作为验收,而不是“日志文件存在”。

【评分标准】

档位答案特征
60 分知道调级别、配轮转
80 分缺失四因分类;轮转+采样+集中
95 分异步丢弃机制;TraceID 与结构化;ERROR 聚合限流;脱敏合规;用演练验收

【关联题】

  • 同一知识簇: 第 171 题(磁盘)、可观测性(指标/日志/追踪)
  • 规范: TraceID、JSON 日志、脱敏
  • 平台: ELK/Loki

【自测】

  1. 线上日志级别是 ERROR,排查时看不到 INFO,这是丢失吗? 参考答案:不是物理丢失,是级别过滤。可动态调级或事后补关键业务 INFO。
  2. 异步日志在什么情况下会丢日志? 参考答案:AsyncAppender 有界队列写满时按策略丢弃低级别日志。需监控丢弃并保护关键日志。
  3. 日志打满磁盘的三大治理手段? 参考答案:轮转限量、采样降量、集中采集本地少留。

177. 主从延迟涨到分钟级,用户看到旧数据(主从延迟事故) ​

【考察内容】主从架构故障治理

【题目】数据库主从延迟从毫秒级涨到分钟级,读库严重滞后:用户下单后查订单是旧的、库存显示不对。什么原因会导致延迟暴涨(大事务、DDL、从库性能)?应急怎么处理?长期怎么治理?

【参考答案】

  1. 确认延迟:SHOW SLAVE STATUS(Seconds_Behind_Master)或监控平台;确认是“单从”还是“所有从”(单从=该从库问题;全从=主库压力大/大事务);

  2. 常见根因:

    • 主库大事务(大批量更新/删数据)——从库单线程重放慢(即使并行复制也受限);
    • 主库写入压力大(QPS 高);
    • 从库硬件弱/磁盘慢;
    • 网络带宽/延迟;
    • 长事务/无主键表(row 格式复制效率低);
  3. 应急:

    • 关键读强制走主库(业务路由,见主从延迟题);
    • 临时摘除延迟严重的从库流量;
    • 大事务场景:先 kill/等待大事务完成,减少后续影响;
  4. 根治:

    • 拆大事务(批量分小批);
    • 开启并行复制(slave_parallel_workers、并行类型);
    • 主库拆分/读写分离扩容(多个从库分担);
    • 半同步复制(降低丢数据风险)或 MGR 组复制;
    • 监控告警:延迟超过阈值(如 10s)告警;
  5. 业务层兜底:写后读走主库策略+缓存补偿(见 46/59 题)。

  6. 容量估算: 主从延迟可接受阈值:一般业务秒级;读己之写场景要求用户关键路径走主库或强制路由。延迟=主库写入耗时+网络+从库回放瓶颈;大事务/DDL 是分钟级延迟主因。监控:Seconds_Behind_Master、从库线程状态、回放 TPS 对比主库。经验:批量导入时延迟可冲到分钟级,应错峰。

  7. 失败与降级: 用户可感知的读(下单后查订单、支付状态)读主库或读缓存+版本号;「切回主」要限定范围:只把「写后短窗口内该用户的这笔关键读」路由回主库,不做「关键读整体切主」——本块第 1 项已判「全员读主=读写分离白做」,而题干里主库常常正是根因(写入风暴/大事务),9:1 的读写比下整体切主会把主库压力放大约 10 倍;延迟达阈值时优先动态把写后窗口拉长到当前延迟 P99、配异步补推与前端「稍后刷新」提示(宁可读到稍旧也不压垮主库),主库确实有余量时才按接口维度小比例回主并限流保护。禁止延迟期间做重分析查询打从库。极端时停从库只读业务,优先保写入与主库读。

【原理溯源】

  • 为什么大事务是延迟头号杀手? 主库一个大事务在 binlog 里可能只需写一次,但从库要完整重放(更新 100 万行=重放 100 万行)。主库“一次性付出”,从库“顺序重放”,延迟被放大。
  • 为什么“单从延迟、其他正常”指向该从库? 若所有从都延迟,根因在主库(大事务/写入风暴)或网络;只有一从延迟,查该从库磁盘/CPU/并行复制配置/大查询。
  • 并行复制为什么能缓解? 传统单线程重放 binlog。并行复制按库/表/行冲突检测让多 worker 同时重放,提高吞吐。但单表热点大事务仍难并行。
  • 半同步的代价与收益? 收益:至少一个从库确认收到 binlog 才提交,降丢数据风险。代价:提交延迟增加(多一次网络往返),极端时从库全挂会阻塞主库(取决于超时退化策略)。
  • 业务为什么要“写后读主”? 复制是异步的,存在延迟窗口。下单后立刻查订单若走从库,可能读到旧状态。写后短窗口强制读主,或用缓存/版本号做会话一致性。

【选型判断树】

主从延迟暴涨:
├─ 看 Seconds_Behind_Master + 单从还是全从
│   ├─ 全从延迟 → 主库大事务/写入风暴 → kill/拆事务
│   └─ 单从延迟 → 该从库:磁盘/并行复制/大查询
├─ 应急
│   ├─ 写后读 → **只把「写后短窗口内该用户的这笔关键读」路由回主**(关键读整体切主是被禁止的:主库常正是根因)
│   ├─ 延迟从库 → 摘流量
│   └─ 正在执行的大事务 → 评估 kill
└─ 根治
    ├─ 拆大事务、并行复制
    ├─ 扩从库、半同步
    └─ 业务:写后读主 + 延迟告警

判断口诀: 先看单从还是全从,再查大事务,应急拉长写后窗口、只回该用户这笔读(不是整体切主)。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“延迟先看单从还是全从,指向不同根因”
0:30–1:30大事务机制主库一次、从库重放放大
1:30–2:30其他根因从库硬件、网络、无主键
2:30–3:30应急写后短窗口内该用户读主(+主库限流)、摘流量、处理大事务
3:30–4:30根治拆事务、并行复制、半同步
4:30–5:00业务兜底写后读主、缓存、延迟告警

【关键数字】

参数经验值说明
健康延迟通常 <1s业务相关
告警阈值>10s 常见按业务
SHOW SLAVE STATUSSeconds_Behind_Master监控字段
并行复制slave_parallel_workers按核数配
大事务避免单事务 >1万行分批提交
半同步至少一个从确认降丢数风险

【追问链】(三层)

L1|“并行复制为什么快?” → 多 worker 并行重放不同库/表或无冲突事务,突破单线程重放瓶颈。热点单表大事务仍受限,所以还要拆事务。

L2|“能不能重启从库解决延迟?” → 不优先。重启有丢位点/主从断开风险,且根因(大事务/硬件)还在。应先处理大事务、摘流量、查从库资源。重启是最后手段且要确认 GTID/位点。

L3|“写后读主会不会把主库打爆?” → 只对“写后短窗口关键读”强制主库,不是所有读。可结合缓存写后失效、会话粘滞、版本号。全量读主会失去读写分离意义。

【评分标准】

档位答案特征
60 分知道有主从延迟,看监控
80 分大事务是主因;应急只回「写后短窗口的这笔读」并限流保护主库(不是关键读整体切主);并行复制
95 分单从 vs 全从判断;重放放大机制;半同步代价;业务写后读主策略

【关联题】

  • 同一知识簇: 主从延迟业务方案(46/59)、半同步/MGR
  • 联动: 第 183 题(误操作与 binlog)
  • 监控: Seconds_Behind_Master 告警

【自测】

  1. 所有从库都延迟,最可能的原因是? 参考答案:主库大事务或写入压力,而不是单个从库问题。
  2. 为什么大事务会造成分钟级延迟? 参考答案:从库需完整串行/有限并行重放该事务,耗时与事务大小成正比,延迟被放大。
  3. 写后读为什么要可能走主库? 参考答案:异步复制有延迟窗口,写后立刻读从库可能读到旧数据;短窗口强制读主保证会话一致性。

178. 大促期间错误率从 0.1% 涨到 30%(错误率飙升分诊) ​

【考察内容】错误率分诊是 SRE/后端高频场景题

【题目】大促进行中,核心接口错误率从 0.1% 突然涨到 30%。怎么快速分诊:先看哪类错误(超时/5xx/业务异常)、按错误码聚合、按机房/实例拆分、按参数/用户群拆分?怎么在 10 分钟内给出结论?

【参考答案】“先分类再定位”:

  1. 看错误类型分布(监控/日志聚合):

    • 超时类(连接超时/读超时):下游变慢或连接池耗尽;
    • 5xx/异常类(Exception/HTTP 500):代码异常(空指针/数据问题)或依赖返回错误;
    • 连接拒绝/资源类(Too many connections/线程池拒绝):容量打满;
    • 限流熔断类(被限流/被熔断):保护机制触发(说明上游或自身过载);
  2. 按分布缩小范围:

    • 全接口 vs 单接口(单接口=该接口逻辑/依赖问题);
    • 全实例 vs 单实例(单实例=该机器问题);
    • 全用户 vs 特定用户(特定用户=数据/权限问题);
    • 按请求参数/业务号聚合找公共入参(题干点名「按参数拆分」,这是第 4 个维度,只拆接口×实例×用户会漏):把报错请求按入参维度 group by——商品 ID/商户 ID/渠道/城市/客户端版本/是否携带某字段,若 30% 错误集中在某个 itemId 或某个参数组合,就是数据或热点问题(脏数据、热点 key、某渠道超长参数),不是容量问题;
  3. 结合发布记录:是否刚发布(回滚)?是否配置变更?

  4. 工具:错误日志(Exception 堆栈聚合)、调用链(失败 Span)、APM 错误率、GC/CPU 联动;

  5. 处置:止血(回滚/扩容/限流/降级)→ 根因修复 → 验证。

  6. 容量估算与分诊: 错误率 0.1%→30% 是数量级跳变,不是缓慢劣化,优先查:发布、配置、依赖挂了、流量攻击/爬虫、DB/Redis 故障。分诊树按影响面:全接口 vs 单接口;全实例 vs 单实例/单机房;全部用户 vs 单渠道。时间对齐:把错误开始时刻与发布/配置/扩缩容/依赖事件对齐(±2 分钟)。

  7. 失败与降级: 分诊同时降级:核心接口限流保成功率,非核心熔断;DB 故障则只读/排队;错误风暴时网关返回统一兜底页,避免重试风暴。每 5 分钟同步影响面与决策。

【原理溯源】

  • 为什么要先按错误类型分? 不同类型指向不同层:超时=依赖/容量;5xx=代码/数据;拒绝=池/线程;限流=保护已触发。10 分钟分诊的本质是用分类排除 80% 错误方向。
  • 限流导致的错误算故障吗? 算保护性响应。说明容量不足或阈值不合理。要区分“限流在保核心”还是“限流误伤核心”。前者找容量,后者调规则。
  • 为什么要按实例/机房拆? 单实例错误=该机问题(坏盘、宿主机、本地配置);单机房=机房网络/基础设施。全量=代码/依赖/容量。维度拆分是分诊最快路径。
  • 和发布时间对齐为什么重要? 变更是故障最大来源。错误开始时刻与发布/配置变更对齐,优先验证回滚是否恢复,可以跳过大量盲查。
  • 10 分钟分诊节奏: 0–2min 看类型分布;2–5min 按接口/实例/用户拆;5–8min 对变更与依赖;8–10min 给出止血动作与初步根因假设。

【选型判断树】

错误率飙升:
├─ 先看错误类型
│   ├─ 超时 → 下游/池/GC/网络
│   ├─ 5xx 异常 → 代码/数据/依赖返回错
│   ├─ 连接拒绝 → 容量打满
│   └─ 限流熔断 → 过载保护,查容量与规则
├─ 再看分布
│   ├─ 单接口 → 该接口
│   ├─ 单实例 → 该机器
│   ├─ 单用户群 → 数据/权限
│   ├─ **按参数/业务号聚合 → 公共入参(itemId/商户/渠道/版本)=脏数据或热点**
│   └─ 全局 → 发布/配置/公共依赖
└─ 对齐变更 → 有则优先回滚验证

判断口诀: 类型→分布→变更,三步缩到可行动结论。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“错误率分诊=先分类再定位,10 分钟出结论”
0:30–2:00四类错误超时/5xx/拒绝/限流各自含义
2:00–3:00四维拆分接口 × 实例/机房 × 用户 × 请求参数/业务号(题干点名的第四维)
3:00–4:00变更对齐发布/配置优先验证
4:00–5:00处置止血→根因→验证

【关键数字】

参数经验值说明
分诊目标10 分钟内初步结论先止血
观察粒度1 分钟曲线看拐点
错误聚合按异常类型/错误码TopN
变更窗口错误开始时间对齐发布时刻高相关
止血顺序回滚/限流/降级/扩容按场景

【追问链】(三层)

L1|“超时类错误首先查什么?” → 调用链看哪个 Span 超时;再查连接池、下游 RT、GC。超时是“慢”不是“错”,根因常在依赖或容量。

L2|“全实例都错,说明什么?” → 更可能是代码、配置、公共依赖、DB/缓存等全局因素,而不是单机问题。优先查变更与公共组件。

L3|“怎么防止错误率告警噪声?” → 基线+同比/环比,区分绝对阈值与突增;按接口分级;错误类型聚合;抑制已知瞬时抖动。目标是“真故障必报、噪声不吵人”。

【评分标准】

档位答案特征
60 分看日志、看监控
80 分能按错误类型、实例/接口、用户群、请求参数/业务号四个维度拆分
95 分四类错误含义;变更对齐;限流是否算故障;10 分钟节奏;止血优先

【关联题】

  • 同一知识簇: 第 174 题(发布)、第 179 题(突发流量)、第 166 题(超时)、第 173 题(大促)
  • 工具: APM、日志聚合、调用链
  • 制度: 值班分诊 SOP

【自测】

  1. 错误类型是“连接被拒绝/线程池拒绝”,首先说明什么? 参考答案:容量或资源打满(池/线程/连接数),而不是单纯代码逻辑错误。
  2. 单实例错误率高、其他正常,优先查什么? 参考答案:该实例:宿主机、磁盘、本地配置、坏节点,考虑摘除。
  3. 限流触发导致错误率上升,算不算事故? 参考答案:算保护性事件。说明过载,需评估容量与限流规则是否合理,避免误伤核心。

179. 凌晨 3 点 QPS 暴涨 10 倍,值班工程师怎么办(突发流量应急) ​

【考察内容】值班应急是综合场景题

【题目】凌晨 3 点被告警叫醒:QPS 暴涨 10 倍(疑似热点事件),错误率开始上升。值班工程师的处理顺序是什么?先保什么(限流/降级/扩容)?怎么判断要不要叫醒负责人?事后怎么复盘?

【参考答案】按“保可用→止血→定位→根治”:

  1. 确认影响:看监控(QPS、RT、错误率、GC、连接池)判断严重程度(P0/P1);确认流量来源(哪个接口/页面);

  2. 止血(按序):

    • 接入层限流(Nginx/网关限流阈值调低,保护系统);
    • 降级非核心功能(推荐/通知/积分关闭,保核心链路);
    • 热点数据本地缓存/预热;
    • 扩容(如果基础设施支持,加实例);
    • 数据库保护(限流、慢 SQL kill、连接池保护);
  3. 定位根因:是真实用户热点(正常流量,需扩容+优化)还是攻击(限流+风控);热点 Key/接口定位(监控 TopN);

  4. 根治与跟进:优化热点链路(缓存/异步)、沉淀预案(下次自动触发)、复盘记录;

  5. 值班纪律:先恢复再排查(用户可感知的故障优先止血)、按 SOP 操作、同步信息到群、事后复盘。

  6. 容量估算: QPS×10 时先判断是真实流量还是攻击/重试/爬虫。真实扩容需求:机器数≈当前×(10/单机安全系数),但 DB/Redis/下游往往先到瓶颈,扩容应用可能无效甚至放大下游压力。限流阈值建议:先限到压测得到的系统安全水位(压测容量的 70–80%,与第 173 题同口径);“昨天同时段×1.5”只能当需求侧参照,不是容量上限,再逐步试探。带宽与连接数同样要算。

  7. 失败与降级: 第一步限流+屏蔽非核心;第二步扩容有状态风险要评估;第三步对爬虫/攻击启用防护(WAF、验证码)。降级顺序:推荐/营销 → 非核心写 → 只读模式 → 排队。保下单支付登录链路。

【原理溯源】

  • 为什么止血顺序是限流→降级→扩容? 限流最快(配置级),立刻减少进入系统的压力;降级释放资源给核心链路;扩容要时间(拉起实例、预热)。先用秒级动作稳住,再用分钟级动作扩容量。
  • 为什么要区分真实热点还是攻击? 真实热点:业务成功,应扩容+优化热点链路;攻击:无业务价值,应限流+风控+封禁。处置策略完全不同,误判要么放大损失,要么赶走真实用户。
  • 为什么“先恢复再排查”? 用户可感知故障每分钟都在损失。根因分析可以事后做,止血必须立刻做。这是值班与开发思维的关键差异。
  • 热点为什么容易凌晨炸? 热点事件(热搜、事故、营销)不受作息限制;且凌晨值班人力少、部分任务(批处理、备份)可能叠加。监控必须 7×24,不能依赖人工发现。
  • 叫醒负责人的判断标准? P0(全站不可用/资金错误/数据丢失)立刻叫;影响面扩大、需要跨团队资源、超出 SOP 授权时叫。叫醒是为了资源与决策,不是推卸执行。

【选型判断树】

QPS 暴涨:
├─ 先判断影响级别
│   ├─ 核心交易受损 / 全站 → P0,立刻限流+叫负责人
│   └─ 非核心接口 → 按 SOP 降级处理
├─ 止血动作
│   ├─ 秒级:网关限流阈值下调
│   ├─ 秒级:关闭非核心功能
│   ├─ 分钟级:扩容、热点缓存
│   └─ 保护 DB:限流、kill 慢SQL
└─ 定位
    ├─ TopN 接口/热点 Key
    ├─ 来源:用户 vs 攻击 IP
    └─ 是否与定时任务/发布重叠

判断口诀: 先保核心,限流降级最快;再扩容;最后归因优化。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“值班应急:先恢复再排查”
0:30–1:30确认影响监控判断 P0/P1,看流量来源
1:30–3:00止血顺序限流→降级→扩容→护 DB
3:00–4:00归因真实热点 vs 攻击
4:00–5:00升级与复盘何时叫负责人;事后沉淀预案

【关键数字】

参数经验值说明
止血优先级限流 > 降级 > 扩容越快越好
限流网关先限,应用兜底阈值可动态调
降级清单推荐/通知/积分等非核心保交易
扩容有状态预热,无状态快有预案
升级P0 立刻叫;超出 SOP 也叫资源决策
复盘沉淀自动触发条件下次不用人肉

【追问链】(三层)

L1|“如何区分攻击和热点?” → 来源分布(IP/UA 集中=攻击)、行为特征(无登录/无转化/固定频率)、与历史流量对比、业务是否产生真实交易。可叠加风控与验证码。

L2|“限流把真实用户也挡了怎么办?” → 分层限流:网关粗限保护系统;应用按用户/接口细限;核心链路高优先级。监控被限用户比例,动态调阈值。宁可短暂影响部分用户,也不全站雪崩。

L3|“值班能不能直接改生产配置限流?” → 按授权与 SOP。预授权的动态阈值可调;重大变更需记录+事后审批。关键是可审计、可回滚,而不是完全不能动。

【评分标准】

档位答案特征
60 分知道限流或扩容
80 分有止血顺序;会区分热点与攻击
95 分先恢复再排查纪律;升级标准;降级清单思维;预案自动化沉淀

【关联题】

  • 同一知识簇: 第 173 题(大促)、第 178 题(错误率)、限流降级熔断
  • 值班: SOP、叫醒机制
  • 热点: 热点 Key、本地缓存

【自测】

  1. 凌晨 QPS 暴涨,止血第一动作通常是什么? 参考答案:接入层限流(网关阈值下调),最快减少系统压力。
  2. 为什么要区分真实热点和攻击? 参考答案:真实热点要扩容优化保业务;攻击要限流封禁止损。策略相反。
  3. 什么情况应该叫醒负责人? 参考答案:P0 全站/资金/数据事故;影响扩大;需要跨团队资源;超出值班 SOP 授权。

180. 配置中心一个开关配错,全量用户受影响(配置事故) ​

【考察内容】配置治理与事故响应

【题目】有人在配置中心把某个功能开关从 true 改成 false,配置实时推送后全量用户受影响。怎么快速止血?配置中心支持回滚吗?怎么避免这类事故(灰度发布、审批、预览、变更审计)?

【参考答案】

  1. 立即回滚配置:配置中心一键回滚到上一个正确版本(动态刷新,无需重启)——这是配置事故的第一动作;

  2. 确认影响面:受影响的实例/流量范围(是否已全量生效)、业务影响(功能不可用/数据错误);

  3. 评估数据影响:如果配置错误导致脏数据(如错误的价格/折扣)——评估是否需要数据修复(对账/补偿),先冻结相关操作;

  4. 根治:

    • 配置变更流程:灰度发布(先小范围实例/用户验证)→ 全量;
    • 配置校验:发布前自动化校验(类型/范围/正则/前后对比);
    • 配置审计:变更记录(谁、何时、改了什么)+审批;
    • 监控:配置生效后关键指标(错误率/业务量)自动比对告警;
  5. 复盘:更新配置操作 SOP、紧急回滚演练。

  6. 容量估算与流程: 配置变更爆炸半径:全量配置 vs 灰度配置;单开关影响面应能在变更单里写清楚。灰度阶梯要按变更类型分两套,别混用:① 代码发布(换制品、回滚要重切流量)→ 1% → 5% → 20% → 50% → 100%,每档观察 5–15 分钟(与第 174 题同口径),因为代码缺陷要靠时间和流量形态才暴露;② 配置/开关变更(秒级可回滚、推送后全集群同时生效)→ 先 1 个实例灰度推送 → 1% → 10% → 50% → 全量,每档 3–10 分钟即可,理由是可回滚成本极低、拖长窗口只是延长风险暴露时间;③ 高危参数(限流阈值、资金开关、风控规则)不做按用户比例放量,改走「按实例灰度 + 取值范围校验 + 双人审批」——同一用户在不同实例上拿到不同阈值本身就是故障。配置中心可用性:配置读取应有本地快照,中心挂了还能用上次配置。审计:谁改的、改了什么、何时生效、回滚版本号。

  7. 失败与降级: 一发现配错:立即回滚配置(秒级),比改代码快;必要时摘流对应功能。对“错误开关打开导致打挂下游”:同时对下游熔断。事后:配置变更强制双人复核+自动 diff+影响面检查。

【原理溯源】

  • 为什么配置事故比代码事故更快? 配置动态推送秒级全量生效,爆炸半径大且快;代码还要构建部署。所以配置必须有同等甚至更严的灰度与审批,不能因为“只是改个开关”就放松。
  • 为什么第一动作是回滚配置而不是分析? 和发布事故同理:止血优先。配置中心若有历史版本,一键回滚秒级恢复。先恢复再分析为什么改错。
  • 配置灰度怎么做? 按实例分组下发,或配置值内嵌生效条件(用户尾号、地域、白名单)。先 1% 验证指标,再放量。
  • 为什么要“前后对比”校验? 防止误把生产值改错、类型从 boolean 变 string、数值少一个数量级。发布前展示 diff + 自动规则校验。
  • 脏数据为什么要先冻结? 若错误配置已产生错误订单/价格,继续写会扩大污染。先冻结相关写入,再评估对账补偿,顺序不能反。

【选型判断树】

配置事故:
├─ 第一动作:一键回滚上一版本(动态生效)
├─ 确认影响面:实例范围、用户范围、是否有脏数据
│   ├─ 仅功能不可用 → 回滚后验证指标恢复
│   └─ 产生脏数据 → 冻结写入 + 对账/补偿
└─ 根治
    ├─ 灰度下发、发布前 diff+校验
    ├─ 审批与审计
    └─ 生效后指标自动比对告警

判断口诀: 回滚第一;有脏数据先冻结;再补灰度与校验。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“配置动态推送=快速全量,事故处置回滚优先”
0:30–1:30止血一键回滚、确认生效
1:30–2:30影响评估功能 vs 脏数据,脏数据要冻结
2:30–3:30根治灰度、校验、审计
3:30–4:30监控生效后指标比对
4:30–5:00收尾“配置与代码同级管控”

【关键数字】

参数经验值说明
回滚秒级动态生效第一动作
灰度阶梯代码发布:1%→5%→20%→50%→100%,每档 5–15 分钟;配置/开关:1 实例→1%→10%→50%→全量,每档 3–10 分钟按变更类型分两套,别混用;高危参数不做比例放量,改按实例灰度+取值范围校验+双人审批
校验类型/范围/diff发布前
审计谁/何时/改了什么必留痕
指标比对错误率、业务量自动告警

【追问链】(三层)

L1|“配置回滚后没生效怎么办?” → 确认推送是否成功、实例是否全部拉到新版本、有无本地缓存配置。必要时重启实例强拉。同时看变更是否涉及需要重启才能生效的项。

L2|“配置灰度和代码灰度一样吗?” → 目标一样(限爆炸半径),实现不同。配置可按实例分组动态生效,通常比代码灰度更灵活更快,但仍要卡点观察。「按用户规则放量」对普通配置可用;对高危参数(风控阈值、利率、限额)不能这么做——同一用户在不同实例上拿到不同阈值本身就是故障,这类只按实例灰度+取值范围校验+双人审批。

L3|“如何防止有人在配置中心直接改生产?” → 权限最小化 + 双人复核 + 高危配置模板锁定 + 紧急变更通道留痕。技术手段拦不住所有误操作,制度与审计要补位。

【评分标准】

档位答案特征
60 分知道改回去
80 分一键回滚;会说灰度
95 分配置与代码同级管控;脏数据冻结;校验+审计+指标比对;误操作制度

【关联题】

  • 同一知识簇: 第 174 题(发布事故)、配置中心架构
  • 治理: 灰度、审批、审计
  • 数据: 对账补偿

【自测】

  1. 配置中心改错,第一动作是什么? 参考答案:立即一键回滚到上一个正确版本,动态生效止血。
  2. 为什么配置变更也需要灰度? 参考答案:动态推送会秒级全量生效,爆炸半径大。灰度限制影响面并允许指标验证。
  3. 错误配置已经产生脏数据,处置顺序? 参考答案:先冻结相关写入,再评估对账/补偿,不要边污染边修。

181. 堆内存从 2G 涨到 4G,不 OOM 但 GC 频繁 RT 变差(内存上涨排查) ​

【考察内容】内存治理进阶

【题目】服务堆内存缓慢从 2G 涨到 4G(堆上限 8G),虽然没 OOM,但 GC 越来越频繁、RT 越来越差。是泄漏吗?还是缓存/集合在增长?怎么区分?怎么定位增长来源?

【参考答案】

  1. 判断性质:GC 后是否回落(回落=对象潮汐,正常但容量不足;不回落=疑似泄漏);

  2. 堆内排查:

    • jmap -histo 看对象分布(哪个类对象多);
    • 本地缓存/静态集合(Caffeine 容量配置是否合理、Map 无界);
    • ThreadLocal 堆积(线程池线程多,值不清理);
    • 大集合(一次查询加载海量数据进内存);
    • dump+MAT(持续增长对象,见泄漏题);
  3. 堆外排查(堆没涨但内存涨):

    • 直接内存(DirectByteBuffer/NIO 未释放);
    • 线程数暴涨(每线程栈内存);
    • 元空间(类加载泄漏,-XX:MaxMetaspaceSize);
    • 压缩类空间;
  4. 解决:修根因(缓存淘汰/集合分批/资源释放)→ 调参(-Xmx、GC 策略)→ 监控(堆、GC、线程数、堆外)告警;

  5. 预防:压测观察内存曲线基线、定期 dump 分析、容量规划。

  6. 容量估算: 堆 2G→4G:先看是稳定水位抬升还是缓慢爬升。Full GC 后水位稳定在 3.5G 说明存活对象真的变多(缓存/泄漏/流量上涨导致 in-flight 变多)。GC 频率经验值:Young GC 秒级可接受,Full GC 小时级以内视堆而定,分钟级必查。调优前先算分配速率:jstat -gcutil 看 YGC 次数与耗时。

  7. 失败与降级: RT 变差时先限流减负;可临时扩堆止血,但要设定观察期限(如 24h)内必须定位。关闭可疑大缓存/同步任务。若为流量上涨导致的合理内存增加,走扩容而非“修泄漏”。

【原理溯源】

  • “不 OOM 但 GC 频繁”为什么更危险? OOM 是硬失败易发现;GC 频繁是慢性性能病,RT 变差、吞吐下降,用户体感差但告警可能不响。可用性已经在受损。
  • 为什么看 GC 后回落? 同 167 题:回落=合法对象潮汐;不回落=强引用持有。这是区分“容量不足/缓存增长”与“泄漏”的最快判据。
  • 堆和 RSS 为什么要分开看? 进程 RSS 含堆、元空间、直接内存、线程栈、native。堆没涨而 RSS 涨,要查堆外。只盯 -Xmx 会漏掉 Netty 直接内存、线程暴涨。
  • 线程数为什么吃内存? 每线程默认栈约 1MB(-Xss)。1000 线程≈1GB。线程池无界或每请求一线程会把内存吃光,且不是堆内,-Xmx 管不到。
  • 缓存“合法增长”也要治理吗? 要。即使不是泄漏,无上限缓存会挤压堆、增加 GC 成本。必须设 maximumSize/expire,把增长变成可预期的稳态。

【选型判断树】

内存缓慢上涨、GC 频繁、未 OOM:
├─ 先分堆内 vs 进程 RSS
│   ├─ 堆内涨
│   │   ├─ GC 后回落 → 缓存/潮汐/容量;设上限或扩堆
│   │   └─ 不回落 → 泄漏路径(167 题双 dump)
│   └─ RSS 涨但堆稳定 → 堆外
│       ├─ NMT 看线程/直接内存/元空间
│       └─ 线程数、Netty 池、类加载
└─ 处置:修根因优先,调参其次,监控兜底

判断口诀: 先分堆内堆外,再问 GC 后回不回落。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“慢性 GC 问题:先分堆内/堆外,再分泄漏/潮汐”
0:30–1:30危害不 OOM 但 RT 变差,可用性受损
1:30–3:00堆内histo、缓存、ThreadLocal、大集合
3:00–4:00堆外直接内存、线程栈、元空间
4:00–5:00收尾“修根因+有界缓存+监控基线”

【关键数字】

参数经验值说明
jmap -histo低开销看对象计数先粗定位
线程栈默认 ~1MB/线程1000 线程≈1GB
NMTNative Memory Tracking查堆外
缓存必须 maximumSize/expire有界
告警GC 频率、堆低点抬升、线程数基线对比
大查询分批/流式避免全量进内存

【追问链】(三层)

L1|“直接内存泄漏怎么查?” → 开启 NMT;监控 DirectByteBuffer;排查 Netty/ NIO 池未 release;调 -XX:MaxDirectMemorySize 并观察。

L2|“线程数暴涨为什么吃内存?” → 每线程独立栈,默认约 1MB。线程数失控时内存被栈吃掉,且发生在堆外,调 -Xmx 无效。要限线程池与每请求线程模型。

L3|“调大堆能缓解 GC 频繁吗?” → 若是分配过快且对象合法,调大可降低频率但增加单次成本;若是泄漏或无界缓存,只是推迟问题。先定位增长源,再决定扩容堆还是改代码。

【评分标准】

档位答案特征
60 分知道看堆内存、可能要 dump
80 分分堆内/堆外;会用 histo;知道有界缓存
95 分GC 后回落判据;线程栈与 NMT;慢性 GC 的可用性危害;监控基线

【关联题】

  • 同一知识簇: 第 164 题(OOM)、第 167 题(泄漏)、第 165 题(Full GC)
  • 堆外: NMT、Netty、线程数
  • 治理: 有界缓存、分批查询

【自测】

  1. 堆没涨但进程内存涨,先查什么? 参考答案:堆外——直接内存、线程数/栈、元空间;可用 NMT。
  2. 如何区分缓存正常增长和泄漏? 参考答案:Full GC 后是否回落。回落=潮汐/容量;不回落且低点抬升=泄漏。
  3. 判断:没 OOM 就不用管内存上涨。 参考答案:错。GC 频繁已导致 RT 变差、吞吐下降,是慢性可用性损伤。

182. 大部分时间正常,每天某时段偶发超时(间歇性抖动) ​

【考察内容】间歇性故障的排查方法论

【题目】接口平时很正常,但每天固定几个时段(比如 10 点、14 点)会偶发一批超时,几分钟后自己恢复。可能是定时任务、其他业务高峰、GC、依赖方定时任务等原因。怎么用监控数据定位?

【参考答案】

  1. 先找规律:按时间(定时任务时段?整点?)、按请求特征(特定用户/特定参数)、按实例(特定机器);

  2. 常见“间歇性”根因:

    • 定时任务与大流量重叠:整点批处理任务(对账/归档/报表)与业务高峰抢资源——看定时任务执行时间;
    • GC 停顿:偶发 Full GC(对象潮汐)——GC 日志与超时时间点对齐;
    • 缓存过期潮:大量 key 同时过期导致回源 DB(雪崩)——看缓存 TTL 分布;
    • 大请求污染:单个大请求(大查询/大对象)占住线程/带宽,拖慢其他请求(长尾)——按耗时分布看 P99 与单请求;
    • JIT 编译(启动初期);
    • 外部依赖抖动:下游/网络偶发;
    • 资源竞争:磁盘 IO 突刺(备份/日志刷盘);
  3. 定位工具:调用链按时间段聚合(哪个时段慢在哪一环)、监控曲线叠加(GC/IO/定时任务时间轴对齐)、抓现场(Arthas 持续观测);

  4. 解决:错峰定时任务、TTL 随机化、大查询拆分限流、GC 调优、依赖超时重试。

  5. 容量估算: 间歇性问题定位法:把时间轴拉长 7–30 天,对齐定时任务(Job、日切、对账)、流量峰谷、缓存过期点、证书/Token 续期、批处理。抖动预算:若某时段每天固定 X 点超时,优先查同时间任务。监控粒度:1 分钟可能掩盖尖刺,关键接口上 10s/30s 桶。

  6. 失败与降级: 业务可接受时对该时段任务错峰/降速;核心接口与批处理资源隔离(独立线程池/集群)。用户侧对抖动接口加重试与超时退避,减少放大。

【原理溯源】

  • 间歇性故障为什么难? 常态监控看不到,必须“时间对齐”。把 GC、定时任务、备份、缓存过期、下游任务的曲线叠在同一时间轴,重叠即嫌疑。
  • 定时任务为什么爱在整点? cron 默认整点/整分。多个系统同时启动批处理,造成资源尖峰。解法:错峰(加随机延迟)、降优先级、资源隔离。
  • 缓存过期潮是什么? 大量 key 在同一时刻写入,TTL 相同→同时过期→瞬间回源 DB。解法:TTL 加随机抖动(如 30min±10%)。
  • 大请求污染机制? 大查询占住连接/线程/CPU/带宽,其他请求被拖慢。表现为“一批超时”而非全挂。要按请求大小/类型拆分限流。
  • 怎么证明是 GC? GC 停顿时间点与超时点毫秒级重叠,且停顿期间所有线程 STW。用 GC 日志与业务超时日志对齐。

【选型判断树】

间歇性超时:
├─ 先固定维度:时间点、请求特征、实例
├─ 时间对齐叠曲线
│   ├─ 与定时任务重叠 → 错峰/资源隔离
│   ├─ 与 GC 停顿重叠 → 调优/查分配
│   ├─ 与缓存 TTL 批量过期重叠 → TTL 随机化
│   └─ 与备份/IO 突刺重叠 → 任务调度
└─ 请求特征对齐
    ├─ 大查询/大报文 → 拆分限流
    └─ 特定用户/参数 → 数据/业务逻辑

判断口诀: 找规律 → 叠时间轴 → 对齐后治标治本。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“间歇性故障靠时间对齐,不靠瞎猜”
0:30–1:30找规律时间/请求/实例三维
1:30–3:00常见根因定时任务、GC、缓存过期潮、大请求
3:00–4:00对齐方法多曲线同时间轴
4:00–5:00治理错峰、TTL 抖动、拆分、调优

【关键数字】

参数经验值说明
时间对齐1 分钟或更细粒度看重叠
TTL 抖动±10%–20%防过期潮
定时任务避开整点,加随机延迟削峰
观察窗口至少连续数天同时间段确认规律
大查询分页/流式防污染

【追问链】(三层)

L1|“怎么证明是 GC 导致?” → GC 日志停顿时刻与超时时刻重叠(毫秒级),且 GC 期间所有业务线程 STW。曲线对齐 + 堆/GC 指标突变。

L2|“定时任务为什么不能和业务高峰重叠?” → CPU/IO/连接/锁被批处理抢占,业务 RT 拔高。应错峰、限流、资源隔离(独立线程池/从库执行)。

L3|“抓不到现场怎么办?” → 靠历史监控曲线规律复盘;下次复现前布置:Arthas 持续观测、更细日志、dump 策略。间歇性问题往往“监控比现场更有用”。

【评分标准】

档位答案特征
60 分猜定时任务或 GC
80 分会说时间对齐、TTL 随机化
95 分多曲线叠加方法论;大请求污染;错峰与资源隔离;用监控复盘

【关联题】

  • 同一知识簇: 第 165 题(GC)、第 166 题(超时)、第 171 题(IO)、缓存雪崩
  • 方法: 时间序列对齐、调用链时段聚合
  • 治理: 错峰、TTL 抖动、隔离

【自测】

  1. 每天固定时段超时,最先建立的分析习惯是? 参考答案:把 GC、定时任务、备份、缓存指标叠在同一时间轴上看重叠。
  2. 缓存过期潮如何预防? 参考答案:TTL 加随机抖动,避免同批 key 同时过期回源。
  3. 判断:间歇性故障因为难复现,只能等下次再抓现场。 参考答案:错。用历史监控曲线做时间对齐往往能定位;同时可预置观测等待复现。

183. 一条 UPDATE 忘加 WHERE,全表数据被改(数据误操作恢复) ​

【考察内容】误操作恢复是数据库运维最高频事故题

【题目】同事执行 UPDATE 忘了加 WHERE,整表数据被改(或 DELETE 全删)。怎么恢复?前提条件是什么(binlog、备份策略)?恢复步骤怎么走?怎么防止此类事故再发生(规范、审计、备份演练)?

【参考答案】

  1. 立即止血:确认影响范围(哪些表/库),必要时冻结相关写入(防止新数据污染恢复);

  2. 恢复手段(按可用性排序):

    • binlog 恢复:用 binlog2sql(解析 binlog 生成反向 SQL——把 UPDATE 转成原值恢复、DELETE 转 INSERT);前提:binlog 开启且格式为 ROW(必须平时就开启);
    • 备份恢复:最近全量备份 + binlog 增量重放到误操作前的时间点(mysqlbinlog --stop-datetime 或 --stop-position)——恢复到临时实例,核对后导回;
    • 延迟从库:配置了延迟复制(如延迟 1 小时)的从库,直接从延迟点取数据;
  3. 流程:定位误操作时间点/位点 → 用备份+binlog 恢复到临时库 → 数据校验(行数/抽样/checksum)→ 确认无误再导回线上;

  4. 预防(根本):

    • 生产账号最小权限(DML 权限受控,高危操作需审批);
    • 高危 SQL 拦截:平台审核(goInception/Yearning)强制 WHERE 条件、UPDATE/DELETE 不带 WHERE 拒绝;
    • 延迟从库(重要系统标配);
    • 备份恢复定期演练(确保备份可用);
  5. 复盘:操作流程规范(变更窗口、双人复核)、权限最小化。

  6. 容量估算(恢复窗口): 误操作恢复时间取决于备份粒度:有 binlog → 按位点/时间点恢复,小时级;只有日备 → 最多丢 1 天,需评估业务是否可接受;没有备份 → 只能从从库/缓存/下游系统拼数据,风险极高。影响行数:先 SELECT COUNT 评估爆炸半径,再决定全量回滚 vs 按条件逆操作。

  7. 失败与降级: 立即停止相关写入;评估是否切只读;通知业务与 DBA 双人操作。恢复过程禁止在生产“试 SQL”。事后强制:生产执行 SQL 走平台+审核+备份检查。

【原理溯源】

  • 为什么必须 ROW 格式 binlog? ROW 记录行变更前后镜像,才能反向生成修复 SQL。STATEMENT 只记语句,重放可能再次产生错误结果或无法精确定位行。误操作恢复对 binlog 格式有硬依赖。
  • binlog2sql 反向 SQL 原理? 解析 ROW 事件中的 before/after 镜像:UPDATE 改回 before;DELETE 变 INSERT(before);INSERT 变 DELETE。本质是用日志做时间反演。
  • 为什么先冻结写入? 恢复期间新写入会与恢复数据冲突或覆盖,扩大不一致。先停写→恢复到临时库→校验→再导回→解冻。
  • 为什么恢复到临时库而不是直接覆盖生产? 直接覆盖风险高(恢复错误、部分成功)。临时库校验行数、抽样、checksum,确认后再导回,符合“可验证再切换”。
  • 延迟从库为什么是杀手锏? 配置延迟 N 分钟复制的从库,误操作后从库尚未执行到该点,可直接取“事故前”数据。简单可靠,重要系统标配。

【选型判断树】

误操作:
├─ 立刻:确认范围 + 冻结相关写入
├─ 恢复路径
│   ├─ 有 ROW binlog → binlog2sql 反向 SQL(最快)
│   ├─ 有全量备份+binlog → 恢复到时间点/临时库
│   └─ 有延迟从库 → 直接取延迟点数据
├─ 流程:临时库 → 校验 → 导回 → 解冻
└─ 预防:最小权限、SQL 审核拦截、延迟从库、备份演练

判断口诀: 先冻结,再选恢复路径,校验后导回,最后上拦截。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“误操作:止血冻结→恢复→校验→导回”
0:30–1:30前提ROW binlog、备份、延迟从库
1:30–3:00三板斧binlog2sql、备份+时间点、延迟从库
3:00–4:00流程纪律临时库校验,不直接覆盖生产
4:00–5:00预防权限、审核拦截、演练

【关键数字】

参数经验值说明
binlog必须 ROW反向恢复前提
延迟从库如延迟 60 分钟按 RPO
恢复点--stop-datetime / position精确到事故前
校验行数+抽样+checksum确认后再导回
拦截无 WHERE 的 UPDATE/DELETE 拒绝平台强制
备份演练定期确保可用

【追问链】(三层)

L1|“没有 ROW binlog 还能恢复吗?” → 很难精确反演。只能靠更早全量备份整体回退(丢失误操作后的合法写入),或业务层补偿。所以 ROW binlog 是生产标配,不是可选项。

L2|“DELETE 误删怎么恢复?” → binlog2sql 生成 INSERT 反向语句;或闪回工具。同样依赖 ROW binlog。恢复前仍要冻结写入。

L3|“为什么备份恢复要先到临时库?” → 避免直接覆盖生产造成二次事故;临时库可校验完整性与一致性;确认无误再导回,符合变更安全流程。

【评分标准】

档位答案特征
60 分知道有备份可以恢复
80 分会说 binlog 恢复与时间点
95 分ROW 格式前提;binlog2sql 反向原理;冻结写入;临时库校验;权限与拦截预防

【关联题】

  • 同一知识簇: 第 177 题(主从/binlog)、备份恢复、SQL 审核平台
  • 预防: 最小权限、高危操作审批
  • 演练: 定期恢复演练

【自测】

  1. 误 UPDATE 后恢复最关键的平时配置是什么? 参考答案:binlog 开启且格式为 ROW;并有可用备份/延迟从库。
  2. 恢复时为什么要先冻结写入? 参考答案:防止新数据与恢复数据冲突,扩大不一致。
  3. 高危 SQL 拦截主要拦什么? 参考答案:无 WHERE 的 UPDATE/DELETE、全表 DDL 等;平台审核强制拦截。

184. 一次大事故处理完了,复盘会怎么开(故障复盘) ​

【考察内容】复盘方法论是高级工程师/负责人必考题

【题目】一次造成 30 分钟不可用的事故处理完了,你要组织复盘会并写复盘报告。复盘的目的是什么(不追责、找根因、防复发)?报告要包含哪些部分(时间线、根因、影响、改进项)?怎么确保改进项落地?

【参考答案】

  1. 复盘目标:不是追责,是找到系统性根因并改进(防止同类事故);

  2. 报告结构(标准模板):

    • 事故概况:时间线(发现→响应→恢复)、影响范围(用户/业务/时长)、严重级别(P0/P1);
    • 根因分析:直接原因 + 深层原因(5 Whys 追问:为什么没被监控发现→为什么没有告警→为什么预案缺失……);
    • 处理过程:谁、何时、做了什么(止血动作是否及时有效);
    • 改进项(Action Items):每项带负责人+截止时间——监控补全、预案编写、代码修复、流程优化;
    • 验证与跟进:改进项验收、定期演练;
  3. 复盘会要点:

    • 无责备文化(人都会犯错,问题在系统缺失);
    • 关注“为什么防线没拦住”(监控、告警、评审、灰度、回滚、演练哪层失效);
    • 区分“人为失误”与“机制缺失”——机制缺失是改进重点;
  4. 长期:事故库沉淀(同类事故检索)、SLO 复盘(可用性目标达成)、混沌演练。

  5. 容量估算(复盘材料): 复盘必须有数:影响时长、影响用户数、错误率峰值、资金/订单影响、MTTR(发现/定位/止血/恢复分段)。时间线精度到分钟。改进项应可验收:如“回滚<5 分钟”“错误率告警<2 分钟触达”“压测覆盖峰值×1.2”。

  6. 失败与降级(复盘中的技术债): 把“没有降级预案”“超时未配置”“单点依赖”列为行动项并排期。复盘文化:对事不对人,否则隐瞒会导致下次更晚发现。

【原理溯源】

  • 为什么复盘不能变成追责会? 追责会导致隐瞒、缩小描述、不敢暴露真实过程,根因永远找不到。无责备才能换来完整时间线与真话。改进对象是系统与机制,不是某个人。
  • 5 Whys 为什么有效? 单层原因停在“某人改错了”。连续问为什么,会落到“为什么能改错/为什么没拦住/为什么没监控”——即机制层。机制改进才能防复发。
  • 为什么每条改进项必须有负责人和 deadline? 没有 owner 的 action item 会流于形式。可跟踪、可验收、可追责到事项(不是人),这是复盘落地的关键。
  • 为什么要看“层层防线为何失效”? 好的系统有多层防线:评审、灰度、监控、告警、回滚、演练。事故说明多层同时失效。复盘要逐层检查,而不是只补最外层。
  • SLO 与复盘的关系? SLO 定义“多少错误预算”。超预算的事故必须复盘并改进。用错误预算驱动改进优先级,避免“每次都说重要、永远没排期”。

【选型判断树】

复盘会怎么开:
├─ 会前:收集时间线、日志、监控、聊天记录
├─ 会中
│   ├─ 先陈述事实时间线(不评价人)
│   ├─ 5 Whys 挖到机制层
│   ├─ 检查各道防线为何没拦住
│   └─ 产出 Action Items:负责人+截止时间
└─ 会后
    ├─ 跟踪验收改进项
    ├─ 事故入库便于检索
    └─ 必要时演练验证

判断口诀: 事实→机制→防线→可跟踪改进项。

【口述骨架】(5 分钟)

时间任务内容
0:00–0:30定性“复盘目标是防复发,不是追责”
0:30–1:30报告结构时间线、影响、根因、处理、改进项
1:30–2:305 Whys从直接原因挖到机制
2:30–3:30防线检查评审/灰度/监控/告警/回滚/演练
3:30–4:30落地负责人+deadline+验收
4:30–5:00文化无责备、事故库、SLO

【关键数字】

参数经验值说明
时间线精确到分钟发现→响应→恢复
5 Whys通常 3–5 层到机制不停在人
Action必须 owner + 截止时间可跟踪
跟进定期检查未完成项防烂尾
SLO错误预算驱动优先级超预算必复盘
事故库可检索同类历史组织记忆

【追问链】(三层)

L1|“5 Whys 举例?” → 订单重复:为什么重复→回调重试;为什么没幂等→设计缺幂等规范;为什么没人发现→无对账;为什么无对账→资源未排期……层层到机制与排期,而不是停在“开发写错了”。

L2|“改进项总是不落地怎么办?” → 进入迭代看板、与错误预算/SLO 挂钩、定期 review 未完成项、超期升级。没有制度压力,改进会被业务需求挤掉。

L3|“复盘报告要不要公开?” → 在安全范围内尽量公开(脱敏后),沉淀组织记忆。越公开越能避免其他团队重蹈覆辙。敏感细节可收敛,机制教训必须传播。

【评分标准】

档位答案特征
60 分知道写时间线和原因
80 分有报告模板;会说 5 Whys
95 分无责备文化;防线失效分析;Action 可跟踪验收;SLO/事故库长期机制

【关联题】

  • 同一知识簇: 第 173 题(保障)、第 174 题(发布)、第 179 题(应急)
  • 方法: 5 Whys、时间线、SLO
  • 文化: 无责备、事故库

【自测】

  1. 复盘的首要目标是什么? 参考答案:找到系统性根因并改进,防止同类事故,而不是追责个人。
  2. 改进项如何保证落地? 参考答案:每项指定负责人和截止时间,纳入看板跟踪与验收,定期 review。
  3. 5 Whys 要挖到哪一层才停? 参考答案:挖到流程/机制/设计缺失层,而不是停在“某人操作失误”。

持续学习,持续积累。