第 36 章 场景设计题的答题框架
把前三十五章的散点知识组装成可复用的作答流程:先澄清、再估算、后设计,最后主动暴露瓶颈与取舍。
学习目标
读完本章,你应当能够:
- 说出设计题的七个评分维度,并判断自己上一轮回答在哪个维度丢分;
- 按六步框架完成一道设计题,并给出每步的时间分配与话术;
- 独立推导
QPS、存储量、带宽、连接数,说明数量级算错为什么比不估算更糟; - 画出秒杀六层防线图,推导超卖与少卖各自的根因与补救,并在短链、排行榜、Feed 流、在线人数四类题上给出被牺牲的代价。
前置知识检查
| 你需要知道 | 在哪一节补齐 |
|---|---|
| 吞吐、延迟与 Little 定律、缓存三件套与缓存一致性 | 第 3 章性能工程、第 27 章 Redis 原理与缓存架构 |
| 削峰、消费幂等、延迟消息、分布式 ID 六种方案与时钟回拨 | 第 33 章 MQ、第 34 章分布式理论 |
| 熔断三态、限流算法、降级分层、接口幂等落地 | 第 34、35 章 |
36.1 面试官在评什么:七个评分维度
| 评分维度 | 面试官在观察什么 | 低分信号 | 加分信号 |
|---|---|---|---|
| 需求澄清 | 是否主动缩小问题空间 | 直接假设日活一亿 | 问出读多写多、峰值倍数、保留期 |
| 容量估算 | 能否把需求换算成数量级 | 回避估算或算错量级 | 给出 QPS、存储、带宽、连接数 |
| 存储与数据结构选型 | 选型是否有依据 | “用 MySQL 存就行” | 说明为什么不是另一种,给出分片键 |
| 核心流程完整性 | 是否覆盖主链路全环 | 只画到"写数据库" | 主链路 + 异步链路 + 回调链路都画 |
| 瓶颈识别与扩展 | 能否指出先崩的一环 | “加机器就能扩展” | 指出单点、热点、有状态节点 |
| 一致性与可用性取舍 | 是否意识到要放弃某一边 | 声称"既强一致又高可用" | 明确"这里最终一致,代价是……" |
| 沟通与推进节奏 | 是否可控地推进 | 沉默三分钟画图 | 每完成一步确认一次方向 |
重要
前四个维度决定及格线,后三个决定"合格"还是"优秀"。而需求澄清是整场回答里性价比最高的一步:花 3 分钟问清约束,可以让后面 30 分钟的方向不至于全错。
警告
最常见的扣分动作是"一上来就写代码或画架构图",它同时暴露不澄清需求、不做估算、不区分主次。典型错误回答:“这个系统我会用 Redis 做缓存、Kafka 做削峰、MySQL 做持久化,然后水平扩展。“这句话没有规模、没有取舍、没有流程;面试官追问"削峰削多少、Kafka 几个分区、消费失败怎么办”,就只能现场编。
36.2 六步答题框架与时间分配
| 步骤 | 内容 | 时间 | 配套话术 |
|---|---|---|---|
| ① 澄清需求 | 六个必问 | 3~5 min | “在设计之前,我想先确认几个约束……” |
| ② 容量估算 | QPS/存储/带宽/连接数 | 4~6 min | “按这个量级,架构等级大概落在……” |
| ③ 接口设计 | 核心接口 + 幂等键 | 4~6 min | “对外只有三个接口,其余都是内部接口” |
| ④ 存储选型 | 表结构、分片、索引 | 8~10 min | “选它是为了 X,代价是 Y” |
| ⑤ 核心流程 | 正常 + 异常,画时序 | 12~15 min | “正常流程走完,我们再看异常分支” |
| ⑥ 瓶颈与扩展 | 先崩点、扩展手段、监控 | 6~8 min | “这个设计第一个崩的地方是……” |
若面试官只给 25 分钟,按比例压缩第 ③④ 步,但第 ①② 步不能省。
36.2.1 第一步:澄清需求,六个必问
要问会改变架构的约束:读写比(100:1 与 1:1 是两套架构)、峰值倍数(均值定容量,峰值定弹性与限流阈值)、强一致还是最终一致(决定能否异步)、可接受延迟(200 ms 与 30 s 是同步与异步的分界线)、数据规模与保留期(决定分片与冷热分层)、是否多端多地域(决定单元化与就近接入)。
提示
澄清完用一句话回述并锁定:“我理解是读多写少、峰值约为均值 4 倍、允许 1 秒内最终一致、数据保留 1 年——如果这个前提不对,后面的取舍得改。“这一句能让面试官从评判者变成合作者。
36.2.2 第三步:接口设计与幂等性
接口只讲核心几个,重点是每个接口的幂等键是什么。设计题的接口清单通常不超过四个:
| |
写接口用 requestId 去重,回调接口用外部单号去重;两者都不能靠"先查再插”,具体落地见第 35 章。
36.3 容量估算:公式、算例与推导
36.3.1 四个量的算法
| |
重要
估算的目的是决定架构等级,不是算准。 算出 QPS 是 8000 还是 12000 无关紧要——两者都指向"单机 MySQL 扛不住写,需要缓存 + 分片”。但把日活 1000 万的 QPS 说成 20 万(量级错 100 倍),面试官会立刻判定你没有容量意识。数量级算错比完全不估算更糟:不估算只是没加分,算错是负分。
36.3.2 完整算例
题目:日活 1000 万、人均 20 次请求、每条记录 1 KB、日增 2000 万条。
| |
注意
面试官追问的下一层通常是:"W 变大了怎么办?“这正是过载保护存在的理由。W 随排队增长而增长,L 也随之增长,形成正反馈;系统必须在 L 超过阈值时主动拒绝(限流、熔断)把 W 压回去。所以限流阈值应按"下游能承受的并发"反推,而不是按机器 CPU 反推。
36.4 综合案例:秒杀系统的六层防线
秒杀是全书知识的汇合点:它同时用到限流(第 34 章)、Redis 原子操作(第 27 章)、MQ 削峰(第 33 章)、数据库扣减与唯一索引(第 23、24 章)、幂等(第 35 章)。
| |
36.4.1 为什么不能用"先查后减”
| |
| |
小心
用 DECR 再判断返回值(返回 < 0 就 INCR 回补)功能上也能防超卖,但必须主动说出代价:回补本身是一次新写入,若回补失败(进程崩溃、网络分区)就会少卖,且负数区间会让前端读到负库存。Lua 脚本不存在回补窗口。能说出这一层对比才是加分项。
36.4.2 超卖与少卖的完整分析
| |
对账与补偿是面试官最容易追问到底的一层。三条守恒式:Redis 余量 + 已占用未支付 + 已支付 = 总库存,不等时差值为负要先停止新扣减再告警、差值为正才按最小粒度补回;Redis 占位用户数 = PENDING 订单数 + SUCCESS 订单数,缺订单触发补单、多订单按唯一索引去重;扫超时未支付订单(例如 now − create_time > 15 min)发回补消息并置为 CLOSED。
警告
对账任务自身的每个修复动作都必须幂等(例如回补前先写一条补偿流水并按流水号去重),否则任务重跑一次就会把库存补多——对账系统成为新的超卖来源,这是很常见的真实事故形态。
36.5 短链接系统:小题目里的大考点
需求澄清决定方案,必问三项:是否需要自定义后缀(vanity url)、是否需要过期时间、是否需要访问统计。第三项直接决定 301 还是 302。
| 发码方案 | 短码来源 | 优点 | 代价 |
|---|---|---|---|
| 数据库自增 | id 转 62 进制 | 绝对唯一,实现最简单 | 每次生成一次写库,单点,扩展差 |
| 号段模式 | 批量取 id 区间在内存分配 | 大幅降低数据库压力 | 重启浪费一段号;多实例要求号段不重叠 |
Redis INCR | 计数器自增 | 性能好,天然唯一 | Redis 故障影响发号,需持久化 |
Snowflake | 时间 + 机器 + 序列转 62 进制 | 无中心依赖,趋势递增 | 依赖时钟,必须处理回拨 |
MurmurHash | 长链哈希取前 N 位 | 无需发号器;同链得同码(幂等) | 有冲突必须处理;短码不可控 |
哈希方案的冲突处理是追问点:取 62 进制低 6 位(62⁶ ≈ 5.68×10¹⁰ 个空间),冲突时在长链后追加固定盐值重算,最多重试 3 次,仍冲突则降级到发号器方案。“同链得同码"是哈希方案独有的优点:同一长链重复提交不会产生多个短码。
| 状态码 | 语义 | 浏览器行为 | 对统计的影响 |
|---|---|---|---|
301 | 永久重定向 | 通常永久缓存,后续直接走本地 | 统计能力丢失:后续点击不再到达服务器 |
302 | 临时重定向 | 不永久缓存,每次仍请求短链服务 | 每次点击都能被记录 |
307/308 | 保留原方法的临时/永久重定向 | 同 302/301,但严格保持方法与请求体 | 短链场景全是 GET,实践上无差别 |
| |
重要
结论要说得干脆:只要业务需要点击统计、防刷、有效期控制或事后改链接,就必须用 302,代价是每次点击多一次服务端往返(同机房约 0.1~0.5 ms)与一份服务端负载。 只有纯跳转、不要任何统计、追求极致性能时 301 才成立。
存储用双向索引:短码查长链是主链路,长链查短码用于"重复提交返回同一短码”。
| |
**布隆过滤器(Bloom filter)**放在写路径:新增长链时先查过滤器判断"是否可能已存在”,不存在就直接生成新码,省掉一次数据库查重。它不支持删除,短码删除后位置仍在,会带来少量假阳性——在防重场景完全可接受,冲突仍由唯一索引兜底。
注意
追问的下一层通常是"短码不够用怎么办"和"热 key 怎么办"。前者答 62 进制位宽可扩(6 位 5.68×10¹⁰、7 位 3.5×10¹²);后者答本地缓存 + 多级缓存,但要承认"本地缓存让过期时间不再统一"这一代价。
36.6 排行榜系统:Redis 数据结构的实战
ORDER BY score DESC LIMIT 10000, 20 在数据量大时既无法用索引完成深分页,又要为每次查询付排序成本;ZSet(有序集合,sorted set)用跳表 + 哈希表实现,写入与排名查询都是对数级。
| 操作 | 命令 | 复杂度 | 用途 |
|---|---|---|---|
| 写入/更新/累加分数 | ZADD key score member / ZINCRBY key delta member | O(log N) | 用户分数变化、点赞加分 |
| 取榜内区间 | ZREVRANGE key start stop WITHSCORES | O(log N + M) | TOP N 展示,M 为返回条数 |
| 取某人名次 / 分数 / 合并榜 | ZREVRANK / ZSCORE / ZUNIONSTORE dest n key1 key2 | O(log N) / O(1) / O(总元素数) | “我的排名"无需遍历、我的分数、日榜合成周榜 |
ZREVRANK 是 O(log N) 这一点必须主动说——很多人以为要遍历整个榜才能算出"我排第几”。此外,ZSet 在分数相同时按成员名称的字典序排列,同一分数下的顺序与业务无关,解法是把时间戳编码进分数低位:
| |
提示
面试话术:考排行榜一定要主动说"分数相同时 ZSet 按成员字典序,所以我把时间戳编码进分数低位保证先到先得"——这一句能立刻区别于只背过 ZADD 的候选人。
警告
ZREVRANGE start stop 是偏移量分页:翻页期间榜单变化(新成绩写入改变了 score)会导致重复或漏项。稳妥做法是用 ZREVRANGEBYSCORE key (lastScore -inf LIMIT 0 20 以上一页最后一名(最后一条)的分数作为游标。
36.7 分布式 ID 与全局唯一约束的落地
订单号要同时满足三个约束:全局唯一、趋势递增(利于 B+ 树插入与范围查询)、可读(含业务语义便于排障)。
| |
数据库唯一索引是最后防线,设计题里必须主动说出来。它保证"一定不重复":即使时钟回拨、部署错误或人工操作造成重复订单号,插入也会失败而非落两条数据。
| |
36.8 Feed 流的推拉模式
| 维度 | 推模式(写扩散,fan-out on write) | 拉模式(读扩散,fan-out on read) |
|---|---|---|
| 写/读成本 | 每个粉丝写一份收件箱(写放大 = 粉丝数),读时直取收件箱 | 只写自己的发件箱,读时聚合关注列表 |
| |
警告
Feed 收件箱分页同样不能用 ZREVRANGE 的偏移量做无限下拉:下拉期间有新动态写入会让 score 变化、已有元素位置偏移,翻页出现重复或漏项。正确做法是用上一页最后一条的 score 作游标:ZREVRANGEBYSCORE inbox:{uid} (lastScore -inf LIMIT 0 20。
36.9 在线人数统计:数据结构选型
| 方案 | 命令 | 内存口径 | 能力边界 |
|---|---|---|---|
Set 存用户 ID | SADD / SCARD | 每个 ID 约 40~70 B(含哈希表开销) | 精确、可查"某人在不在线",但 1 亿用户约 4~7 GB |
Bitmap | SETBIT / BITCOUNT | 用户数 / 8 字节,1 亿用户约 12.5 MB | 精确计数与精确查单人,要求 UID 为稠密整数,稀疏时浪费 |
Bitmap 按 UID 取模分片 | 多 key SETBIT / BITCOUNT | 总量不变,但分散到多实例 | 解决单 key 过大与热 key |
HyperLogLog | PFADD / PFCOUNT | 固定约 12 KB/key | 标准误差约 0.81%,不支持精确查单个用户 |
| |
36.10 白板上的沟通节奏
- 先说结论再展开:“这个系统分三层:接入层限流、服务层异步、存储层分片。下面逐层说。“面试官需要在 30 秒内知道你的整体判断。
- 每做完一步确认方向:“接口设计就到这,接下来看数据模型,这样可以吗?“既给机会纠偏,也把节奏握在自己手里。
- 主动暴露取舍:“Redis 扣减成功后投递失败就会少卖,我的取舍是接受短暂不一致、用对账兜底。“主动说缺陷远比被问出来得分高。
- 被质疑时先复述再回答:“您的意思是问 Redis 故障时会丢多少数据,对吗?“复述避免答错题,也换来 3 秒思考时间。
- 画图与说话交替:沉默画三分钟架构图是低分行为;边画边解释每层存在的理由,不确定处标注"这一层我还没想清楚,先画上”。
提示
时间不够时的收尾模板(30 秒内说完):“时间关系我收一下。整体方案是三层:接入层用网关限流挡掉八成无效流量;服务层用 Redis 原子扣减 + MQ 削峰;存储层用 UPDATE ... WHERE stock > 0 兜底并靠唯一索引防重。还有三点没展开:一是库存对账任务的频率与幂等设计;二是 Redis 主从切换期间的少卖窗口;三是多机房部署时的一致性方案。您有兴趣的话我可以先讲其中一点。”
36.11 答不全时的补救
| 情形 | 补救策略 | 具体说法 |
|---|---|---|
| 只记得结论、推不出过程 | 拆成"我确定的部分"与"我需要推理的部分” | “我确定这里必须原子,至于是 Lua 还是 WATCH,我需要从原子性范围推一下。” |
| 完全没接触过 | 类比迁移 | “这和 Redis 持久化里 RDB 与 AOF 的取舍同类:要么牺牲实时性换吞吐,要么反过来。” |
| 有理论无实战 | 承认边界 + 给推理 | “这块我没有生产调参经验,我的推理是……如果要落地,我会先压测验证这个假设。” |
| 数字记不准 | 给量级并标注口径 | “具体数字记不清,量级是几十毫秒——机械盘随机读在毫秒级,SSD 在百微秒级。” |
| 方案被指出漏洞 | 接受并修正,不硬撑 | “您说得对,主从切换确实有这个窗口。修正方案是……” |
小心
绝对不要编造具体数字或系统行为。 典型的崩盘回答:"ZSet 插入是 O(1)"(概念错误,实际 O(log N))、"Kafka 单分区能扛 100 万 QPS”——无从验证。面试官一旦发现你在编,后面所有陈述都会被重新打折。说"我不确定,我的推理是……“永远比编一个数字安全。
36.12 设计题知识索引
| 设计题里的能力 | 需要的知识点 | 本书出处 |
|---|---|---|
| 防线 ①② 前端与网关 | 按钮置灰、验证码、CDN 分流、限流四算法、防刷黑名单 | 第 19 章 DNS 与 CDN、第 34 章限流、第 35 章网关 |
| 防线 ③④ 库存与扣减 | Redis 原子命令、Lua 脚本、持久化与主从 | 第 27 章 Redis 原理与缓存架构 |
| 防线 ⑤⑥ 削峰与落库 | 削峰填谷、分区顺序、消费幂等、延迟消息、UPDATE ... WHERE stock > 0、唯一索引、行锁 | 第 33 章可靠三件套、第 23 章索引、第 24 章锁 |
| 幂等与去重、熔断降级 | requestId 去重、唯一索引、状态机、SETNX、Redlock 争议、熔断三态、隔离策略、降级层次 | 第 35 章接口幂等与治理、第 33 章 MQ 视角、第 27/34 章分布式锁 |
| 分布式 ID 与一致性 | 六种方案、时钟回拨、号段、Snowflake、CAP/PACELC、BASE、柔性事务 | 第 34 章 分布式理论 |
| 容量与数据结构选型 | QPS 换算、Little 定律、延迟数量级、ZSet/Bitmap/HyperLogLog/布隆过滤器 | 第 3 章性能工程、第 27 章 Redis |
本章小结
| 关键结论 | 一句话表述 | 常见错误 |
|---|---|---|
| 评分维度先于方案 | 先澄清、再估算,方案才有靶子 | 一上来画架构图或写代码 |
| 估算决定架构等级 | QPS ≈ 2300 与 ≈ 10000 都指向缓存 + 分片 | 日活 1000 万却给出 20 万 QPS |
| 原子扣减消除 TOCTOU | 判断与扣减必须在一次原子操作里 | 先 GET 后 DECR 导致超卖 |
| 少卖来自"扣了没下单” | 扣减与投递之间必须可靠 | 只防超卖,不设计回补与对账 |
统计场景必须 302 | 301 被浏览器永久缓存,统计失真 | 只答"301 性能更好"而不提统计 |
| 排行榜要点在同分 | 时间戳编码进分数低位保证先到先得 | 直接 ZADD 业务分,忽略字典序 |
| 唯一索引是最后防线 | 发号器"尽量不重复”,索引"一定不重复” | 只讲 Snowflake,不提数据库约束 |
| 在线人数看查询需求 | 精确查询用 Bitmap/Set,大盘用 HyperLogLog | 用 HLL 去做发奖励的精确判断 |
| 沟通与收尾同样计分 | 主动说取舍,时间不够就说清没展开的点 | 沉默画图、草草收尾或编造数字 |
自测题
Q1(计算)日活 5000 万、人均 12 次请求、读:写 = 20:1、平均响应 3 KB、平均处理耗时 150 ms、峰值倍数取 4。求峰值读 QPS、峰值写 QPS、出向带宽、所需处理并发数,并说明该规模对应的架构等级。
答案与解析
| |
写 QPS 仅约 1300,单主库能扛,不需要为写分库;读 QPS 2.6 万必须靠缓存与读写分离;出向 635 Mbps 说明静态与图片必须进 CDN。错在哪:把读写比用反(把 20:1 当成写多),或忘了"先乘峰值倍数再拆读写”。面试官会追问:“这 4167 的并发怎么落到线程池和连接池上?“答法是用 Little 定律给出 L,说明连接池至少要覆盖 L 且留余量,同时强调超过下游承载能力时必须限流而不是加线程。
Q2(设计)秒杀结束统计发现:库存 1000 件,实际生成订单 1003 张。给出两条以上根因,并说明如何定位、如何修复。
答案与解析
超卖 3 件,根因候选:扣减非原子(存在"先查后减"的 TOCTOU 窗口);多实例各持本地库存副本各自扣减;库存在本地缓存与 Redis 各一份却只更新一处;数据库最终扣减没用 WHERE qty > 0 而是先查后更新。
定位与修复:核对 Redis 扣减路径与 Lua 脚本版本,确认是否存在非原子分支;按 created_at 取出超出的订单,看 orderNo 是否落在同一毫秒、同一分片——同毫秒聚集就是并发窗口的直接证据;比对 Redis 扣减流水条数与数据库订单条数。修复则把判断与扣减合并进单个 Lua 脚本、库存只保留一份权威副本、数据库改为 UPDATE stock SET qty = qty - 1 WHERE sku_id = ? AND qty > 0 并按影响行数判断成败,再加对账任务持续校验库存守恒。错在哪:只答"加分布式锁"而不定位根因,会把同毫秒的并发强行串行化,引入延迟与锁失效风险(锁的命令与坑见第 27 章)。面试官会追问:“如果是 Redis 主从切换导致的超卖呢?“要指出异步复制下主库扣减未同步就从库升主会丢扣减;补救是使用 WAIT、对库存路径强制走主库,并接受这段时间的部分不可用。
Q3(辨析)面试官问:“短链服务为什么不用 301?301 不是对浏览器和搜索引擎都更友好吗?”
答案与解析
三层要点:① 301 是永久重定向,浏览器通常永久缓存映射,之后的点击直接走本地缓存、不再到达服务端,于是点击统计、防刷、来源分析全部失效;② 302 是临时重定向,浏览器每次仍请求短链服务,服务端能记录每一次点击,代价是每次多一次服务端往返(同机房约 0.1~0.5 ms)与一份服务端负载;③ “对搜索引擎友好"在短链场景方向相反——外链权重会随 301 传给目标页,运营方通常不希望外部站点白拿权重,302 能保留短链自身的权重。
判定规则:只要需要统计、防刷、有效期控制或事后改链接,就必须 302;只有纯跳转、不要统计、追求极限性能时才 301。错在哪:只背”301 永久、302 临时"的定义(本科已教),却说不出对统计能力的影响,这是本题的零分线。面试官会追问:"307 和 308 有什么区别?“答:两者强制保持原请求方法与请求体(301/302 历史上允许客户端把 POST 改成 GET),短链场景全是 GET,实践上无差别。
Q4(设计)排行榜要求支持 TOP 100、“我的排名”、同分先到者优先,同时维护日榜周榜总榜。给出数据结构、分数编码、分页方式与内存控制方案。
答案与解析
结构:每榜一个 ZSet,key 为 rank:daily:{yyyyMMdd} / rank:weekly:{yyyyWW} / rank:total。写入用 ZADD/ZINCRBY;TOP N 用 ZREVRANGE key 0 99 WITHSCORES;我的排名用 ZREVRANK(O(log N))。分数编码:score = 业务分 × 10¹⁰ + (10¹⁰ − 1 − timestamp秒),让先到者分数更大;必须检查 double 的 53 位有效精度(约 9×10¹⁵)不被撑爆。分页与内存:不用偏移量分页(榜单变化会导致重复/漏项),用 ZREVRANGEBYSCORE key (lastScore -inf LIMIT 0 20 以分数为游标,同分边界用成员名做二次去重。日榜/周榜设 EXPIRE(日榜 8 天、周榜 70 天)让 Redis 自动清理;总榜按月归档冷数据;只展示头部时可只把 TOP 10 万放进 ZSet。周期榜合并用 ZUNIONSTORE,复杂度 O(总元素数),必须低峰执行——否则阻塞单线程的 Redis。
错在哪:答"用 MySQL ORDER BY + LIMIT 分页"会同时踩两个坑(深分页性能塌方、同分排序不稳定);答”ZSet 分数相同随机排序"是概念错误(实际按成员字典序)。面试官会追问:"ZUNIONSTORE 会不会阻塞?“这正是要主动提的,并给出错峰或离线计算写回的方案。
Q5(设计)某社交产品日活 500 万,其中头部 2000 个账号各有百万级粉丝。选择 Feed 流推拉模式并说明读路径;若追问"收件箱怎么分页"如何回答?
答案与解析
选混合模式:普通用户走推模式(写扩散),发动态时 ZADD inbox:{followerUid} timestamp postId;头部 2000 个大 V 走拉模式(读扩散),只写自己的发件箱 outbox:{uid}。读路径三步:① ZREVRANGE inbox:{uid} 0 19 取自己收件箱最新 20 条;② 读该用户关注的大 V 的发件箱中最新若干条(通常只有几十个);③ 内存按时间戳归并两个候选集,取前 20 条返回。
理由要用数字说清:若对大 V 也走推模式,一条动态写 100 万次收件箱,2000 个大 V 各发一条就是 20 亿次写入,写放大无法接受;若全部走拉模式,一个关注 2000 人的普通用户每次刷新要查 2000 个发件箱,读放大同样不可接受。混合模式的本质是把不可控的一侧(粉丝规模)从写路径移到读路径,只在粉丝数超过阈值时迁移。 另需补充在线推、离线拉:离线用户不投递,登录时按游标批量拉增量。
分页答法:ZREVRANGE 偏移量分页在下拉期间有新动态写入时会重复或漏项,必须用 ZREVRANGEBYSCORE inbox:{uid} (lastScore -inf LIMIT 0 20 以上一页最后一条的分数作游标;同一时间戳有多条时再叠加 postId 做二次游标。错在哪:只答"用推模式因为读快”,不提大 V 的写放大;或只答"用拉模式省存储”,不提读放大。面试官会追问:“阈值定多少、怎么迁移?“可答按粉丝数阈值(例如 10 万)判定,账号跨越阈值只影响后续动态,历史收件箱靠 TTL 自然过期收敛,不需要回填。
面试高频问题与陷阱
| 面试问法 | 考查角度 | 陷阱 / 加分点 | 对应本章小节 |
|---|---|---|---|
| “设计一个秒杀系统。“你打算从哪里开始? | 答题框架与需求澄清 | 错误回答:直接画架构图或打开编辑器写代码,不提规模、不问读写比与一致性要求 | 36.1、36.2 |
“日活 1000 万的系统,QPS 大概多少?一天存储涨多少?” | 容量估算的量级意识 | 错误回答:答"大概几万"但推不出过程,或把日请求量当 QPS(漏除 86400)。加分:给出均值—峰值—索引—副本的完整链条 | 36.3 |
| “秒杀库存为什么不能先查一下还有没有,再减一?” | 并发原子性 | 错误回答:答"会有并发问题"却说不出 TOCTOU 窗口的具体时序,也说不出为什么 Lua 能解决 | 36.4.1 |
| “扣减成功之后消息投递失败,库存怎么办?” | 少卖分析与补偿设计 | 错误回答:只答"重试"就不管了,没有回补、没有对账、回补本身也不幂等 | 36.4.2 |
“短链接用 301 还是 302?” | 状态码语义与统计能力 | 错误回答:只背”301 永久、302 临时"的定义,或答”301 性能好所以用 301",完全不提统计失真 | 36.5 |
“排行榜里两个人分数一样,Redis 会怎么排?” | ZSet 内部排序规则 | 错误回答:答"随机排"或"按插入顺序排”。正确:按成员字典序,需把时间戳编码进分数低位 | 36.6 |
| “在线人数统计你用什么结构?” | 数据结构选型与精度取舍 | 错误回答:直接答 HyperLogLog 而不问"是否需要精确查某个用户”,也不给 0.81% 误差的适用边界 | 36.9 |
| “订单号怎么生成?如果发号器出现重复怎么办?” | 分布式 ID 与最后防线 | 错误回答:只说 Snowflake,不提时钟回拨处理,更不提数据库唯一索引兜底 | 36.7 |
延伸阅读
- Martin Kleppmann《数据密集型应用系统设计》第 1 章(可靠性、可扩展性、可维护性)与第 11 章(流处理)——写放大与推拉模式的通用框架。
- Redis 官方文档:
ZADD/ZREVRANGE/ZREVRANGEBYSCORE(分页游标)、PFADD/PFCOUNT(HyperLogLog 误差口径)、SETBIT/BITCOUNT(Bitmap 复杂度)、EVAL(Lua 脚本的原子性说明)。 - MySQL 8.0 官方手册:
INSERT ... ON DUPLICATE KEY、UPDATE的"影响行数"语义、InnoDB 唯一索引与死锁检测;Apache Kafka 官方文档 “Design” 章节(分区内有序性与消费位点语义)、Apache RocketMQ 官方文档(延时消息与事务消息)。 - RFC 7231《Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content》第 6.4 节:
301/302/307/308的语义与缓存行为;本书交叉章节:第 3 章(Little 定律与延迟数量级)、第 27 章(Redis 数据结构与缓存一致性)、第 33 章(MQ 可靠性三件套)、第 34 章(CAP、分布式 ID、限流)、第 35 章(熔断降级与接口幂等)。