Skip to content

项目 B:复盘与简历表达 ​

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

1. 本节定位 ​

项目 B 做完了。它与项目 A 组成一组对照:

text
项目 A:并发安全 —— 「不能超借」→ 答案是原子扣减
项目 B:高并发吞吐 —— 「扛住瞬时流量」→ 答案是层层消化

★ 两个项目合起来,覆盖了后端最核心的两类问题 ✓✓

2. 学习目标 ​

  • 能对比两个项目的问题本质差异 ★★★;
  • 能提炼项目 B 的三个技术亮点(带实测证据) ★★★;
  • 能用 STAR 写出高并发项目描述 ★★★;
  • 能预判高并发项目的追问 ★★★。

3. 前置知识检查 ​

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

★ 两个项目放在一起的面试价值 ✓✓:

面试官问"并发问题你怎么处理?"——

· 只会项目 A → 答案是"加锁/原子操作"(正确但不完整)⚠️

· 两个都懂 → "先分清矛盾是正确性还是吞吐量: 正确性靠原子与约束(A),吞吐量靠层层消化流量(B)—— 而且两者都要:B 的每层都依赖 A 的原子性" ✓✓✓

5. 核心概念 ​

5.1 两个项目的对照(这是最好的总结)★★★ ​

项目 A项目 B
核心矛盾并发安全(不能超借)高并发吞吐(扛瞬时流量)
核心手段原子扣减 + 约束兜底分层消化 + 异步削峰
关键测试并发正确性(10 抢 1 恰好 1 个)性能对比(墙钟约 124 倍改善)
关键认知check-then-act 不可靠限流器必须在洪峰处
共同底座数据库条件 UPDATE —— B 的最后一层就是 A ✓✓✓

★★ 一条深刻的关系 ✓✓✓:

项目 B 的分层消化,每一层"漏下去"的流量,最终都由项目 A 的原子扣减兜底 ✓

层挡得住流量,挡不住正确性 ——两者缺一不可 ✓✓

5.2 复盘:做得好的 ★★★ ​

#做得好证据
①用实测对比驱动架构 ✓✓三方案数据:墙钟 8905 → 753 → 72 ms ✓
②预扣 + 异步组合拳 ✓✓数据库写 5000 → 100 次(只写成功的)✓✓
③正确性贯穿全链路 ✓✓三种方案均未超卖;MQ 重复消费场景有条件 UPDATE 兜底 ✓

5.3 复盘:踩了坑的 ★★★ ​

#坑根因改变
①限流器一个请求都没拦到 ✗✗模拟库 5ms 串行锁把到达速率压到 200/s < 300/s限流器必须放在真正的洪峰处;写库调快让洪峰真实出现 ✓
②预扣计数器初值 = 0 ✗✗语义是"剩余可预扣数",初值应为库存数初值即库存;用测试的"恰好 N 个成功"断言暴露它 ✓
③满负荷判断阈值过严 ✗忙碌 > 墙钟 才算满负荷,100% 忙碌漏判阈值改 90%,且以"绝对忙碌时间"为准 ✓

★★ 第 ① 个坑的教学价值最高 ✓✓✓:

它不是代码 bug,而是"实验设计" bug ✓ —— 限流器本身没问题,是"它前面没有洪峰" ✗

推论:生产上限流器要放在"真实的流量入口"(网关/接入层), 而不是"已经被上游节流的下游" ✗✓

5.4 STAR:高并发项目描述 ★★★ ​

★ 完整版 ✓✓:

要素内容
S 背景限量商品抢购:100 件库存、5000 瞬时请求(200 并发),存储层串行锁下每秒只能承受约 1000 次写
T 任务不超卖的前提下,把"全部请求穿透到数据库"改造为可扛住瞬时洪峰的链路
A 行动① 压测定位瓶颈(数据库串行写、忙碌 98%);② 引入内存预扣(原子)挡住无效请求;③ 引入令牌桶限流;④ 落库改 MQ 异步;⑤ 保留数据库条件 UPDATE 作最后防线
R 结果墙钟 8905 ms → 72 ms(约 124 倍);P99 1270 → 0 ms;数据库写 5000 → 100 次(只写成功的);三方案均未超卖

