技术博客

系统设计入门:如何准备大厂面试中最难的环节

系统设计面试是大厂后端/架构岗位的核心考察环节,也是最能拉开候选人差距的部分。本文从零讲解系统设计面试的框架:如何在 45 分钟内完成一个系统的设计(需求分析→估算→核心模块→深入讨论的四步法)、四个最常考的系统设计题的解法(短链系统/消息队列/CDN/分布式缓存)、设计中必须掌握的核心概念(一致性哈希/分库分表/读写分离/限流熔断),以及如何从课本知识过渡到能实际设计系统的思维方式。

系统设计面试大厂后端分布式架构面试准备学习路线

系统设计面试是大多数应届生最没准备好的环节。

算法题可以刷,背面经可以准备,但系统设计考的是“你真的理解这个系统为什么这样设计”——靠死记硬背不管用,靠理解才行。本文给你一个可操作的框架。


系统设计面试考什么

面试官不在乎你给出一个"正确答案"
——系统设计没有唯一正确答案

面试官在乎的是:
  1. 你能不能把模糊的需求转化成技术问题?
  2. 你在设计时是否考虑了关键约束(性能/可用性/一致性)?
  3. 你知不知道各种方案的优缺点和适用场景?
  4. 面对 follow-up 问题,你能不能深入讨论细节?

常见系统设计题:
  设计一个短链服务(bit.ly)
  设计一个消息队列(Kafka)
  设计一个分布式缓存(Redis 集群)
  设计一个秒杀系统
  设计微博/Twitter 的时间线功能
  设计一个文件存储系统(Dropbox)
  设计一个 CDN
  设计一个限流系统

四步法:45 分钟内完成一个系统设计

第一步:需求分析(5-8 分钟)

面试官给的题目往往很模糊:
  "设计一个 URL 短链服务"

你要做的:把模糊需求变成具体约束

必须问的问题:
  功能需求:
    用户能做什么?(生成短链、点击跳转、查看统计?)
    有没有自定义短链的需求?
    短链有没有过期时间?
    
  非功能需求(更重要):
    日活用户规模?(100万 vs 1亿,差一个数量级设计完全不同)
    读写比例?(短链服务:读 >> 写,100:1 很正常)
    可用性要求?(99.9% vs 99.999%,差别巨大)
    一致性要求?(是否允许短暂数据不一致?)

例:短链服务需求确认后
  功能:生成短链(URL → 短码)、点击跳转(短码 → 原URL)
  规模:日活 100 万用户,每天 1 亿次跳转,每天 100 万次创建
  可用性:99.9%(允许每年约 8 小时不可用)
  一致性:最终一致性(允许短暂不一致)

第二步:容量估算(3-5 分钟)

做估算的目的:帮助选择合适的技术方案

短链服务估算示例:

存储:
  每天 100 万条新短链
  每条记录大约 500 字节(短码 + 原URL + 元数据)
  3 年保留期:100万 × 365 × 3 × 500B ≈ 500GB
  → 普通关系数据库就能存,不需要分库分表

带宽:
  读 QPS:1亿次/天 ÷ 86400秒 ≈ 1160 QPS(峰值 3x = 3500 QPS)
  写 QPS:100万次/天 ÷ 86400秒 ≈ 12 QPS
  → 读多写少,需要缓存

响应时间目标:
  跳转:< 10ms(用户体验关键)
  生成:< 100ms

结论:需要缓存(Redis),不需要分布式数据库

第三步:高层设计(15-20 分钟)

核心:画出主要组件和数据流

短链服务的组件:
  ┌──────────┐         ┌──────────────┐       ┌──────────┐
  │  用户     │──请求──▶│ API Gateway  │──────▶│  短链服务 │
  └──────────┘         │ (限流/鉴权)  │       └─────┬────┘
                       └──────────────┘             │
                                               ┌────┴─────┐
                                               │  Redis   │ ← 缓存热点短链
                                               └────┬─────┘
                                                    │ miss
                                               ┌────▼─────┐
                                               │  MySQL   │ ← 持久化存储
                                               └──────────┘

主要接口:
  POST /api/shorten
    请求:{ "url": "https://..." }
    返回:{ "short_code": "abc123", "short_url": "https://short.ly/abc123" }
  
  GET /{short_code}
    返回:HTTP 301/302 重定向到原 URL

核心算法(短码生成):
  方案A:随机生成(6位 Base62 字符 = 56 亿种可能)
    → 简单,但要处理冲突
  方案B:哈希(MD5/SHA1 取前几位)
    → 确定性,但有冲突风险
  方案C:自增 ID + Base62 编码(推荐)
    → ID = 1000000 → Base62 = "4c92"
    → 天然唯一,无冲突

第四步:深入讨论(10-15 分钟)

面试官会 follow-up,例如:

Q:"如果短链服务要支持 10 倍规模呢?"
A:
  数据库层:读写分离(一主多从)→ 再大可以分库分表
  缓存层:Redis 集群,热点数据缓存命中率 > 99%
  服务层:无状态,水平扩展
  静态资源:CDN 分发
  
Q:"如何保证高可用?"
A:
  多地部署(主备或双活)
  数据库故障自动切换(MHA/Orchestrator)
  限流熔断(防止下游故障雪崩)
  优雅降级(缓存挂了,直接查数据库,性能降低但不中断)

Q:"如何统计短链点击次数?"
A:
  方案1:直接写数据库(高频写,成为瓶颈)
  方案2:写 Redis,定期刷到 MySQL(推荐)
  方案3:写 Kafka,消费者异步更新(最好,但复杂度高)

