主题
界面
主题
《项目 B:缓存、消息与限流落地》把组件放对了位置;本节做两件事:
① 把 [S5-008] 的压测数据读出"故障结论"(而不只是数字)✓✓
② 用同一套方法预估"如果真实流量再大 10 倍会先坏在哪" ✓✓✓★ 一句话:压测不是跑个数字,而是产出"故障预案" ✓✓
★ 同一组数字,两种读法 ✗✓:
读法一(数字):P99 = 1270 ms
读法二(故障):「4900 个注定失败的用户,排队尾部等了 1 秒多才知道失败;
期间数据库满负荷,任何额外流量都会引发雪崩」✗✗✗★ 只有第二种读法能驱动架构决策 ✓✓
实测(无保护方案) ✓:
P50 = 3 ms P99 = 1270 ms 差距超过 400 倍 ✗✗★★ 这个差距说明什么 ✓✓:
一半请求很快、但最后的请求要等很久 ——这是排队的典型特征 ✓✓
· 如果是"均匀的慢"(如每次都有 5 ms 开销)→ P50 ≈ P99 ✗ · 如果是"排队"(先来的快、后来的等)→ P99 >> P50 ✓✓
本项目里:200 并发抢一把串行锁,后来的排队等锁 ✓
★ 对应的解法不是"优化锁",而是"减少排队的人" ✓✓: → 限流 + 预扣(让大部分请求根本不进队列)✓✓✓
实测 ✓✓:
| 方案 | 数据库忙碌占比 | 状态 |
|---|---|---|
| 无保护 | 98% ✗✗ | 满负荷,零余量 ✗✗✗ |
| 限流 | 97%(但只写了 436 次)⚠️ | 时间上满,量上已减 ✓ |
| 异步 | 180%(消费者满负荷追赶)⚠️ | 有积压,但接口已返回 ✓ |
★★ "忙碌 98%" 的真正含义 ✓✓:
它已经没有能力处理任何额外流量 ✗ ——
· 再来 10% 的流量 → 不是慢 10%,而是排队无限增长直到雪崩 ✗✗✗
这就是"为什么要在它前面挡流量"的定量依据 ✓✓
★ 一条容量法则 ✓✓:
生产系统的瓶颈利用率应控制在 70~80% 以下 ✓
· 留给突发流量 ✓ · 留给 GC、备份、抖动 ✓ · 100% 利用的系统等于"已经故障的系统" ✗✗
★ 用实测数据推演 ✓✓:
当前:100 库存 / 5000 请求 / 200 并发 → 数据库写 5000 次 × 1 ms = 5 秒纯写
推演 ×10:1000 库存 / 50000 请求 / 2000 并发
· 数据库写 50000 次 × 1 ms = 50 秒纯写 ✗✗(串行锁下)
· 连接池:200 并发 → 2000 并发 → **连接池爆** ✗✗✗
· 网关/应用:内存与线程先到极限 ⚠️★★ 先坏的是"连接池",然后才是数据库 ✓✓ —— 这就是为什么压测要全链路压,而不是只压数据库 ✓✓
★ 演进路线(按坏掉的顺序修) ✓✓:
① 连接池与线程池扩容(治标,撑过瞬时)✓
② 内存预扣(挡住 90%+ 无效请求)✓✓ ← 收益最大
③ MQ 异步化(写库洪峰摊平)✓✓
④ 读写分离/分库(最后才考虑)⚠️★ 用"无保护方案"写一份示范 ✓✓:
| 项 | 内容 |
|---|---|
| 现象 ★★★ | 5000 请求下 P99 = 1270 ms;4900 个注定失败的请求排队尾部等待 1 秒+;数据库忙碌 98% |
| 根因 ★★★ | 所有请求(含无效请求)都穿透到数据库;数据库串行写成为唯一瓶颈;无预扣、无限流 |
| 影响 ★★ | 用户体验(1 秒多才知失败);系统风险(满负荷,一压即溃);浪费 98% 写能力在无效请求上 ✗✗ |
| 短期修复 ★★★ | 网关限流 + 内存预扣(当日可上线)✓ |
| 长期修复 ★★ | MQ 异步化 + 对账 + 容量监控告警 ✓ |
| 验证 ★★★ | 修复后重跑同场景:P99 1270 → 509 → 0 ms ✓✓✓ |
★ 两个必须避免的写法 ✗✗:
| 错误 | 为什么 |
|---|---|
| "根因:并发量大" ✗ | 并发量大是场景,不是根因 —— 要答"为什么大并发会打穿" ✓ |
| "修复:优化代码" ✗ | 太模糊 —— 要写清改哪个层、预期效果、怎么验证 ✓✓ |
★ 本项目已演练的故障 ✓:
| 演练 | 注入方式 | 观察点 |
|---|---|---|
| 数据库变慢 ✗ | sleep 调大 | 无保护时 P99 暴涨;有预扣时用户无感 ✓✓ |
| 限流失效 ✗ | 去掉令牌桶 | 数据库写暴涨 ✗(验证限流的价值) |
| MQ 不可用 ✗ | 关闭消费者 | 预扣成功但落库停滞 → 验证本地消息表兜底 ✓✓ |
★★ 一条原则 ✓✓:
故障演练的目的不是"证明系统没问题",而是"发现你以为没问题的地方" ✓✓✓
本项目就演练出了两个盲区:限流器位置错误、预扣初值错误 ✓✓
P99/P50 = 7.3 → 排队 → 谁在排队?→ 200 线程抢 1 把锁
→ 解法:让 4900 人不进队列(预扣 + 限流)✓数据库忙碌率 > 70% 持续 1 分钟 → 告警
预扣队列积压 > 5000 → 告警
P99 > 1 s 持续 5 分钟 → 告警✗ "压测成功率 100%,系统没问题"
→ 实际 P99 = 1270 ms、数据库满负荷 ✗✗ —— 成功率掩盖了一切 ⚠️代码:labs/project-b(SeckillLesson)
★ 三方案完整数据 ✓:
成功 拒绝 墙钟 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 ✓✓✓| # | 错误 | 说明 |
|---|---|---|
| 1 | 只看成功率 ✗✗ | 成功率掩盖延迟与满负荷 ✓✓ |
| 2 | 把场景当根因 ✗✗ | "并发大"不是根因 ✓ |
| 3 | 修复方案模糊 ✗✗ | 无法验证 ✓ |
| 4 | 只压数据库不压全链路 ✗✗ | 先坏的是连接池 ✓✓ |
| 5 | 瓶颈利用率 100% 还上线 ✗✗✗ | 一压即溃 ✓✓✓ |
| 6 | 异步后不监控积压 ✗✗ | 接口快了但落库堆积 ✓✓ |
| 7 | 不做故障演练 ✗✗ | 盲区留在生产爆发 ✓✓ |
目标:产出一份完整的故障分析报告。
步骤:
验收方式:能回答"流量 ×10 时你的系统第一个倒下的是哪个组件、为什么"。
扩展任务:给项目 A + B 写统一容量手册:
《项目 B:复盘与简历表达》:高并发项目的 STAR。