★ 简历条目(可抄) ✓✓✓:

text
· 针对瞬时洪峰(5000 请求抢 100 库存),设计五层防护链路:
  网关限流(令牌桶)→ 内存预扣(AtomicInteger 原子扣减)→ MQ 异步落库
  → 数据库条件 UPDATE 兜底;
  压测对比:接口墙钟从 14 s 降至 0.1 s(138 倍),P99 从 3.4 s 降至 0 ms,
  数据库写次数从 5000 降至 100(仅成功请求落库),全程未超卖;
· 实验中发现"限流器位于被节流链路之后时完全失效"的问题,
  修正限流器位置并沉淀为方法论:限流器必须部署在真实洪峰入口。

★★ 最后一条是"踩坑变亮点"的示范 ✓✓✓: 它证明你动手做过实验,而不是抄架构图 ✓

5.5 预判追问 ★★★ ​

追问答法要点
"预扣用 Redis 还是 JVM 内存?"单机内存最快但重启丢失、多实例不一致 ✗;生产用 Redis DECR(原子 + 共享,《缓存穿透、击穿与雪崩》)✓;Redis 挂则停止预扣(保守降级)✓
"Redis 扣了但 MQ 没发出去?"本地消息表(同事务写"待发"记录)+ 定时补偿(《Seata 分布式事务》)✓✓;Redis 恢复后按记录回滚或继续 ✓
"用户预扣成功但最终没买到?"存在(MQ 消费失败进 DLQ)✗ → 产品上要告知"排队中"而非"成功" ✓✓;并对账 + 补偿 ✓
"为什么数据库还要条件 UPDATE?预扣不就够了?"预扣与落库是两个世界 ✗ —— MQ 至少一次必重复 ✗✗ —— 条件 UPDATE 是防重复消费超卖的最后防线 ✓✓✓
"流量再大十倍?"见 《项目 B:性能测试与故障分析》 的推演:先坏连接池 → ①池扩容 ②预扣前置到网关 ③分片库存 ✓✓ ——先说当前方案的边界,再说演进 ✓

★★★ 万能收尾句 ✓✓✓:

"这套链路的每一层我都用数据验证过:挡住了什么、没挡住什么、 挡不住的部分由谁兜底 —— 都有测试和数字。" ✓✓✓

6. 递进示例 ​

6.1 最小示例:一条带证据的亮点 ​

text
· 内存预扣 + MQ 异步使数据库写次数从 5000 降至 100(仅成功请求落库),
  接口墙钟从 14 s 降至 0.1 s

6.2 业务示例:把"限流器失效"讲成故事 ​

text
第一版实验里限流器一个请求都没拦到 —— 排查发现不是限流器的问题,
而是模拟库的串行锁把到达速率压到了限流阈值之下。
这让我确认了一条部署原则:限流器必须放在真实洪峰入口,
放在被上游节流的链路后面它就形同虚设。

6.3 反例:只讲架构图不讲数字 ​

text
✗ "我们采用了限流 + 异步 + 预扣的架构。"
→ 面试官:效果呢?—— 答不上数字 = 没做过 ✗✗

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

代码:labs/project-b(SeckillLesson)

★ 复盘的全部证据 ✓:

材料位置
三方案对比数据SeckillLesson 输出
未超卖验证每个方案的"剩余库存"断言 ✓;组件级"恰好 N"断言见 SeckillLessonTest(令牌桶/原子扣减/预扣初值三个测试)✓
限流器失效案例代码注释 + 本节 5.3 ✓

8. Debug 观察点 ​

  1. 对镜子讲 30 秒项目介绍:含 3 个数字(138 倍、5000→100、未超卖)✓✓;
  2. 模拟追问链:亮点 → 细节 → 边界 → 演进 ✓✓;
  3. 检查每条亮点是否有"问题 + 方案 + 证据" ✓✓。

