本页使用系统设计方法论中的六步框架,完整走读四个高频面试案例。
案例一:秒杀系统
1. 需求澄清
- 核心功能:限量商品在固定时间点开始售卖,用户抢购
- 高并发写入:库存扣减须保证不超卖
- 读多写少:活动开始前大量用户刷新页面,写请求集中在开始瞬间
- 非功能要求:高可用、低延迟、防作弊
2. 容量估算
| 指标 | 数值 |
|---|---|
| 峰值并发用户 | 100,000 |
| 峰值写 QPS(下单) | 10,000 |
| 峰值读 QPS(查库存/页面) | 100,000 |
| 库存数量(示例) | 1,000 件 |
- 库存数据量极小,完全可以放入 Redis 内存
- 订单写入需异步削峰,DB 不直接承受 10K QPS
3. 高层设计
用户 → CDN(静态资源)
→ Nginx 负载均衡
→ 限流层(令牌桶 / Redis 计数)
→ 订单服务(Order Service)
→ 库存服务(Inventory Service)──→ Redis(原子扣减)
──→ MQ(Kafka/RocketMQ)
→ DB(MySQL 最终落盘)4. 详细设计
库存预热
活动开始前将库存写入 Redis:
bash
SET stock:{itemId} 1000原子扣减(Lua 脚本)
使用 Lua 脚本保证"检查 + 扣减"的原子性,彻底避免超卖:
lua
local stock = tonumber(redis.call('GET', KEYS[1]))
if stock <= 0 then
return -1 -- 库存不足
end
redis.call('DECR', KEYS[1])
return stock - 1异步下单流程
- 库存扣减成功后,向 MQ 发送订单创建消息
- 订单消费者异步写入 MySQL,生成正式订单
- 用户通过轮询或 WebSocket 获取下单结果
防作弊措施
- 请求携带活动 Token,服务端校验一次性有效
- 同一用户 ID 限购一件,Redis
SETNX user:{userId}:{itemId}去重 - 前端随机延迟 + 验证码,打散流量峰值
5. 扩展与优化
- Redis 集群:库存分片,水平扩展读写能力
- MQ 削峰:Kafka 分区并行消费,订单服务无状态横向扩展
- 本地缓存:Nginx/应用层缓存活动配置,减少 Redis 压力
- 限流降级:令牌桶限流,超出流量直接返回"活动火爆,请稍后重试"
6. 权衡讨论
| 方案 | 优点 | 缺点 |
|---|---|---|
| 强一致(DB 行锁) | 绝对不超卖 | 并发极低,性能瓶颈 |
| Redis 原子扣减 + MQ | 高吞吐,不超卖 | 最终一致,需处理消息失败补偿 |
| 预扣库存 + 超卖补偿 | 吞吐最高 | 实现复杂,需业务兜底 |
面试要点:重点说明 Lua 脚本原子性如何解决超卖,以及 MQ 带来的最终一致性挑战(消息幂等、失败重试)。
案例二:即时通讯系统(IM)
1. 需求澄清
- 支持单聊、群聊(上限 500 人)
- 在线状态显示
- 消息历史记录(可查询最近 N 条)
- 离线推送通知(APNs / FCM)
- 消息送达回执(已发送、已送达、已读)
2. 容量估算
| 指标 | 数值 |
|---|---|
| DAU | 10,000,000 |
| 人均发消息数/天 | 50 条 |
| 每日消息总量 | 5 亿条 |
| 峰值 QPS(发消息) | ~6,000 |
| P99 消息延迟目标 | < 200ms |
| 消息存储(按 1 年) | ~100 TB |
3. 高层设计
客户端 ←──WebSocket──→ WebSocket 网关(Gateway)
↓
消息服务(Message Service)
↙ ↘
消息存储 推送服务
(Cassandra/HBase) (APNs / FCM)
↘
在线状态
(Redis)4. 详细设计
WebSocket 连接管理
- 每台 Gateway 节点维护本地连接表
{ userId → socket } - 同时在 Redis 记录路由信息:
HSET gateway:user {userId} {gatewayNodeId} - 消息路由时先查 Redis 找到目标 Gateway 节点,再通过内部 RPC 转发
单聊消息投递流程
发送方 ─→ Gateway A ─→ Message Service
├─ 写 Cassandra(持久化)
├─ 查 Redis 路由(接收方在哪个 Gateway)
├─ 若在线:RPC → Gateway B → 推送 WebSocket
└─ 若离线:写推送队列 → APNs/FCM群聊 Fan-out 策略
| 策略 | 适用场景 | 说明 |
|---|---|---|
| 写扩散(Fan-out on Write) | 小群(< 200 人) | 发消息时同步写入每个成员的消息队列 |
| 读扩散(Fan-out on Read) | 大群 / 超级群 | 消息只写一份,成员读取时拉取 |
实践中采用混合策略:群成员数小于阈值用写扩散,超过后切换读扩散。
消息存储(Cassandra)
表结构(按会话分区,时间排序):
partition key: conversationId
clustering key: messageId(基于时间的 Snowflake ID,降序)
columns: senderId, content, type, timestamp, status- 查询最近消息:
SELECT * FROM messages WHERE conversationId=? LIMIT 50 - 消息 ID 使用全局唯一且有序的 Snowflake ID,便于排序与去重
在线状态
- 客户端每 30 秒发送心跳,Gateway 刷新 Redis TTL:
SETEX online:{userId} 60 {gatewayId} - TTL 过期即视为离线,避免状态不一致
消息回执
- 已送达:接收方 Gateway 收到消息后回 ACK,Message Service 更新状态
- 已读:接收方打开会话后发送 Read Receipt 事件
5. 扩展与优化
- Gateway 水平扩展:无状态,WebSocket 连接通过 L4 负载均衡分发
- Message Service 分区:按 conversationId 哈希路由,保证同一会话消息有序
- Cassandra 分片:自动分片,写入吞吐随节点数线性扩展
- 推送合并:离线期间多条消息合并为一条推送,减少唤醒次数
6. 权衡讨论
| 方案 | 优点 | 缺点 |
|---|---|---|
| 写扩散 | 读取简单,延迟低 | 大群写放大严重 |
| 读扩散 | 写入压力小 | 读取需聚合,延迟高 |
| 混合策略 | 兼顾两者 | 实现复杂,阈值需调优 |
面试要点:重点讨论大群消息的 Fan-out 策略选择,以及 WebSocket 网关的连接路由机制。
案例三:Feed 流 / Timeline
1. 需求澄清
- Twitter 类时间线:关注的人发布的内容按时间倒序展示
- 支持发帖、关注、取消关注
- 时间线近实时更新
- 读写比例约 20:1(读多写少)
2. 容量估算
| 指标 | 数值 |
|---|---|
| DAU | 100,000,000 |
| 人均发帖/天 | 1 条 |
| 日发帖总量 | 1 亿条 |
| 人均读 Feed/天 | 20 次 |
| 峰值读 QPS | ~500,000 |
| 峰值写 QPS | ~25,000 |
3. 高层设计
发帖:Post Service → Fan-out Service → Timeline Cache(Redis)
→ 持久化存储(MySQL/Cassandra)
读取:Feed Service → Timeline Cache(Redis)→ 返回用户时间线
→ (缓存 miss)→ 持久化存储重建4. 详细设计
三种 Fan-out 模型
推模型(Push / Fan-out on Write)
发帖时立即将帖子 ID 写入所有粉丝的 Timeline Cache:
发帖 → Fan-out Service → 遍历粉丝列表
→ ZADD timeline:{followerId} {timestamp} {postId}
→ ZREMRANGEBYRANK timeline:{followerId} 0 -1001 (保留最新 1000 条)- 优点:读取 O(1),延迟极低
- 缺点:大 V(粉丝百万级)发帖时写放大严重
拉模型(Pull / Fan-out on Read)
读取时实时合并关注列表中每个人的最新帖子:
读取 → 获取关注列表 → 并行查询每个人的最新帖子 → 归并排序 → 返回- 优点:写入简单,无写放大
- 缺点:读取耗时高,关注人数多时延迟不可控
混合模型(Hybrid)
- 普通用户(粉丝 < 100K):推模型,发帖后 Fan-out 到粉丝 Timeline Cache
- 大 V(粉丝 ≥ 100K):拉模型,Timeline Cache 中只存普通用户帖子
- 读取时:
Timeline Cache(普通用户帖子)+实时拉取已关注大 V 的最新帖子→ 归并
Redis ZSET Timeline
bash
# 写入帖子
ZADD timeline:{userId} {timestamp} {postId}
# 读取最新 20 条
ZREVRANGEBYSCORE timeline:{userId} +inf -inf LIMIT 0 20
# 控制缓存大小,保留最新 1000 条
ZREMRANGEBYRANK timeline:{userId} 0 -1001帖子内容存储
- 帖子元数据(作者、时间、文本)存 MySQL,按 postId 查询
- 图片/视频资源存对象存储(S3),通过 CDN 加速
5. 扩展与优化
- Fan-out 异步化:大量粉丝的 Fan-out 通过 Kafka 异步处理,避免发帖接口超时
- Timeline Cache 冷启动:用户长时间未登录后 Cache 过期,首次读取触发异步重建
- 热点帖子缓存:爆款内容单独缓存,减少 DB 压力
- 地理分布:多 Region 部署,就近读取
6. 权衡讨论
| 方案 | 读延迟 | 写开销 | 适用场景 |
|---|---|---|---|
| 纯推模型 | 极低 | 高(大 V 写放大) | 粉丝数均匀的平台 |
| 纯拉模型 | 高 | 低 | 写多读少,关注数少 |
| 混合模型 | 低 | 适中 | 存在大 V 的社交平台 |
面试要点:清晰解释混合模型中大 V 的判断阈值及切换机制,以及 ZSET 裁剪策略对存储的控制。
案例四:短视频推荐系统
1. 需求澄清
- 个性化视频 Feed,无限下拉
- 冷启动(新用户/新视频)处理
- 内容安全审核
- 支持 A/B 实验
- 非功能:推荐延迟 < 200ms,结果多样性
2. 容量估算
| 指标 | 数值 |
|---|---|
| DAU | 50,000,000 |
| 人均观看视频/天 | 100 条 |
| 每日推荐请求量 | 50 亿次 |
| 峰值推荐 QPS | ~100,000 |
| 视频库规模 | 10 亿+ |
3. 高层设计
客户端
→ API Gateway
→ 推荐服务(Recommendation Service)
├─ 召回层(Recall):从海量视频中初筛候选集
├─ 排序层(Rank):ML 模型精排
└─ 重排层(Re-rank):多样性 + 业务规则
→ 视频服务(Video Service):获取视频元数据
→ CDN:视频流分发4. 详细设计
推荐三层漏斗
视频库(10亿)
↓ 召回(多路)
候选集(~10,000)
↓ 粗排(轻量模型)
候选集(~1,000)
↓ 精排(重模型)
候选集(~100)
↓ 重排(多样性 + 规则)
最终推荐列表(20 条)召回层(多路召回)
| 召回通道 | 说明 |
|---|---|
| 协同过滤 | 相似用户喜欢的视频 |
| 内容相似 | 与历史观看视频相似的内容 |
| 热门召回 | 当前热门视频兜底 |
| 关注博主 | 已关注账号的最新发布 |
| 探索召回 | 随机探索,解决信息茧房 |
排序层(ML 模型)
- 特征维度:用户特征(历史行为、画像)、视频特征(标签、时长、完播率)、上下文特征(时间、设备、网络)
- 预测目标:完播率、点赞率、分享率的加权综合分
- 模型部署:TensorFlow Serving / Triton,GPU 推理加速
重排层
- 多样性:同一作者/标签的视频不连续出现(Sliding Window 去重)
- 已看过滤:Bloom Filter 过滤近期已推送视频
- 业务规则:广告位插入、违规内容降权、新人扶持
- 时效性:优先推送 24 小时内的新视频
实时特征更新(Flink)
用户行为事件(Kafka)
→ Flink 实时计算
→ 更新实时特征(Redis):短期兴趣、近 1 小时完播率
→ 更新离线特征(Hive):长期画像,次日生效内容审核流水线
视频上传 → 转码服务
→ 审核服务(OCR + 音频识别 + 图像分类)
├─ 机器初审(秒级)
└─ 人工复审(分钟级)
→ 审核通过 → 进入推荐候选池A/B 实验
- 用户按 userId 哈希分桶,不同桶命中不同实验组
- 实验配置中心实时下发,推荐服务读取对应策略
- 指标监控:完播率、CTR、留存率,统计显著后全量
5. 扩展与优化
- 预计算推荐列表:离线预生成部分用户的推荐列表缓存到 Redis,降低在线延迟
- 模型蒸馏:将大模型知识蒸馏到轻量模型,降低推理延迟
- 召回并行化:多路召回并行请求,取并集后去重,控制总耗时
- CDN 预加载:推荐返回后,客户端预加载下一屏视频,提升体验
6. 权衡讨论
| 维度 | 选项 A | 选项 B | 取舍 |
|---|---|---|---|
| 实时 vs 批量特征 | 实时(Flink):低延迟、捕捉瞬时兴趣 | 批量(Hive):高质量、稳定 | 混合:实时特征捕捉短期兴趣,批量特征描述长期画像 |
| 参与度 vs 多样性 | 纯优化 CTR:信息茧房风险 | 强制多样性:短期指标下降 | 重排层引入多样性惩罚项,长期留存更优 |
| 质量 vs 延迟 | 重模型精排:效果好,耗时长 | 轻模型:延迟低,效果差 | 粗排用轻模型缩小候选集,精排只处理 1000 条 |
面试要点:清晰描述三层漏斗架构(召回 → 排序 → 重排),解释各层的目标与技术选型;重点讨论冷启动问题(新用户用热门召回 + 探索,新视频用内容召回 + 流量扶持)。