Skip to content

项目 B:性能测试与故障分析 ​

S5-010选修高级约 90 分钟JDK 21项目实战 · 项目 B:高并发业务系统

1. 本节定位 ​

《项目 B:缓存、消息与限流落地》把组件放对了位置;本节做两件事:

text
① 把 [S5-008] 的压测数据读出"故障结论"(而不只是数字)✓✓
② 用同一套方法预估"如果真实流量再大 10 倍会先坏在哪" ✓✓✓

★ 一句话:压测不是跑个数字,而是产出"故障预案" ✓✓

2. 学习目标 ​

  • 能从 P50/P99 的差距读出排队现象 ★★★;
  • 能用"瓶颈利用率"判断系统还有多少余量 ★★★;
  • 能写出一份结构化的故障分析(现象/根因/影响/修复) ★★★;
  • 能做容量估算并回答"流量 ×10 会先坏在哪" ★★★。

3. 前置知识检查 ​

  • 知道压测方法吗?(T2-003)✓
  • 知道分位延迟的含义吗?(T2-014)✓

4. 为什么需要这个知识 ​

★ 同一组数字,两种读法 ✗✓:

text
读法一(数字):P99 = 1270 ms
读法二(故障):「4900 个注定失败的用户,排队尾部等了 1 秒多才知道失败;
              期间数据库满负荷,任何额外流量都会引发雪崩」✗✗✗

★ 只有第二种读法能驱动架构决策 ✓✓

5. 核心概念 ​

5.1 从分位数读排队:P50 与 P99 的差距 ★★★ ​

实测(无保护方案) ✓:

text
  P50 = 3 ms   P99 = 1270 ms   差距超过 400 倍 ✗✗

★★ 这个差距说明什么 ✓✓:

一半请求很快、但最后的请求要等很久 ——这是排队的典型特征 ✓✓

· 如果是"均匀的慢"(如每次都有 5 ms 开销)→ P50 ≈ P99 ✗ · 如果是"排队"(先来的快、后来的等)→ P99 >> P50 ✓✓

本项目里:200 并发抢一把串行锁,后来的排队等锁 ✓

★ 对应的解法不是"优化锁",而是"减少排队的人" ✓✓: → 限流 + 预扣(让大部分请求根本不进队列)✓✓✓

5.2 瓶颈利用率:系统还有多少余量 ★★★ ​

实测 ✓✓:

方案数据库忙碌占比状态
无保护98% ✗✗满负荷,零余量 ✗✗✗
限流97%(但只写了 436 次)⚠️时间上满,量上已减 ✓
异步180%(消费者满负荷追赶)⚠️有积压,但接口已返回 ✓

★★ "忙碌 98%" 的真正含义 ✓✓:

它已经没有能力处理任何额外流量 ✗ ——

· 再来 10% 的流量 → 不是慢 10%,而是排队无限增长直到雪崩 ✗✗✗

这就是"为什么要在它前面挡流量"的定量依据 ✓✓

★ 一条容量法则 ✓✓:

生产系统的瓶颈利用率应控制在 70~80% 以下 ✓

· 留给突发流量 ✓ · 留给 GC、备份、抖动 ✓ · 100% 利用的系统等于"已经故障的系统" ✗✗

5.3 容量估算:流量 ×10 会先坏在哪 ★★★ ​

★ 用实测数据推演 ✓✓:

text
当前:100 库存 / 5000 请求 / 200 并发 → 数据库写 5000 次 × 1 ms = 5 秒纯写

推演 ×10:1000 库存 / 50000 请求 / 2000 并发
  · 数据库写 50000 次 × 1 ms = 50 秒纯写 ✗✗(串行锁下)
  · 连接池:200 并发 → 2000 并发 → **连接池爆** ✗✗✗
  · 网关/应用:内存与线程先到极限 ⚠️

★★ 先坏的是"连接池",然后才是数据库 ✓✓ —— 这就是为什么压测要全链路压,而不是只压数据库 ✓✓

★ 演进路线(按坏掉的顺序修) ✓✓:

text
① 连接池与线程池扩容(治标,撑过瞬时)✓
② 内存预扣(挡住 90%+ 无效请求)✓✓ ← 收益最大
③ MQ 异步化(写库洪峰摊平)✓✓
④ 读写分离/分库(最后才考虑)⚠️

5.4 故障分析的固定结构 ★★★ ​

★ 用"无保护方案"写一份示范 ✓✓:

项内容
现象 ★★★5000 请求下 P99 = 1270 ms;4900 个注定失败的请求排队尾部等待 1 秒+;数据库忙碌 98%
根因 ★★★所有请求(含无效请求)都穿透到数据库;数据库串行写成为唯一瓶颈;无预扣、无限流
影响 ★★用户体验(1 秒多才知失败);系统风险(满负荷,一压即溃);浪费 98% 写能力在无效请求上 ✗✗
短期修复 ★★★网关限流 + 内存预扣(当日可上线)✓
长期修复 ★★MQ 异步化 + 对账 + 容量监控告警 ✓
验证 ★★★修复后重跑同场景:P99 1270 → 509 → 0 ms ✓✓✓

★ 两个必须避免的写法 ✗✗:

错误为什么
"根因:并发量大" ✗并发量大是场景,不是根因 —— 要答"为什么大并发会打穿" ✓
"修复:优化代码" ✗太模糊 —— 要写清改哪个层、预期效果、怎么验证 ✓✓

5.5 故障演练:让故障在演练日发生 ★★★ ​

★ 本项目已演练的故障 ✓:

演练注入方式观察点
数据库变慢 ✗sleep 调大无保护时 P99 暴涨;有预扣时用户无感 ✓✓
限流失效 ✗去掉令牌桶数据库写暴涨 ✗(验证限流的价值)
MQ 不可用 ✗关闭消费者预扣成功但落库停滞 → 验证本地消息表兜底 ✓✓