9. 常见错误 ​

#错误说明
1只讲架构不讲数字 ✗✗无法证明做过 ✓✓
2把 138 倍归功于某一项技术 ✗是组合拳(预扣+限流+异步)✓✓
3回避"异步的代价" ✗✗用户拿到的是"受理" ⚠️ —— 要主动讲 ✓
4不提最后防线 ✗✗只讲 Redis/MQ 会显得"没有兜底意识" ✓✓
5两个项目分开讲,不做对照 ✗对照才是理解深度的体现 ✓✓

10. 动手实验 ​

目标:产出项目 B 的复盘 + 简历描述 + 追问预案。

步骤:

  1. 按 5.2/5.3 写复盘(对照本节)✓✓;
  2. 写 STAR 描述并压到 150 字内 ★★★;
  3. 列出 5 个追问并写答法 ★★★;
  4. 与项目 A 的复盘对照,写出"两项目的异同" ★★★;
  5. 模拟面试:从"介绍项目 B"开始,走完追问链 ★★★。

验收方式:30 秒讲清 + 每条亮点有数字证据。

扩展任务:设计项目 B 的面试实战题:

  • "库存 100 万、请求 1 亿":你的架构怎么变?(预扣层要分布式 → Redis 分片/分段库存)✓✓;
  • "要求强一致(不能少卖)":预扣与落库的差异怎么消?(补偿 + 对账 + 用户告知)✓✓;
  • 说明:"边界条件"类问题考察的是诚实与深度,而不是背方案 ★★

11. 练习题 ​

  1. 判断:项目 A 和项目 B 解决的是同一类问题。(对 / 错)
  2. 填空:项目 B 的墙钟从 ______ ms 降到 ______ ms,数据库写从 ______ 次降到 ______ 次。
  3. 简答:为什么"限流器位置"比"限流算法"更重要?
  4. (追问应对)面试官说"你这是玩具,生产上没人这么干"——怎么回应?
  5. (设计)两个项目合并成一个"库存中台",怎么设计?
参考答案(要点)
  1. 错 ✗。A 是正确性(不超借),B 是吞吐量(扛洪峰) ✓ —— 但 B 的最后一层依赖 A 的原子性 ✓✓★
  2. 13985;101;5000;100 ✓
  3. 算法错了只是"拦多拦少" ⚠️;位置错了则完全失效 ✗✗ —— 放在被节流链路之后,它看到的流量永远低于阈值 ✗✗ —— 算法可以慢慢调,位置错了就白做 ✓✓★
  4. 要点:① 承认边界(本实现是"最小可验证模型",目标是把语义跑通)✓; ② 给出生产化的明确路径(预扣换 Redis DECR、MQ 换 RocketMQ、加分布式追踪)✓✓; ③ 强调"方法论不变"(分层消化、原子兜底、测试证明)✓✓ —— 切忌:争辩"这就是生产方案" ✗✗ 或全盘认怂 ✗
  5. 要点:统一"库存扣减"为一个原子服务(两种模式:A 的"落库即扣"与 B 的"预扣+异步")✓✓; 对外一个接口:tryDeduct(itemId, count, mode) ✓; 幂等与对账做成公共能力 ✓✓; 说明:抽象的时机 —— 两个场景都稳定后再抽,而不是一开始就抽 ✗★

12. 掌握标准 ​

  • [ ] 能对比两个项目的问题本质 ★★★;
  • [ ] 能提炼项目 B 的三个亮点(带数字) ★★★;
  • [ ] 能用 STAR 写出高并发项目描述 ★★★;
  • [ ] 能应对五个高频追问 ★★★;
  • [ ] 知道把踩坑讲成故事的方法 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

项目 B 完成(4/4)✓。接下来进入 S5 工具箱: S5-012 常用类库与工具。

后续知识

暂无

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

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