综合练习
这一课你将能做到
- 为「课程推荐服务」产出完整架构文档:C4 图、ADR、非功能需求清单
- 用前面 11 课的知识回答评审问题:为什么选 MQ 不选 REST、为什么是 ES 不是 MySQL
- 画出一条关键链路(用户下单 → 推荐更新)的 UML 时序图
为什么要学
前 11 课是零件,这一课是装机。真实的架构工作就是给你一个模糊需求(「我们要做千人千面推荐」),你在两天内产出一份能过评审、能指导开发的架构方案。
人话:学游泳的最后一步是跳进深水区。本课不提供标准答案——架构没有标准答案,只有「能自圆其说且经得起追问」的答案。
先建立图像
本次练习的交付物全景:
architecture/
├── README.md # 架构总览(给谁看:新人和评审)
├── c4-context.png # C4 第 1 层:系统和外部世界
├── c4-container.png # C4 第 2 层:服务和技术选型
├── sequence-order.puml # 关键链路时序图
├── nfr.md # 非功能需求:可用性/延迟/成本
└── adr/
├── 001-storage-selection.md
├── 002-sync-vs-async.md
└── 003-recommendation-engine.md
跟着做
步骤 1:澄清需求(真实架构的第一步)
拿到「课程推荐服务」需求,先问五个问题并记录答案:
| 问题 | 假设答案 | 架构影响 |
|---|
| 推荐更新频率? | 用户行为后 5 分钟内更新 | 异步流处理即可,不用实时计算 |
| 数据规模? | 50 万用户 × 5000 课程 × 日均 100 万行为 | 单机 MySQL 存行为不够,需 Kafka |
| 延迟要求? | 推荐接口 P99 < 300ms | 推荐结果必须预计算 + Redis |
| 团队? | 2 个后端,无算法工程师 | 用简单协同过滤,不上深度学习 |
| 预算? | 每月云成本 < 5000 元 | 托管 Kafka + 单 ES 节点 |
步骤 2:画 C4 图
Context 图(系统在哪):
┌──────┐ ┌─────────────────────────────────┐
│ 用户 │───→│ CodeRoadMap 平台 │
└──────┘ │ ┌──────────┐ ┌─────────────┐ │
│ │ 课程服务 │←──│ 推荐服务 │ │
│ └──────────┘ │ (本次设计) │ │
│ ┌──────────┐ └──────┬──────┘ │
│ │ 订单服务 │──MQ──────┘ │
│ └──────────┘ │
└─────────────────────────────────┘
Container 图(推荐服务内部):
┌─────────────────── 推荐服务 ───────────────────┐
│ │
│ 行为采集 API ──→ Kafka ──→ 流处理任务(Flink/ │
│ (REST) 定时批量) │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ 推荐计算(协同过滤) │ │
│ │ 每日全量 + 增量更新 │ │
│ └──────────┬──────────┘ │
│ ┌────────────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ Redis MySQL ES │
│ (推荐结果缓存) (用户画像) (物品特征) │
└───────────────────────────────────────────────┘
▲
│ GET /api/v1/recommendations?userId=
API Gateway
步骤 3:写三条 ADR
# ADR-001: 推荐结果存 Redis,行为数据存 Kafka+ClickHouse
## 背景
推荐接口 P99 < 300ms,每日行为事件 100 万条。
## 决策
- 行为事件:API 采集 → Kafka(削峰)→ ClickHouse(分析)
- 推荐结果:离线计算后预写入 Redis Hash(key=userId)
- 用户画像:MySQL(更新少,查询少)
## 后果
+ 推荐接口只读 Redis,P99 < 50ms,余量充足
+ 行为数据链路全异步,采集接口无压力
- 推荐非实时(每日全量),新用户冷启动用热门榜兜底
# ADR-002: 用户行为采集走 MQ 异步,推荐查询走 REST 同步
## 背景
下单事件在订单服务,浏览事件在前端埋点,推荐服务需要消费两者。
## 决策
- 事件流入:RocketMQ topic(order_events / browse_events)
- 结果读出:REST 同步查询 Redis
## 理由
流入是「写完就忘」——异步削峰、解耦、可重放;
读出是「用户等着看」——必须同步且快。
ADR-003 算法选型(略): 协同过滤 ItemCF,理由是无需训练基础设施、可解释性强、5000 课程规模离线计算 10 分钟内完成。
步骤 4:定义非功能需求
## NFR 清单
| 属性 | 指标 | 保障手段 |
|------|------|---------|
| 可用性 | 99.9%(月停机 43 分钟) | 双副本 + 降级到热门榜 |
| 延迟 | 推荐接口 P99 < 300ms | Redis 直读 + 预计算 |
| 吞吐 | 行为采集 500 QPS 峰值 | Kafka 缓冲,采集 API 无状态扩容 |
| 数据一致 | 推荐可延迟 24h | 最终一致,监控计算任务成功率 |
| 成本 | < 5000 元/月 | 单 ES 节点 + 托管 Kafka 按量 |
| 降级策略 | Redis 挂 → 返回全站热门榜(本地缓存) |
步骤 5:画关键时序图
用户下单成功 → 推荐更新的链路:
用户 订单服务 RocketMQ 推荐流处理 Redis
│ 下单 │ │ │ │
│───────→│ │ │ │
│ │ 本地事务 │ │ │
│ │ (订单+事件表) │ │ │
│ │────────────→│ │ │
│ │ │ order_created│ │
│ │ │─────────────→│ │
│ │ │ │ 更新物品共现 │
│ │ │ │ 矩阵 │
│ │ │ │ (ClickHouse) │
│ │ │ │ │
│ [次日全量计算任务: 共现矩阵 → 每个用户的 Top-N] │
│ │ │ │───────────→│
│ │ │ │ │
│ 刷首页 │ │ │ │
│─────────────────────────────────────────────────→│
│←──────────────────── 推荐列表(<50ms) ─────────────│
讲清楚要点
- 架构方案的完整性大于精妙性:评审时「降级策略是什么」「新用户怎么办」「数据延迟多久」答不上来,比算法简单更致命。NFR 清单就是完整性检查表。
- C4 图的价值是分层沟通:给老板看 Context 层(花钱买了什么能力),给开发看 Container 层(用什么技术),不要一张图画完所有细节。
- 每条 ADR 都是一次防御:评审上的每个挑战(「为什么不用 ES 存行为数据?」)都应该有对应 ADR 可追溯。
- 降级方案必须进入设计:推荐服务挂了,首页不能白屏——返回运营配置的热门榜。核心思想:非核心功能允许有损,核心链路不允许中断。
- 冷启动是推荐系统架构问题不是算法问题:新用户没行为数据,架构解法 = 热门榜兜底 + 引导选择兴趣标签(显式反馈)。
你可能会懵的地方
-
我的方案和「标准答案」不一样怎么办?
架构没有标准答案。检查三点:约束是否都考虑到了?决策是否能追溯到理由?权衡是否说清了放弃什么?都满足就是合格方案。
-
要不要在方案里写具体代码?
架构文档不写实现代码,但关键接口契约(事件格式、API 定义)要写。代码级别的设计留给详细设计阶段。
-
怎么评估自己的方案好不好?
用第三课的 NFR 清单自查,再找同事做一次「破坏性评审」——请对方专门挑刺。被挑出 10 个问题并修复的方案,远好过没人看的方案。
-
练习里没有真实数据,怎么做容量估算?
用费米估算法:用户数 × 人均行为 × 峰值系数。50 万用户 × 20% 日活 × 10 次行为 = 日 100 万事件,峰值按均值 5 倍 = 约 60 QPS,留 10 倍余量设计到 600 QPS。估算不怕不准,怕的是不算。
错法对照
| 错法 | 问题 | 正解 |
|---|
| 拿到需求直接画框图 | 约束没澄清,方案根基不稳 | 先问五个问题并记录假设 |
| 为「将来可能实时」上 Flink 实时计算 | 团队无经验,运维重 | 每日批量够用,ADR 记录重评触发条件 |
| 推荐结果实时计算 | P99 不可控,扛不住峰值 | 离线预计算 + Redis 预写 |
| NFR 写「高可用、高性能」 | 无法验证的口号 | 量化成数字:99.9%、P99<300ms |
| 没有降级方案 | Redis 一挂首页白屏 | 本地缓存热门榜兜底 |
卡点急救
- 评审被问倒(没考虑过的问题):诚实回答「这点没覆盖,我评估后 24 小时内补充 ADR」,不要现场编造。架构师的信誉建立在严谨上。
- 两个评审人意见冲突:把冲突转化为权衡项——列出 A 方案和 B 方案的取舍矩阵,让数据说话,而不是让职位说话。
- 做完方案发现超预算:回看 NFR 找可降级项——ES 换 MySQL 全文索引(ADR-001 触发重评)、Kafka 换 RocketMQ 自建、副本数减半 + 明确风险。
面试常问
-
现场设计一个推荐系统(大题)?
按本课步骤答:1) 澄清需求(规模、实时性、团队);2) 分层讲——采集层(MQ 异步)、计算层(离线批量优先,说明何时上实时)、存储层(Redis 预计算结果)、服务层(无状态 REST + 降级);3) 主动讲权衡(为什么不用实时、冷启动怎么办);4) 主动讲降级和监控。面试官考察的不是算法深度,是工程完整性。
-
你的架构方案如何验证是可行的?
四层验证:1) 容量估算(费米估算 + 压测计划);2) 原型验证(Spike:2 天搭最小链路验证关键技术点);3) 评审(跨团队破坏性提问);4) 灰度上线(5% 流量观察指标)。
自测三问
- 如果业务要求推荐「分钟级」更新,本方案的哪几个决策要改?怎么改?
- 为什么推荐结果的读取走 Redis 直读而不是查计算引擎?
- 本方案中 RocketMQ 和 Kafka 互换,对架构有什么实质影响?
小结一句话
架构方案的评价标准不是用了多少先进技术,而是:约束覆盖完整、决策可追溯、权衡说清、降级有案——这份练习的完整交付模板可以直接复用到你下一份真实工作。
延伸