📖 本篇简介:场景设计题考的是"把需求落成表结构和方案"——给你一个业务,你要能说出表怎么建、索引怎么加、怎么扩展。本篇精选 20 道高频场景设计题,覆盖短链、秒杀、Feed 流、计数器、分布式 ID、缓存、评论、分布式锁、多租户、附近查询、对账、滑动窗口限流等。前置知识:第 9、17、18、23、26 章;本系列示例基于 MySQL 8.0。
目录
- 1 场景设计答题框架
- 题目 1:设计一个短链接系统
- 题目 2:设计一个秒杀库存系统
- 题目 3:设计一个Feed时间线
- 题目 4:设计一个关注关系表
- 题目 5:设计一个点赞计数器
- 题目 6:设计订单分库分表方案
- 题目 7:设计一个优惠券系统
- 题目 8:设计分布式ID生成
- 题目 9:设计MQ与DB双写方案
- 题目 10:设计一个热点数据缓存
- 题目 11:设计一个评论系统
- 题目 12:设计一个分布式锁
- 题目 13:设计一个多租户SaaS库
- 题目 14:设计附近的人查询
- 题目 15:设计一个对账系统
- 题目 16:设计大报表聚合查询
- 题目 17:设计定时任务调度表
- 题目 18:设计数据归档方案
- 题目 19:设计多数据源路由
- 题目 20:设计滑动窗口限流
- 速查速记
- 常见坑与注意事项
- 复习清单
- 练习题
1 场景设计答题框架
场景设计别一上来就建表,按这个框架答:需求澄清 → 表结构 → 索引 → 扩展(缓存/分库/异步)→ 一致性与兜底。
| 步骤 | 要回答的问题 |
|---|---|
| 需求 | QPS、读写比、数据量、一致性要求 |
| 表结构 | 核心表、字段、状态机 |
| 索引 | where/order by 列、联合索引 |
| 扩展 | 缓存、分库分表、异步 |
| 兜底 | 幂等、对账、降级 |
题目 1:设计一个短链接系统
考点
- 短码生成;映射存储。
参考答案
1. 映射表:(short_code, long_url, created_at, expire_at);
2. 短码唯一索引:short_code 主键/唯一;
3. 生成:发号器取 ID 转 62 进制,或哈希;
4. 跳转:查 long_url,302 重定向;
5. 访问统计:异步计数,不阻塞跳转。
-- 短链映射表,本示例基于 MySQL 8.0
CREATE TABLE t_short_link (
short_code VARCHAR(16) PRIMARY KEY, -- 短码,唯一
long_url VARCHAR(2048) NOT NULL,
expire_at DATETIME,
created_at DATETIME DEFAULT NOW()
);
追问
- 问:短码冲突?答:唯一索引 + 生成时冲突重试。
- 问:访问量大?答:长 URL 结果挡 Redis 缓存。
题目 2:设计一个秒杀库存系统
考点
- 防超卖;分层拦截。
参考答案
1. 前端限流 + 验证码挡机器人;
2. Redis 预扣:Lua 原子判断+扣减;
3. MQ 异步下单:DB 匀速消费;
4. DB 原子扣:WHERE stock>0 最终核对;
5. 对账:Redis 与 DB 库存定时校正。
追问
- 问:Redis 扣了下单失败?答:失败回补 Redis,异步补偿。
- 问:超卖兜底?答:DB 条件扣 + 对账。
题目 3:设计一个Feed时间线
考点
- 推/拉模式;收件箱。
参考答案
1. 发件箱:用户自己发的动态;
2. 收件箱:粉丝各自的时间线;
3. 推模式:发动态写进粉丝收件箱,读快;
4. 大V 走拉模式:避免写放大;
5. 存储:收件箱缓存 + 分表,正文存 MySQL/OSS。
追问
- 问:大V发动态?答:大V走拉模式,粉丝读时聚合。
- 问:已读未读?答:存上次拉取时间,不存逐条。
题目 4:设计一个关注关系表
考点
- 双向索引;唯一约束。
参考答案
1. 一行一条:(user_id, followee_id);
2. 主键 (user_id, followee_id) 防重复;
3. 反向索引 followee_id 查粉丝;
4. 好友:双向都有记录;
5. 分页:按 ID 游标。
追问
- 问:要冗余粉丝表吗?答:高频大流量可冗余,接受异步同步。
- 问:拉黑怎么存?答:单独关系类型字段。
题目 5:设计一个点赞计数器
考点
- 计数高频写;合并。
参考答案
1. 点赞明细:(obj_id, user_id) 唯一,防重复赞;
2. 计数:Redis 维护点赞数,高频写;
3. 异步回写:计数定时刷 DB 汇总表;
4. 谁赞了:明细分页查;
5. 取消赞:明细删除 + Redis 减。
-- 点赞明细表,本示例基于 MySQL 8.0
CREATE TABLE t_like (
obj_id BIGINT, user_id BIGINT, created_at DATETIME DEFAULT NOW(),
PRIMARY KEY (obj_id, user_id) -- 一个对象一人只赞一次
);
追问
- 问:计数放 DB 行不行?答:高频写热点行锁,放 Redis。
- 问:Redis 丢计数?答:定时从明细 count 重建校正。
题目 6:设计订单分库分表方案
考点
- 分片键;路由。
参考答案
1. 按 user_id 哈希分 8/16 库;
2. 单表千万级;
3. 分布式 ID:趋势递增;
4. 商家维度:路由表/ES;
5. 扩容:双写迁移。
追问
- 问:跨分片查询?答:避免,复杂检索走 ES。
- 问:分片数怎么定?答:预留 2 的幂,控制单表千万内。
题目 7:设计一个优惠券系统
考点
- 模板 + 用户券;库存。
参考答案
1. 模板表:面额、库存、有效期;
2. 用户券表:谁领了、状态;
3. 领券原子扣库存;
4. 一人一单:UNIQUE(user_id, tpl_id);
5. 过期定时处理。
追问
- 问:用券幂等?答:
WHERE status=未用,用过不能再用。 - 问:超发?答:库存条件扣 + 对账。
题目 8:设计分布式ID生成
考点
- 全局唯一;趋势递增。
参考答案
1. 号段模式:DB 一段段取,简单;
2. 雪花算法:时间戳+机器+序列,本地生成;
3. 时钟回拨:记录上次时间,回拨报错/等待;
4. 趋势递增:B+ 树顺序插入;
5. 高可用:号段库主从,雪花多机房机器位隔离。
追问
- 问:雪花机器位怎么分配?答:ZK/配置中心分配,避免冲突。
- 问:号段缺点?答:号段库是单点(主从兜底)。
题目 9:设计MQ与DB双写方案
考点
- 本地消息表。
参考答案
1. 同事务写业务表 + 消息表;
2. 后台扫消息表投递 MQ;
3. 消费幂等:按消息 ID;
4. 失败重试 + 死信;
5. 对账兜底。
追问
- 问:消息表和业务表不同库?答:用事务消息,不能本地事务。
- 问:消息顺序?答:同分片有序。
题目 10:设计一个热点数据缓存
考点
- Cache-Aside;防穿透击穿雪崩。
参考答案
1. 读:缓存 miss 查库回填;
2. 写:更库 + 删缓存;
3. 穿透:空值缓存/布隆;
4. 击穿:热点 key 互斥重建;
5. 雪崩:TTL 加随机。
追问
- 问:先更库还是先删缓存?答:先更库再删缓存。
- 问:删缓存失败?答:重试队列/binlog 订阅。
题目 11:设计一个评论系统
考点
- 按对象分片;分页。
参考答案
1. 按 obj_id 分表;
2. 评论游标分页;
3. 热门评论缓存;
4. 晒图 OSS;
5. 计数冗余:comment_count。
追问
- 问:评论数怎么统计?答:Redis 计数 + 冗余字段。
- 问:嵌套评论?答:二级回复树状/平铺。
题目 12:设计一个分布式锁
考点
- 加锁;释放;超时。
参考答案
1. Redis SET NX EX:加锁带过期;
2. 唯一 value:释放时校验 value 防误删;
3. Lua 原子释放;
4. 看门狗续期:业务长时自动续;
5. DB 锁:SELECT ... FOR UPDATE 也可但性能差。
-- Redis 分布式锁(伪代码)
SET lock:order:123 <uuid> NX EX 10 -- 加锁,10秒自动释放
-- 释放时Lua: if get==uuid then del
追问
- 问:锁超时业务没执行完?答:看门狗续期。
- 问:Redis 主从切换丢锁?答:RedLock 或接受极小概率。
题目 13:设计一个多租户SaaS库
考点
- 隔离级别。
参考答案
1. 独立库:隔离最强、成本高;
2. 共享库独立 schema:折中;
3. 共享库共享表 + tenant_id:成本低,所有查询带 tenant_id;
4. 行级隔离:tenant_id 建索引;
5. 选型:大客户独立库,小客户共享表。
追问
- 问:共享表怎么防串数据?答:所有 SQL 强制带 tenant_id。
- 问:大租户影响小租户?答:资源隔离/独立库。
题目 14:设计附近的人查询
考点
- 地理空间;Redis GEO。
参考答案
1. 实时用 Redis GEO:GEOADD/GEOSEARCH;
2. MySQL BBox 粗筛:经纬度范围 + 空间索引;
3. 精算距离:筛后 ST_Distance;
4. GeoHash 网格:查周边网格;
5. 缓存热门区域。
追问
- 问:为什么不全 MySQL?答:全表算距离慢,实时用 GEO。
- 问:网格跨边界?答:查自己+周围8格。
题目 15:设计一个对账系统
考点
- 三方核对;差异处理。
参考答案
1. 权威源:以交易/流水为准;
2. T+1 全量:夜间比对;
3. 差异清单:自动输出;
4. 自动补偿:可修的自动订正;
5. 人工工单:复杂差异。
追问
- 问:对账压在线库?答:迁从库/离线。
- 问:差异率高?答:停自动补偿,人工查根因。
题目 16:设计大报表聚合查询
考点
- 别在线库跑大聚合;预聚合。
参考答案
1. 别在 OLTP 跑大报表:拖垮在线;
2. 预聚合表:定时按天/维度汇总;
3. 数仓/ClickHouse:分析走列存;
4. 实时:流式预聚合;
5. 缓存结果:报表结果缓存。
追问
- 问:非要实时?答:预聚合 + 缓存,别现算。
- 问:预聚合表怎么建?答:按维度 group by 提前算好落表。
题目 17:设计定时任务调度表
考点
- 状态;防重;分片。
参考答案
1. 任务表:任务 ID、状态、下次执行时间;
2. 抢占执行:UPDATE ... SET status=执行中 WHERE id=? AND status=待执行;
3. 防重:状态条件抢占;
4. 失败重试;
5. 分片:多实例按 ID 取模抢不同任务。
-- 抢占一个待执行任务,本示例基于 MySQL 8.0
UPDATE t_job SET status = 1, update_time = NOW()
WHERE id = (SELECT id FROM t_job WHERE status = 0 LIMIT 1)
AND status = 0; -- 条件防多个实例抢到同一任务
追问
- 问:任务执行中宕机?答:超时扫描重置状态重新调度。
- 问:重复执行?答:状态条件抢占天然幂等。
题目 18:设计数据归档方案
考点
- 冷热分离;可恢复。
参考答案
1. 在线只留近 N 月;
2. 按时间分区:DROP 老分区;
3. 迁移校验后删;
4. 归档库可查;
5. 备份兜底。
追问
- 问:DELETE 大表?答:优先 DROP 分区/分批删。
- 问:归档后查不到?答:归档库提供只读入口。
题目 19:设计多数据源路由
考点
- 读写分离;分片路由。
参考答案
1. 主写从读:按 SQL 类型路由;
2. 分片路由:按分片键选库表;
3. 路由层:中间件或 SDK;
4. 写后读主:一致性读走主;
5. 故障切流:从库延迟大切回主。
追问
- 问:路由怎么实现?答:ShardingSphere 或自研动态数据源。
- 问:跨分片?答:业务规避,复杂检索走 ES。
题目 20:设计滑动窗口限流
考点
- 计数;窗口;Redis。
参考答案
1. 固定窗口:简单但临界突刺;
2. 滑动窗口:Redis ZSET 记录每次请求时间戳;
3. ZREMRANGEBYSCORE:清窗口外;
4. ZCARD:窗口内计数;
5. 超限拒绝。
追问
- 问:固定窗口缺点?答:窗口边界瞬间 2 倍流量。
- 问:更省内存?答:漏桶/令牌桶,或滑动日志近似。
速查速记
| 场景 | 核心方案 | 兜底 |
|---|---|---|
| 短链 | 短码映射表 | 唯一索引冲突重试 |
| 秒杀 | Redis预扣+MQ | DB条件扣+对账 |
| 计数器 | Redis+明细 | 定时重建 |
| 分布式锁 | SET NX EX+Lua | 看门狗续期 |
| 附近查询 | Redis GEO | BBox粗筛 |
| 限流 | ZSET滑动窗口 | 超限拒绝 |
| 报表 | 预聚合+数仓 | 别在线跑 |
| 对账 | T+1三方核对 | 差异工单 |
常见坑与注意事项
- 坑 1:计数器直接高频写 DB——热点行锁竞争,应放 Redis 异步回写;
- 坑 2:分布式锁不设过期——持有者宕机死锁,必须带 EX;
- 坑 3:大报表直接在线库跑聚合——拖垮 OLTP,预聚合/数仓;
- 坑 4:所有查询不带 tenant_id——多租户串数据;
- 坑 5:附近查询全表算距离——慢,用 Redis GEO/BBox;
- 注意:本题为通用场景设计,结合公开高频题型,不涉及具体公司业务。
复习清单
| 场景 | 关键 | 易错点 |
|---|---|---|
| 短链 | 短码唯一 | 跳转别阻塞 |
| 秒杀 | 分层拦截 | 别把洪峰打DB |
| 计数器 | Redis+明细 | 别高频写库 |
| 分布式锁 | NX+EX+校验value | 别误删别人锁 |
| 多租户 | tenant_id | 别漏条件 |
| 对账 | 权威源比对 | 别压在线库 |
| 归档 | 校验后删 | 别DELETE大表 |
| 限流 | 滑动窗口ZSET | 固定窗口临界突刺 |
练习题
练 1:设计一个点赞系统,要求防重复赞、能查点赞数、高并发不被热点行锁拖垮。
<details>
<summary>参考答案</summary>
点赞明细表 (obj_id, user_id) 主键唯一防重复赞;点赞计数放 Redis(INCR/DECR)抗高频写,定时从明细 count 回写 DB 汇总表;取消赞删除明细并 Redis 减计数;Redis 计数异常时用明细重建校正。
</details>
练 2:要求设计一个分布式锁,说明加锁和释放的关键细节。
<details>
<summary>参考答案</summary>
加锁:SET lock:key <唯一uuid> NX EX 10,NX 保证只有一个客户端拿到,EX 防死锁。释放:用 Lua 脚本先 GET 比对 uuid 再 DEL,避免误删别人的锁;业务执行时间可能超过锁过期时用看门狗自动续期。Redis 主从切换极端情况可能丢锁,强一致场景可用 RedLock 或接受极小概率。
</details>
下一篇预告:第 39 章 高频性能优化真题精讲
文章回复
0 条公开回复