★★ 一条原则 ✓✓:

故障演练的目的不是"证明系统没问题",而是"发现你以为没问题的地方" ✓✓✓

本项目就演练出了两个盲区:限流器位置错误、预扣初值错误 ✓✓

6. 递进示例 ​

6.1 最小示例:一次排队分析 ​

text
P99/P50 = 7.3 → 排队 → 谁在排队?→ 200 线程抢 1 把锁
→ 解法:让 4900 人不进队列(预扣 + 限流)✓

6.2 业务示例:容量告警阈值 ​

text
数据库忙碌率 > 70% 持续 1 分钟 → 告警
预扣队列积压 > 5000 → 告警
P99 > 1 s 持续 5 分钟 → 告警

6.3 反例:只报"成功率 100%" ​

text
✗ "压测成功率 100%,系统没问题"
→ 实际 P99 = 1270 ms、数据库满负荷 ✗✗ —— 成功率掩盖了一切 ⚠️

7. 完整可运行代码与实测 ​

代码:labs/project-b(SeckillLesson)

★ 三方案完整数据 ✓:

text
              成功  拒绝    墙钟      P50     P99   数据库写
① 无保护       100     0  8905 ms     3 ms  1270 ms   5000 ✗✗
② 限流         100  4581    753 ms     0 ms   509 ms    419 ✓
③ 异步         100  4900     72 ms     0 ms     0 ms    100 ✓✓✓

8. Debug 观察点 ​

  1. 把并发从 200 加到 500:观察 P99 恶化速度(排队是非线性的)✓✓;
  2. 统计"失败请求的延迟":它们也在消耗数据库 ✗✓;
  3. 监控消费者积压:异步方案的 P99=0 是"接口返回"的 P99,落库延迟要单独监控 ⚠️✓;
  4. 做一次"断 MQ"演练:验证对账能否发现差异 ★★★。

9. 常见错误 ​

#错误说明
1只看成功率 ✗✗成功率掩盖延迟与满负荷 ✓✓
2把场景当根因 ✗✗"并发大"不是根因 ✓
3修复方案模糊 ✗✗无法验证 ✓
4只压数据库不压全链路 ✗✗先坏的是连接池 ✓✓
5瓶颈利用率 100% 还上线 ✗✗✗一压即溃 ✓✓✓
6异步后不监控积压 ✗✗接口快了但落库堆积 ✓✓
7不做故障演练 ✗✗盲区留在生产爆发 ✓✓

10. 动手实验 ​

目标:产出一份完整的故障分析报告。

步骤:

  1. 跑三方案压测,记录全部指标 ✓✓;
  2. 算 P99/P50 比值,解释排队现象 ✓✓;
  3. 做流量 ×10 的容量推演,列出"先坏清单" ★★★;
  4. 按 5.4 的结构写一份故障分析(以无保护方案为故障)★★★;
  5. 做一次故障演练(断 MQ 或让库变慢),补全预案 ★★★。

验收方式:能回答"流量 ×10 时你的系统第一个倒下的是哪个组件、为什么"。

扩展任务:给项目 A + B 写统一容量手册:

  • 每个接口的容量(QPS)与瓶颈 ✓;
  • 各依赖(DB/缓存/MQ)的水位阈值与告警线 ✓✓;
  • 扩容动作清单(先做什么后做什么)✓✓;
  • 说明:为什么"容量手册"要在故障演练里持续修订 ★★

11. 练习题 ​

  1. 判断:P99 比 P50 高很多,说明系统整体变慢了。(对 / 错)
  2. 填空:故障分析的六个要素是 、、、、、。
  3. 简答:为什么"数据库忙碌 98%"比"P99 高"更危险?
  4. (推演)流量 ×10 时,以下哪个先坏:数据库写、应用连接池、网关限流配置?
  5. (设计)给秒杀系统定三条告警线。
参考答案(要点)
  1. 错 ✗✗。P50 还很快 → 不是整体慢,而是"排队" ✓ —— 尾部请求在等前面请求释放资源 ✓✓(解法是减少排队者,不是优化每个请求)★
  2. 现象;根因;影响;短期修复;长期修复;验证 ✓
  3. P99 高只影响尾部用户体验 ⚠️; 忙碌 98% = 零余量 ✗✗ ——任何额外流量都会让排队无限增长 → 雪崩 ✗✗✓✓ 前者是体验问题,后者是存亡问题 ✓✓★
  4. 应用连接池先坏 ✗✗ —— 并发 ×10(2000)通常超过连接池上限(如 50~100)→ 请求在拿连接时排队/超时 ✓✓; 数据库写因有预扣 + MQ 削峰,压力被摊平 ✓ —— 这也是"全链路压测"才能发现的 ✓✓★
  5. 要点:① 瓶颈利用率(数据库/消费者忙碌 > 70% 告警)✓✓; ② 预扣队列积压(> 5000 告警)✓; ③ 尾部延迟(P99 > 1 s 持续 5 分钟告警)✓; 再加一条:对账差值 > 0 告警(一致性)✓✓

12. 掌握标准 ​

  • [ ] 能从分位数差距读出排队 ★★★;
  • [ ] 能用瓶颈利用率判断余量 ★★★;
  • [ ] 能做流量 ×10 的容量推演 ★★★;
  • [ ] 能按六要素写故障分析 ★★★;
  • [ ] 知道异步方案的 P99 与落库积压要分开监控 ★★★;
  • [ ] 知道故障演练是为了发现盲区 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《项目 B:复盘与简历表达》:高并发项目的 STAR。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引