阶段:分布式与高并发篇 | 用法:三面/架构面的弹药库。每题按固定框架答:澄清需求 → 估算量级 → 存储设计 → 核心链路 → 难点与取舍。秒杀已在《02-高并发系统设计》串讲,本篇补另外 6 个高频场景。冲刺期每个场景对着朋友讲一遍,讲不下去的就是漏洞。
答题通用框架 ⭐
- 澄清:读多还是写多?数据量级?一致性要求高吗?有哪些隐藏约束(会过期?要统计?)——先问再画,直接开画是减分项。
- 估算:QPS = 日活 × 人均次数 ÷ 86400,再乘 10 倍峰值;存储 = 条数 × 单条大小 × (1+冗余),一年翻几倍要留量。
- 最小可用架构:客户端 → LB → 无状态服务 → 缓存 → DB,先跑通再谈优化。
- 难点:每个场景真正的考点(下表「考点」列),主动讲出来,别等面试官挖。
场景一:短链接系统 ⭐(读多写少 + 发号器)
- 澄清:要自定义后缀吗?要点击统计吗?链接会过期吗?
- 估算:1 亿条/年 ≈ 300 万/日,读多写少 100:1;单条约 200B → 存储无压力,瓶颈在读。
- 核心链路:长链 → 发号器取 ID → Base62 编码(0-9A-Za-z,62 进制)得 6~7 位短码 → 存
short → long映射 → 跳转用 301(永久重定向,浏览器缓存,别用 302 每次回源)。 - 发号器三选一:DB 号段(批量取段,防单点)、雪花(呼应《01-分布式理论基础》)、Redis INCR。自定义后缀走业务表唯一键,撞了就重试。
- 考点:为什么 301 vs 302;缓存热点(LRU + 布隆过滤不存在短码);点击统计异步 MQ 上报不阻塞跳转;防刷(同 IP 限流)。
场景二:海量数据去重(10 亿 URL 判断是否出现过)
- 精确方案:Redis SET/BitMap——内存算账:10 亿 × 32B ≈ 32GB 起步,太贵,除非量级小。
- 考点核心:布隆过滤器:k 个哈希 + 位数组,说「没有」一定准,说「有」可能假阳性;容量与误判率定
m = -n·ln p/(ln2)²、k = m/n·ln2;不能删除(计数布隆可)。10 亿 + 1% 误判 ≈ 1.2GB,这就是它赢的地方。 - 大数据视角:离线场景直接 Spark/Hive
distinct,或 bitmap 精确去重(RoaringBitmap);实时场景布隆 + 定期重建。 - 加分:UV 统计进一步用 HyperLogLog(12KB 误差 0.81%),去重要精确吗?先问指标精度要求。
场景三:排行榜(游戏/热榜/直播间)
- ZSET 起步:
ZADD rank {score} {member}、ZREVRANGE取 TopN、ZREVRANK查自己名次——单机 Redis 一题解决。 - 考点:① 分数相同怎么排(score 拼时间戳取负,双写一次排序键);② 数据量大分片:按分区/赛区拆多个 ZSET,Top 汇总归并;③ 实时更新频率高:客户端轮询改服务端推(WebSocket/SSE)、写合并(多次变更攒批 ZADD)。
- 亿级:分桶 + 近似(Count-Min 估热度)+ 定时快照,讲清「精确名次」与「TopN 列表」是两个量级的问题。
场景四:延迟任务 / 订单超时关闭 ⭐
- 方案四对比(面试就爱问这个):
| 方案 | 原理 | 问题 |
|---|---|---|
| 定时任务扫表 | 每分钟 WHERE status=待付 AND create<now-15min | 延迟大、扫表压力、分片麻烦 |
| JDK DelayQueue | 内存优先级队列 | 重启全丢,单机 |
| Redis ZSET | score=执行时间,轮询 ZRANGEBYSCORE | 需自己保证消费幂等与不丢 |
| RabbitMQ 死信/TTL | 队列设 TTL,过期进 DLX,消费 DLX | 队头阻塞(用延迟插件解决) |
| RocketMQ 延迟等级/定时消息 5.x | Broker 原生支持 | 版本与运维成本 |
- 口径:小任务用 MQ 延迟消息(业务解耦 + 不丢),量大用时间轮(HashedWheelTimer 内存版 / 容器化调度平台)。超时关单必须与支付回调并发安全:关单前查最新状态(CAS update … where status=待付),呼应秒杀防超卖。
场景五:Feed 流(朋友圈/微博首页)
- 推拉模型:推(写扩散,发布时写进所有粉丝收件箱,读 O(1) 写爆炸)、拉(读扩散,读时现查关注列表聚合,写 O(1) 读慢)、推拉结合(普通用户拉、大 V 推)是标准答案。
- 收件箱存储:Redis List/ZSET 存最近 N 条 ID + 回源 DB 翻页;粉丝千万级写扩散要分批 + 异步。
- 考点:删除与可见性(读时过滤被删内容)、计数的最终一致、大 V 发文的削峰(呼应 MQ 削峰填谷)。
场景六:附近的人 / GEO(一句话了解)
- Redis
GEOADD(底层 ZSET + GeoHash)→GEORADIUS圈范围;GeoHash 原理:经纬度交叠编码成字符串,前缀相同≈距离近,用空间索引换掉全表算距离。 - 讲清 GeoHash 方块边界问题(相邻可能跨格子,查周围 8 格)即可,不深入。
场景七:IM 消息系统(了解框架即可)
- 链路:客户端长连接(WebSocket,呼应《IO模型与网络编程》Netty)→ 接入层(连接散列保持)→ 逻辑层 → 存储(Timeline:每会话一个 seq 递增)。
- 难点四件套:不丢(ACK + 重传 + 离线拉取)、不乱(会话内 seq)、不重(幂等 msgId,呼应 MQ 消费幂等)、在线状态(临时节点/心跳,呼应 ZK)。
- 已读回执用位图/区间压缩,别逐条存。
万能兜底话术
没准备过的场景也别慌,套路一样:「先澄清三个问题 → 给一个能跑的最小方案 → 说两个瓶颈(读瓶颈加缓存、写瓶颈加队列/分片)→ 点一个一致性/可靠性取舍」。面试官考的是思维结构,不是标准答案。
高频面试题
- Q:短链为什么用 301 不用 302? —— 永久重定向浏览器会缓存跳转,省一次回源;302 每次都打到服务端,读放大 | 详见《03-系统设计场景题》
- Q:布隆过滤器能删元素吗?误判率怎么定? —— 基础版不能(置 0 会误伤);m 与 k 由容量 n 和目标误差 p 推出;「说没有一定准」 | 详见《03-系统设计场景题》
- Q:订单超时关闭有哪几种实现,怎么选? —— 扫表/DelayQueue/Redis ZSET/MQ 延迟;生产选 MQ 延迟消息或 RocketMQ 定时消息,关单用条件更新防与支付回调打架 | 详见《02-RabbitMQ》
- Q:排行榜同分怎么处理? —— score 复合(分数 × 基数 + 取反时间戳),一次排序解决「同分先达优先」 | 详见《01-Redis》
- Q:Feed 流推拉结合怎么划分用户? —— 粉丝量阈值:超阈值大 V 写扩散推,普通用户读扩散拉,兼顾写放大与读延迟 | 详见《03-系统设计场景题》
- Q:UV 统计为什么用 HyperLogLog? —— 只关心计数不关心成员,12KB 内存 0.81% 误差,比 SET 省几个量级 | 详见《01-Redis》
推荐资料
| 资料 | 用来干什么 | 链接 |
|---|---|---|
| JavaGuide 系统设计面试题 | 场景题答题框架的原始出处 | https://javaguide.cn/system-design/system-design-questions.html |
| pdai.tech 分布式与架构篇 | 发号器/缓存/消息队列的工程视角 | https://pdai.tech/md/arch/arch-index.html |
| 廖雪峰 Web 应用开发 | HTTP 重定向/状态码的底层语境 | https://www.liaoxuefeng.com/books/java/ |
| 码工具 Java API | 场景实现时查类签名 | https://www.matools.com/api/java8 |