必须掌握的核心概念

1. 一致性哈希(分布式缓存必考)

问题:Redis 集群有 3 个节点,如何决定 Key 放在哪个节点?

朴素方案:hash(key) % 3
问题:增加/删除节点时,几乎所有 Key 都要重新分配(缓存雪崩)

一致性哈希的解法:
  把所有可能的 hash 值(0 ~ 2^32-1)排成一个环
  节点也映射到环上
  Key 顺时针找到第一个节点 → 该节点负责这个 Key
  
  优点:增加/删除节点时,只有相邻节点的 Key 需要重新分配
  加上虚拟节点:解决节点不均匀分布的问题

2. 读写分离

场景:读多写少的系统(90%读 + 10%写)

架构:
  一主库(写)
  多从库(读)
  应用根据操作类型路由到主/从

注意主从延迟:
  写完主库,读从库可能还没同步
  → 写后立即读:强制走主库
  → 对延迟不敏感的读:走从库

3. 限流算法

令牌桶(Token Bucket):推荐
  桶里有令牌,处理请求消耗令牌,定速补充
  允许一定的突发流量(桶里积攒的令牌)
  
漏桶(Leaky Bucket):
  请求进队列,定速处理(平滑输出)
  不允许突发,对下游保护更强

滑动窗口:
  统计滑动时间窗口内的请求数
  比固定窗口更平滑

实现选择:
  单机:Java Guava RateLimiter(令牌桶)
  分布式:Redis + Lua 脚本(原子操作保证准确)

4. 缓存策略

Cache Aside(旁路缓存):最常用
  读:先查缓存,miss 了查数据库,再写回缓存
  写:先更新数据库,再删除缓存
  → 删缓存不更新缓存:防止并发写的脏数据问题

Write Through:
  写数据库时同步更新缓存
  → 强一致,但写性能下降

Write Behind(异步写回):
  先更新缓存,异步更新数据库
  → 写性能最好,但有数据丢失风险

缓存穿透(查不存在的数据):
  → 缓存空值 或 布隆过滤器(Bloom Filter)

缓存击穿(热点 Key 失效):
  → 分布式锁或互斥锁(只让一个请求重建缓存)

缓存雪崩(大量 Key 同时失效):
  → 过期时间加随机抖动(+0~10分钟随机)

四个经典系统设计题解析框架

秒杀系统(最常考)

核心挑战:瞬间 100 万并发请求 1000 件商品

解决思路:
1. 前端:页面静态化(CDN),按钮置灰防重复点击
2. 网关:限流(每秒只放 X 个请求到下游)
3. Redis:预减库存(原子操作 DECR,负数则秒杀失败)
4. 消息队列:成功减库存的请求发到 Kafka,异步创建订单
5. 数据库:仅由消费者异步写,不承受高并发

关键点:
  库存存 Redis,数据库不直接承压
  超卖问题:Redis DECR 原子操作,判断 < 0 则失败
  幂等性:同一用户多次点击,只能秒杀一次(Redis set NX)

消息推送(微博/Twitter 时间线)

核心挑战:明星 1000 万粉丝,发一条微博 → 1000 万条记录写入

推模式(Push):发博时写入所有粉丝的 Feed
  优点:读很快(从自己的 Feed 表读)
  缺点:大 V 发博时写放大严重(1条→1000万条)

拉模式(Pull):读时从关注的人的博文表合并
  优点:写简单
  缺点:读时需要合并大量数据,延迟高

推拉结合(最优):
  普通用户:推模式
  大 V(超过 X 粉丝):拉模式
  读时:自己的 Feed 表(推) + 关注的大 V 实时拉取 → 合并排序

面试时的表达技巧

1. 大声说出你的思考过程
   面试官看不到你的脑子,你的分析过程比结论更重要
   "我考虑两种方案,方案A的优点是..缺点是..方案B..."

2. 先整体再细节
   不要一开始就深入某个组件(面试官会认为你只会这一点)
   先给出高层架构,确认面试官认可后,再深入某个部分

3. 主动说出你知道的权衡
   "这里用 MySQL 是因为数据量不大,如果规模增长到 10x,
    需要考虑分库分表,那时候可以用..."
   → 展示你懂得权衡,而不只是给一个"最好的"方案

4. 画图(白板或纸)
   系统设计必须画架构图,口头描述很难让面试官跟上

5. 量化你的说法
   不要说"很多请求",说"每秒 10000 QPS"
   不要说"响应很快",说"P99 延迟 < 100ms"

推荐的学习资源

书:
  《System Design Interview》(Alex Xu)
  → 最直接的面试准备书,有大量经典题目的完整解析
  
  《数据密集型应用系统设计》(DDIA)
  → 深度理解分布式系统原理,进阶必读
  
网站:
  System Design Primer(GitHub 开源,中文版也有)
  → github.com/donnemartin/system-design-primer

YouTube 频道:
  "Gaurav Sen" 和 "ByteByteGo"
  → 每个视频讲一个系统设计问题,视频 + 动画非常直观

练习:
  在纸上设计 → 讲给同学听 → 看标准答案对比
  找同学模拟面试(最有效的练习方式)

小结

系统设计能力不是天生的,是通过大量的阅读、思考和练习积累的。大一大二时读 DDIA,了解分布式系统的核心问题;大三开始做设计练习,用四步法(需求→估算→设计→深入)反复训练;大四面试时就能游刃有余。

最后一个建议:系统设计的学习,从“我为什么需要这个技术”开始比“这个技术怎么用”更有效。知道了 Redis 集群解决什么问题,再学它的实现,才会真正理解而不是死记硬背。