2041 字
5 分钟
系统设计面试题
系统设计是面试中的重难点,考察候选人的架构思维、技术广度和工程实践经验。本文整理了常见的高频系统设计题目,并提供详细的解题思路。
一、短 URL 系统设计
1.1 题目描述
设计一个短链接服务,将长 URL 转换为短 URL,要求支持高并发访问。
1.2 解题思路
- 短链生成:长 URL → 短 URL
- 短链跳转:短 URL → 长 URL(301/302 重定向)
- 数据存储:映射关系持久化
设计方案:
flowchart LR
A[用户请求] --> B[API网关]
B --> C{请求类型}
C -->|生成短链| D[长URL哈希计算]
D --> E[Base62编码]
E --> F[存储映射]
F --> G[返回短链]
C -->|访问短链| H[查询缓存]
H --> I{缓存命中?}
I -->|是| J[返回长URL]
I -->|否| K[查询数据库]
K --> J
短链生成算法:
| 方案 | 优点 | 缺点 |
|---|---|---|
| 哈希算法(MD5/SHA) | 生成速度快 | 可能冲突,需处理 |
| 自增 ID + Base62 | 无冲突,短 | ID 递增可预测 |
| 雪花算法 | 无冲突,分布式友好 | 长度稍长 |
| 随机数 + 冲突检测 | 分布式友好 | 需多次尝试 |
存储设计:
- 缓存层:Redis 缓存热点短链映射
- 存储层:MySQL 分库分表或 NoSQL(如 MongoDB)
- 容量估算:假设 100 亿条记录,每条记录 500B,约需 5TB 存储
性能优化:
- 使用布隆过滤器快速判断短链是否存在
- 多级缓存:本地缓存 → Redis → 数据库
- 异步写入:生成短链后异步持久化
1.3 代码示例
import hashlibimport base62
class URLShortener: def __init__(self, cache, db): self.cache = cache self.db = db
def shorten(self, long_url: str) -> str: # 计算哈希并编码 hash_value = hashlib.md5(long_url.encode()).hexdigest()[:8] short_code = base62.encode(int(hash_value, 16))
# 存储映射关系 self.cache.set(short_code, long_url, ex=86400) self.db.save(short_code, long_url)
return f"https://s.url/{short_code}"
def expand(self, short_code: str) -> str: # 先查缓存 long_url = self.cache.get(short_code) if long_url: return long_url
# 再查数据库 long_url = self.db.find(short_code) if long_url: self.cache.set(short_code, long_url, ex=86400)
return long_url二、Feed 流系统设计
2.1 题目描述
设计一个类似微博/朋友圈的 Feed 流系统,用户可以发布动态,粉丝可以实时看到关注人的动态。
2.2 解题思路
| 模式 | 原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| 推模式(写扩散) | 发布时写入所有粉丝收件箱 | 粉丝少的大V | 读快写慢 |
| 拉模式(读扩散) | 读取时聚合所有关注人动态 | 粉丝多的用户 | 写快读慢 |
混合方案设计:
flowchart TD
A[用户发布动态] --> B{粉丝数量}
B -->|< 1000| C[推模式]
B -->|>= 1000| D[拉模式]
C --> E[写入所有粉丝收件箱]
D --> F[仅写入发件箱]
G[用户拉取Feed] --> H{是否大V粉丝}
H -->|是| I[从发件箱拉取]
H -->|否| J[从收件箱读取]
存储设计:
- 用户关系表:存储关注关系(user_id, follower_id)
- 动态表:存储发布内容(post_id, user_id, content, timestamp)
- 收件箱表:存储推模式的动态副本(user_id, post_id, timestamp)
关键问题解决:
- 分页问题:使用游标分页代替 offset 分页
- 重复内容:对重复动态去重(基于 post_id)
- 实时性:使用消息队列异步推送
- 热点用户:大 V 采用拉模式,普通用户采用推模式
架构示意:
发布流程:用户发布 → API服务 → 消息队列 → 消费者 → 写入存储
拉取流程:用户请求 → API服务 → 缓存查询 → 数据库查询 → 聚合排序 → 返回2.3 面试要点
- 为什么选择推/拉模式?根据读写比例决定
- 如何处理大 V 粉丝多的场景?采用混合模式
- 如何保证实时性?消息队列 + 推送通知
- 如何处理缓存一致性问题?延迟双删 + 过期时间
三、延迟任务队列设计
3.1 题目描述
设计一个延迟任务队列,支持在指定时间后执行任务,如订单超时取消、定时消息推送等。
3.2 解题思路
| 方案 | 实现方式 | 优点 | 缺点 |
|---|---|---|---|
| 数据库轮询 | 定时扫描到期任务 | 简单可靠 | 性能差,延迟高 |
| Redis ZSet | score 存执行时间 | 高效,支持范围查询 | 需额外处理可靠性 |
| 时间轮 | 环形数组 + 指针 | O(1) 入队出队 | 内存限制 |
| 消息队列延迟 | RocketMQ 延迟消息 | 可靠性高 | 功能受限 |
Redis ZSet 方案详解:
flowchart LR
A[生产者] --> B[Redis ZSet]
B --> C[定时器轮询]
C --> D{到期任务}
D --> E[取出执行]
E --> F[消费者处理]
核心实现:
public class DelayQueue { private final RedisTemplate<String, Object> redis; private final String QUEUE_KEY = "delay:queue";
// 添加延迟任务 public void offer(String taskId, long delayMs) { long executeTime = System.currentTimeMillis() + delayMs; redis.opsForZSet().add(QUEUE_KEY, taskId, executeTime); }
// 消费到期任务 public void consume() { while (true) { long now = System.currentTimeMillis(); // 查询到期任务 Set<Object> tasks = redis.opsForZSet() .rangeByScore(QUEUE_KEY, 0, now, 0, 100);
if (tasks == null || tasks.isEmpty()) { Thread.sleep(100); continue; }
for (Object task : tasks) { // CAS 删除,保证只被消费一次 Long removed = redis.opsForZSet() .remove(QUEUE_KEY, task); if (removed > 0) { executeTask(task); } } } }}可靠性保证:
- 任务持久化:任务存储在 Redis,宕机不丢失
- 幂等消费:CAS 删除保证任务只被执行一次
- 重试机制:执行失败的任务重新入队
- 监控告警:监控积压任务数量和延迟时间
时间轮方案:
时间轮结构:[0] → [1] → [2] → ... → [N-1] → [0](循环) ↓ ↓ ↓ ↓任务链表 任务链表 任务链表 任务链表
每个槽位:存储该时刻执行的任务指针移动:每秒移动一格,执行当前槽位的任务四、秒杀系统设计
4.1 题目描述
设计一个秒杀系统,支持高并发场景下的库存扣减,保证不超卖、不少卖。
4.2 解题思路
- 高并发:瞬时流量巨大(QPS 可达 10 万+)
- 一致性:库存扣减不能出错
- 高可用:系统稳定运行
架构设计:
flowchart TD
A[用户请求] --> B[CDN静态资源]
A --> C[网关限流]
C --> D[答题/验证码]
D --> E[Redis库存预扣]
E --> F{库存充足?}
F -->|否| G[返回失败]
F -->|是| H[消息队列]
H --> I[数据库扣减]
I --> J[创建订单]
关键设计:
1. 多级限流
第一层:CDN 静态资源 + 动态请求过滤第二层:网关限流(令牌桶/漏桶)第三层:应用层限流(Sentinel)第四层:Redis 原子操作限流2. 库存扣减方案
// Redis Lua 脚本保证原子性String luaScript = """ local stock = redis.call('GET', KEYS[1]) if tonumber(stock) <= 0 then return 0 end redis.call('DECR', KEYS[1]) return 1 """;
// 执行库存扣减Long result = redis.execute(luaScript, Arrays.asList("stock:sku_001"));if (result == 1) { // 扣减成功,发送消息异步创建订单 mq.send("order.create", orderInfo);}3. 数据库优化
- 独立数据库:秒杀表单独库,隔离影响
- 行级锁优化:
UPDATE stock SET count = count - 1 WHERE id = ? AND count > 0 - 异步落库:先扣 Redis,后异步同步到数据库
4. 防超卖机制
| 层级 | 方案 | 说明 |
|---|---|---|
| 应用层 | 预扣库存 | 内存预减,快速失败 |
| 缓存层 | Lua 原子操作 | 保证 Redis 操作原子性 |
| 数据库层 | 乐观锁/条件更新 | WHERE count > 0 |
5. 防刷机制
- 验证码/答题:分散请求,防止机器刷单
- 用户限购:Redis 记录购买次数
- IP 限流:同一 IP 请求频率限制
- 黑名单机制:异常用户加入黑名单
4.3 性能优化要点
读优化:- 静态资源 CDN 加速- 商品详情页静态化- Redis 缓存热点数据
写优化:- 消息队列异步削峰- 批量写入数据库- 分库分表五、分布式 ID 生成器
5.1 题目描述
设计一个分布式 ID 生成器,要求全局唯一、有序、高性能。
5.2 解题思路
| 方案 | 格式 | 优点 | 缺点 |
|---|---|---|---|
| UUID | 32位十六进制 | 简单,无依赖 | 无序,过长 |
| 数据库自增 | 数字递增 | 简单有序 | 单点瓶颈 |
| 号段模式 | 数字递增 | 高性能 | 依赖数据库 |
| 雪花算法 | 64位数字 | 有序分布式 | 时钟回拨问题 |
| Redis 生成 | 数字递增 | 高性能 | 依赖 Redis |
雪花算法详解:
64位结构:0 - 41位时间戳 - 10位机器ID - 12位序列号
| 1位符号位 | 41位时间戳 | 10位机器ID | 12位序列号 ||----------|-----------|-----------|-----------|| 0 | 毫秒级时间 | 数据中心+机器 | 毫秒内序列 |
- 时间戳:41位可使用 69 年- 机器ID:支持 1024 台机器- 序列号:每毫秒可生成 4096 个 ID- 理论 QPS:400万+/秒实现代码:
public class SnowflakeIdGenerator { private final long epoch = 1288834974657L; // 起始时间戳 private final long workerIdBits = 10L; // 机器ID位数 private final long sequenceBits = 12L; // 序列号位数
private final long maxWorkerId = ~(-1L << workerIdBits); // 1023 private final long sequenceMask = ~(-1L << sequenceBits); // 4095
private long workerId; private long sequence = 0L; private long lastTimestamp = -1L;
public synchronized long nextId() { long timestamp = System.currentTimeMillis();
// 时钟回拨处理 if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨"); }
if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; }
lastTimestamp = timestamp;
return ((timestamp - epoch) << (workerIdBits + sequenceBits)) | (workerId << sequenceBits) | sequence; }}时钟回拨解决方案:
- 等待回拨:短暂回拨(<5ms)可等待时钟追上
- 借用未来时间:回拨时使用未来时间戳
- 备用机器ID:回拨时切换到备用 workerId
- 直接报错:严重回拨直接拒绝服务
号段模式(Leaf):
每次从数据库取一个号段:[1000, 2000] → 应用内存分配 → 分配完毕再取下一号段
优点:- 减少 DB 访问次数- 高性能(QPS 可达 10 万+)
缺点:- ID 不连续(重启可能丢失)- 依赖 DB六、一致性哈希与负载均衡
6.1 题目描述
设计一个分布式缓存系统,要求能够动态扩缩容,且缓存迁移量最小。
6.2 解题思路
传统哈希:hash(key) % N
问题:当节点数 N 变化时,几乎所有 key 的映射都会改变扩容:N=4 → N=5,约 80% 的 key 需要迁移一致性哈希原理:
graph LR
A[哈希环 0~2^32-1] --> B[节点均匀分布]
B --> C[Key 顺时针寻找最近节点]
C --> D[节点变更只影响相邻节点]
核心特性:
| 特性 | 说明 |
|---|---|
| 均衡性 | 数据均匀分布到各节点 |
| 单调性 | 新增节点不影响已分配数据 |
| 分散性 | 相同 key 映射到相同节点 |
| 负载均衡 | 避免节点负载不均 |
虚拟节点优化:
问题:节点少时,数据分布可能不均匀解决:每个物理节点对应多个虚拟节点
实现:Node A → Node A#1, Node A#2, ..., Node A#150Node B → Node B#1, Node B#2, ..., Node B#150
虚拟节点映射到哈希环上,使数据分布更均匀代码实现:
public class ConsistentHash { private final TreeMap<Long, String> ring = new TreeMap<>(); private final int virtualNodes = 150;
// 添加节点 public void addNode(String node) { for (int i = 0; i < virtualNodes; i++) { String virtualNode = node + "#" + i; long hash = hash(virtualNode); ring.put(hash, node); } }
// 获取 key 对应的节点 public String getNode(String key) { if (ring.isEmpty()) { return null; } long hash = hash(key); // 顺时针查找第一个大于等于 hash 的节点 Map.Entry<Long, String> entry = ring.ceilingEntry(hash); if (entry == null) { entry = ring.firstEntry(); // 环形,回到起点 } return entry.getValue(); }
private long hash(String key) { return CRC16.crc16(key.getBytes()) & 0xFFFFFFFFL; }}负载均衡策略对比:
| 策略 | 算法 | 适用场景 | 优缺点 |
|---|---|---|---|
| 轮询 | Round Robin | 服务端性能相近 | 简单,不考虑差异 |
| 加权轮询 | Weighted RR | 服务端性能不同 | 需配置权重 |
| 随机 | Random | 无状态服务 | 分布可能不均 |
| 最少连接 | Least Conn | 长连接场景 | 需维护连接数 |
| 一致性哈希 | Consistent Hash | 缓存场景 | 扩缩容友好 |
实际应用:
- Redis Cluster:槽位机制(16384 个槽)
- Memcached:Ketama 一致性哈希
- Nginx:支持一致性哈希负载均衡
- Dubbo:支持一致性哈希路由
七、总结
系统设计面试的核心考察点:
- 需求分析:明确功能需求和非功能需求
- 架构设计:画出系统架构图,说明各组件职责
- 容量估算:计算 QPS、存储、带宽等
- 瓶颈分析:识别系统瓶颈并提出解决方案
- 权衡取舍:说明方案的优缺点和选择理由
答题技巧:
- 先问清楚需求再动手设计
- 从简单方案开始,逐步优化
- 用数字说话(QPS、延迟、容量)
- 考虑边界情况和异常处理
- 提及监控、报警、容灾等
推荐阅读:
- 《Designing Data-Intensive Applications》
- 《系统设计面试指南》
- 各大厂技术博客(美团、阿里、字节)
提示:系统设计没有标准答案,重要的是展示你的思考过程和权衡能力。面试中要主动和面试官沟通,确认需求细节,然后给出适合场景的解决方案。
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时
相关文章 智能推荐
1
面试题库
面试 系统整理的面试题库,涵盖计算机基础、编程语言(Go/Python)、数据库(MySQL/Redis)、云原生(Kubernetes/容器)、系统设计与云安全等核心知识点。
2
MySQL 面试题
面试 面试中常见的 MySQL 题目——索引原理、事务隔离级别、锁机制、日志系统、SQL 优化等知识点整理。
3
Redis 面试题
面试 面试中常见的 Redis 题目——数据结构、持久化、集群方案、缓存穿透/击穿/雪崩、分布式锁等知识点整理。
4
项目经验面试题
面试 面试中关于项目经验的典型问题与作答思路整理。
5
计算机基础面试题
面试 面试中常见的计算机基础题目——网络七层模型、进程与线程、内存管理、系统调用等核心知识点整理。






