系统设计方法论
架构师面试的核心能力:如何系统化地设计一个完整的系统
📖 章节概述
系统设计面试不是考察你是否记住了某个技术细节,而是考察你如何将复杂问题拆解为可落地的技术方案的能力。本章将介绍完整的系统设计方法论,帮助你建立结构化的思维框架。
学习目标
- 掌握系统设计的完整流程(从需求到架构的7个步骤)
- 学会容量估算(QPS、存储、带宽的计算方法)
- 理解架构演进(从MVP到高可用的路径)
- 掌握技术选型的权衡方法(trade-off分析)
- 熟悉面试技巧(如何沟通、如何画图、如何应对追问)
🔄 一、系统设计的完整流程
系统设计面试通常是45-60分钟,需要在有限时间内完成以下7个步骤:
┌─────────────────────────────────────────────────────────────┐
│ 系统设计流程(45-60分钟) │
├─────────────────────────────────────────────────────────────┤
│ 1. 需求澄清(5-8分钟) → 功能范围、数据规模、性能要求 │
│ 2. 容量估算(5-8分钟) → QPS、存储、带宽计算 │
│ 3. 接口设计(5-8分钟) → API定义、请求/响应格式 │
│ 4. 数据模型(5-8分钟) → 表结构、索引、分库分表 │
│ 5. 架构设计(15-20分钟) → 画架构图、讲解核心流程 │
│ 6. 深入细节(10-15分钟) → 技术选型、优化方案 │
│ 7. 总结收尾(3-5分钟) → 回顾设计、讨论tradeoff │
└─────────────────────────────────────────────────────────────┘
⚠️ 时间分配建议
| 阶段 | 时间占比 | 重要程度 | 注意事项 |
|---|---|---|---|
| 需求澄清 | 10-15% | 必做,避免理解偏差 | |
| 容量估算 | 10-15% | 展示数据敏感度 | |
| 接口设计 | 10-15% | 可选,看时间 | |
| 数据模型 | 10-15% | 核心表结构必须有 | |
| 架构设计 | 30-40% | 核心,决定面试成败 | |
| 深入细节 | 20-30% | 展示技术深度 | |
| 总结收尾 | 5-10% | 查漏补缺 |
二、步骤1:需求澄清(Clarify Requirements)
1️⃣ 为什么要澄清需求?
面试题通常故意模糊,例如:
- "设计一个短链系统"
- "设计一个秒杀系统"
- "设计一个IM系统"
你需要通过提问来明确范围,避免:
- 设计了不需要的功能(过度设计)
- 遗漏了核心功能(理解偏差)
- 性能指标不清楚(无法优化)
2️⃣ 需求澄清的三个维度
A. 功能需求(Functional Requirements)
通过提问明确核心功能和边界:
示例:设计短链系统
| 问题 | 为什么要问 | 影响的设计决策 |
|---|---|---|
| "是否需要自定义短链?" | 核心功能范围 | 如果需要,要考虑冲突检测 |
| "是否需要统计点击量?" | 是否需要统计系统 | 需要设计统计表和异步写入 |
| "短链是否有有效期?" | 数据清理策略 | 需要定时任务清理过期数据 |
| "是否需要防刷?" | 安全需求 | 需要限流和风控模块 |
提问模板:
1. 这个系统的核心功能是什么?(3-5个)
2. 是否需要XXX功能?(列出可能的扩展功能)
3. 对于YYY场景,我们需要支持吗?
4. 有没有不需要考虑的功能?(明确不做什么)
B. 非功能需求(Non-Functional Requirements)
明确性能、可用性、一致性的要求:
| 维度 | 问题 | 影响的设计 |
|---|---|---|
| 性能 | "预期QPS是多少?峰值QPS?" | 决定是否需要缓存、分库分表 |
| 可用性 | "可用性要求是99.9%还是99.99%?" | 决定多活、容灾方案 |
| 一致性 | "能否接受最终一致性?" | 决定是否使用分布式事务 |
| 延迟 | "响应时间要求是多少?P99?" | 决定缓存策略、数据库优化 |
常见的非功能需求:
- 性能:QPS(Queries Per Second)、TPS(Transactions Per Second)
- 延迟:P50、P95、P99、P999
- 可用性:99%、99.9%(3个9)、99.99%(4个9)
- 一致性:强一致性、最终一致性、因果一致性
- 扩展性:水平扩展能力、支持的用户规模
C. 数据规模(Scale)
明确数据量和增长速度:
示例问题:
- "日活用户(DAU)是多少?月活(MAU)?"
- "每个用户平均产生多少数据?"
- "数据保留多久?是否需要归档?"
- "预期多久达到什么规模?"
为什么重要:
- 决定是否需要分库分表
- 决定存储方案(MySQL、MongoDB、HBase)
- 决定缓存策略(缓存多少数据)
3️⃣ 需求澄清的输出
完成需求澄清后,你应该得到:
功能清单:
核心功能(Must Have):
1. 生成短链
2. 短链跳转
3. 统计点击量
扩展功能(Nice to Have):
4. 自定义短链
5. 二维码生成
6. 链接有效期
性能指标:
- 日活用户:1000万
- 生成短链QPS:1000
- 跳转QPS:10000(读写比 10:1)
- 响应时间:P99 < 100ms
- 可用性:99.9%
数据规模:
- 每天生成100万条短链
- 数据保留1年
- 总数据量:100万 × 365 = 3.65亿条
三、步骤2:容量估算(Capacity Estimation)
容量估算展示你的数据敏感度和工程经验,是面试的加分项。
1️⃣ 估算的三个维度
A. QPS估算(Queries Per Second)
从DAU推算QPS:
公式:QPS = DAU × 每用户操作次数 / 86400秒 × 峰值系数
步骤:
1. DAU(日活用户)
2. 每用户操作次数
3. 平均QPS = 总操作 / 86400
4. 峰值QPS = 平均QPS × 峰值系数(通常2-5倍)
示例:短链系统
假设:
- DAU = 1000万
- 每用户每天点击10次短链
- 峰值系数 = 3倍
计算:
- 总操作 = 1000万 × 10 = 1亿次/天
- 平均QPS = 1亿 / 86400 ≈ 1157 QPS
- 峰值QPS = 1157 × 3 ≈ 3500 QPS
读写比例:
假设生成:跳转 = 1:10
- 写QPS(生成短链)= 3500 / 11 ≈ 320 QPS
- 读QPS(跳转)= 3500 × 10 / 11 ≈ 3200 QPS
B. 存储估算(Storage)
公式:
存储 = 单条数据大小 × 数据条数 × 冗余系数
示例:短链系统
表结构(简化):
- short_url: VARCHAR(10) = 10 bytes
- long_url: VARCHAR(2048) = 2048 bytes
- created_at: TIMESTAMP = 8 bytes
- click_count: INT = 4 bytes
总计:2070 bytes ≈ 2KB
数据量:
- 每天生成100万条
- 保留1年:100万 × 365 = 3.65亿条
存储计算:
- 裸数据:3.65亿 × 2KB = 730GB
- 索引:730GB × 30% = 219GB
- 冗余(主从):(730 + 219) × 2 = 1.9TB
结论:需要2TB存储
C. 带宽估算(Bandwidth)
公式:
带宽 = QPS × 平均数据大小
示例:短链系统
流量计算:
- 跳转请求:3200 QPS × 0.5KB = 1.6 MB/s ≈ 12.8 Mbps
- 跳转响应(302重定向):3200 QPS × 2KB = 6.4 MB/s ≈ 51.2 Mbps
- 总带宽:12.8 + 51.2 = 64 Mbps
结论:需要100 Mbps带宽(留余量)
2️⃣ 记住这些常用数字
时间单位:
1秒 = 1,000 毫秒(ms) = 1,000,000 微秒(μs) = 1,000,000,000 纳秒(ns)
1天 = 86,400 秒 ≈ 10^5 秒(估算时)
存储单位:
1 KB = 1,024 bytes ≈ 10^3 bytes
1 MB = 1,024 KB ≈ 10^6 bytes
1 GB = 1,024 MB ≈ 10^9 bytes
1 TB = 1,024 GB ≈ 10^12 bytes
常见数据大小:
- 一个字符:1 byte
- 一个整数(INT):4 bytes
- 一个时间戳(TIMESTAMP):8 bytes
- 一个UUID:16 bytes (128 bits)
- 一张图片:100 KB - 1 MB
- 一个视频:10 MB - 100 MB
系统性能参考:
- L1缓存访问:0.5 ns
- L2缓存访问:7 ns
- 内存访问:100 ns
- SSD随机读:150 μs
- HDD磁盘寻道:10 ms
- 网络:同城1ms,跨城10-50ms
数据库性能:
- MySQL单机:1万 QPS(读),5千 QPS(写)
- Redis单机:10万 QPS(读写)
- MongoDB单机:1万 QPS
3️⃣ 容量估算的输出
完成容量估算后,写在白板上:
┌─────────────────────────────────────────────────────────────┐
│ 容量估算结果 │
├─────────────────────────────────────────────────────────────┤
│ QPS: │
│ - 峰值读QPS: 3,200 │
│ - 峰值写QPS: 320 │
│ │
│ 存储: │
│ - 数据量: 3.65亿条 │
│ - 存储空间: 2TB(含索引和冗余) │
│ │
│ 带宽: │
│ - 出口带宽: 64 Mbps → 需要100 Mbps │
└─────────────────────────────────────────────────────────────┘
🔌 四、步骤3:接口设计(API Design)
接口设计是可选步骤,如果时间紧张可以跳过,但建议至少列出核心API。
1️⃣ RESTful API设计原则
HTTP方法:
- GET:查询资源
- POST:创建资源
- PUT:更新资源(完整更新)
- PATCH:更新资源(部分更新)
- DELETE:删除资源
2️⃣ 示例:短链系统API
# 1. 生成短链
POST /api/v1/shorten
Request:
{
"long_url": "https://www.example.com/very/long/url",
"custom_alias": "my-link", // 可选,自定义短链
"expire_at": "2024-12-31" // 可选,过期时间
}
Response:
{
"code": 0,
"message": "success",
"data": {
"short_url": "https://short.ly/abc123",
"short_code": "abc123",
"long_url": "https://www.example.com/very/long/url",
"created_at": "2024-01-01T00:00:00Z",
"expire_at": "2024-12-31T23:59:59Z"
}
}
# 2. 短链跳转(HTTP 302重定向)
GET /{short_code}
Response:
HTTP/1.1 302 Found
Location: https://www.example.com/very/long/url
# 3. 查询短链统计
GET /api/v1/stats/{short_code}
Response:
{
"code": 0,
"data": {
"short_code": "abc123",
"click_count": 12345,
"created_at": "2024-01-01T00:00:00Z",
"daily_stats": [
{"date": "2024-01-01", "clicks": 100},
{"date": "2024-01-02", "clicks": 150}
]
}
}
3️⃣ API设计的最佳实践
使用版本号:/api/v1/,便于后续升级 RESTful风格:资源名词复数,如 /users、/orders统一响应格式:包含 code、message、data错误码设计:定义清晰的错误码 分页参数:page、page_size、total过滤排序:支持 filter、sort、order
错误码示例:
{
"code": 4001,
"message": "短链已存在",
"data": null
}
错误码规范:
- 0: 成功
- 1xxx: 通用错误(1001: 参数错误,1002: 未授权)
- 2xxx: 业务错误(2001: 短链不存在,2002: 短链已过期)
- 3xxx: 系统错误(3001: 数据库错误,3002: 缓存错误)
🗄️ 五、步骤4:数据模型设计(Data Model)
数据库设计是系统的核心骨架,必须认真设计。
1️⃣ 表结构设计原则
三范式(但可以适当冗余以提升性能) 索引设计(根据查询场景设计索引) 分库分表(单表超过500万-1000万行考虑) 字段类型(选择合适的类型,节省空间)
2️⃣ 示例:短链系统表设计
核心表:url_mapping(短链映射表)
CREATE TABLE url_mapping (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
short_code CHAR(8) NOT NULL COMMENT '短链码(Base62编码)',
long_url VARCHAR(2048) NOT NULL COMMENT '原始长链接',
user_id BIGINT UNSIGNED DEFAULT NULL COMMENT '创建用户ID',
click_count INT UNSIGNED DEFAULT 0 COMMENT '点击次数',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
expire_at TIMESTAMP NULL COMMENT '过期时间',
UNIQUE KEY uk_short_code (short_code),
KEY idx_user_id (user_id),
KEY idx_created_at (created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短链映射表';
统计表:url_stats(访问统计表)
CREATE TABLE url_stats (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
short_code CHAR(8) NOT NULL COMMENT '短链码',
access_date DATE NOT NULL COMMENT '访问日期',
click_count INT UNSIGNED DEFAULT 0 COMMENT '当日点击次数',
UNIQUE KEY uk_short_code_date (short_code, access_date),
KEY idx_access_date (access_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='短链访问统计表';
3️⃣ 分库分表策略
何时需要分库分表?
- 单表数据量超过500万-1000万行
- 单表磁盘文件超过2GB
- 单表索引过大,查询性能下降
分库分表方案:
方案1:按短链码Hash分表
表名:url_mapping_0, url_mapping_1, ..., url_mapping_99(100张表)
路由规则:hash(short_code) % 100
方案2:按时间范围分表
表名:url_mapping_202401, url_mapping_202402, ...
路由规则:根据created_at年月分表
优点:历史数据可归档
缺点:热点数据集中在最新表
方案3:按用户ID分库
库名:db_0, db_1, ..., db_15(16个库)
路由规则:hash(user_id) % 16
优点:用户维度查询高效
缺点:需要全局ID生成器
4️⃣ 索引设计原则
主键索引:自增ID或雪花ID 唯一索引:业务唯一键(如 short_code) 普通索引:高频查询字段(如 user_id、created_at) 复合索引:多条件查询(遵循最左前缀原则)
索引优化技巧:
-- 错误:索引字段使用函数
SELECT * FROM url_mapping WHERE DATE(created_at) = '2024-01-01';
-- 正确:直接使用索引字段
SELECT * FROM url_mapping
WHERE created_at >= '2024-01-01 00:00:00'
AND created_at < '2024-01-02 00:00:00';
-- 复合索引的最左前缀原则
-- 索引:(user_id, created_at)
-- 可以使用的查询:
SELECT * FROM url_mapping WHERE user_id = 123; -- 使用索引
SELECT * FROM url_mapping WHERE user_id = 123 AND created_at > '2024-01-01'; -- 使用索引
SELECT * FROM url_mapping WHERE created_at > '2024-01-01'; -- 不使用索引(违反最左前缀)
🏗️ 六、步骤5:架构设计(Architecture Design)
这是面试的核心部分,占30-40%的时间。
1️⃣ 架构演进三部曲
优秀的架构是演进出来的,不是一开始就设计得很复杂。
V1:单体架构(MVP)
先设计一个最简单可用的版本:
┌─────────────────────────────────────────────────────────────┐
│ V1:单体架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ 客户端 │ │
│ └────┬─────┘ │
│ │ HTTP │
│ │
│ ┌─────────────┐ │
│ │ Web Server │ │
│ │ (Nginx) │ │
│ └──────┬──────┘ │
│ │ │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Application │ ─────→ │ MySQL │ │
│ │ (Go/Java) │ │ (Master) │ │
│ └─────────────┘ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
特点:
简单易实现,快速上线
单机部署,运维成本低
性能瓶颈:数据库和应用都是单点
可用性差:任何组件故障都会导致服务不可用
V2:读写分离 + 缓存
引入缓存和读写分离提升性能:
┌─────────────────────────────────────────────────────────────┐
│ V2:读写分离 + 缓存 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ 客户端 │ │
│ └────┬─────┘ │
│ │ │
│ │
│ ┌─────────────┐ │
│ │Load Balancer│ (Nginx/LVS) │
│ └──────┬──────┘ │
│ │ │
│ ├─────────┬─────────┬─────────┐ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ App 1 │ │ App 2 │ │ App N │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └────────┬───┴────────────┘ │
│ │ │
│ ┌─────────┼─────────────┐ │
│ │ │ │ │
│ │
│ ┌────────┐ ┌────────┐ ┌──────────┐ │
│ │ Redis │ │ MySQL │ │ MySQL │ │
│ │(缓存) │ │(Master)│ │ (Slave) │ │
│ └────────┘ └────────┘ └──────────┘ │
│ │ │ │
│ └──────────────┘ (主从复制) │
│ │
└─────────────────────────────────────────────────────────────┘
改进:
引入Redis缓存,读性能提升10-100倍
读写分离,主库写、从库读
应用层水平扩展,负载均衡
主库仍是单点,写能力有限
缓存一致性问题
V3:分库分表 + 高可用
引入分库分表和高可用:
┌─────────────────────────────────────────────────────────────┐
│ V3:分库分表 + 高可用架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ CDN │ (静态资源加速) │
│ └────┬─────┘ │
│ │ │
│ ┌────┴───────────────┐ │
│ │ Load Balancer │ (Nginx + Keepalived) │
│ │ (主备高可用) │ │
│ └────┬───────────────┘ │
│ │ │
│ ├────────────────┬────────────────┐ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ App集群 │ │ App集群 │ │ App集群 │ │
│ │ (机房A) │ │ (机房B) │ │ (机房C) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ┌────┴───────────────┴───────────────┴────┐ │
│ │ 中间件层 │ │
│ │ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Redis集群 │ │ Kafka │ │ │
│ │ │ (主从+哨兵) │ │ (消息队列) │ │ │
│ │ └────────────┘ └────────────┘ │ │
│ └────┬───────────────────────────────────┘ │
│ │ │
│ ┌────┴───────────────┬────────────────┐ │
│ │ 数据库层(分库分表) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ │ DB Shard│ │ DB Shard│ │ DB Shard│ │
│ │ │ 0 │ │ 1 │ │ N │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ │ ┌───┴───┐ ┌───┴───┐ ┌───┴───┐ │
│ │ │ Slave │ │ Slave │ │ Slave │ │
│ │ └───────┘ └───────┘ └───────┘ │
│ └───────────────────────────────────────────────────────┘│
│ │
└─────────────────────────────────────────────────────────────┘
改进:
分库分表,突破单机写瓶颈
多机房部署,容灾能力强
Redis哨兵模式,缓存高可用
Kafka异步化,削峰填谷
支持千万级DAU
2️⃣ 架构图绘制技巧
标准符号:
□ 矩形:服务器、应用、数据库
○ 圆形:客户端、用户
◇ 菱形:决策点、网关
→ 箭头:数据流向(标注协议:HTTP、TCP、gRPC)
== 双线:主从复制、数据同步
分层原则:
从上到下:
1. 客户端层(Web、Mobile、API)
2. 接入层(CDN、DNS、负载均衡)
3. 应用层(Web服务器、业务服务)
4. 中间件层(缓存、消息队列)
5. 数据层(数据库、存储)
关键要素:
- 标注每个组件的作用
- 标注数据流向和协议
- 标注关键的技术选型
- 标注数据量和QPS
️ 七、步骤6:深入细节(Deep Dive)
这一步展示你的技术深度和工程经验。
1️⃣ 核心流程详解
选择1-2个核心流程,深入讲解实现细节。
示例:短链跳转流程
┌─────────────────────────────────────────────────────────────┐
│ 短链跳转流程(详细版) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 用户访问短链 │
│ GET https://short.ly/abc123 │
│ │ │
│ │
│ 2. Nginx接收请求 │
│ - 解析Host和Path │
│ - 转发到应用服务器 │
│ │ │
│ │
│ 3. 应用层处理 │
│ ┌─────────────────────────────────┐ │
│ │ a. 查询Redis缓存 │ │
│ │ key = "short:abc123" │ │
│ │ value = "https://long.url" │ │
│ │ TTL = 3600s │ │
│ ├─────────────────────────────────┤ │
│ │ b. 缓存未命中 → 查询MySQL │ │
│ │ SELECT long_url │ │
│ │ FROM url_mapping │ │
│ │ WHERE short_code = 'abc123' │ │
│ ├─────────────────────────────────┤ │
│ │ c. 更新缓存 │ │
│ │ SET short:abc123 long_url │ │
│ │ EXPIRE 3600 │ │
│ ├─────────────────────────────────┤ │
│ │ d. 异步更新点击量(Kafka) │ │
│ │ 发送消息 → Kafka │ │
│ │ topic: url_click │ │
│ │ payload: {short_code, ts} │ │
│ └─────────────────────────────────┘ │
│ │ │
│ │
│ 4. 返回302重定向 │
│ HTTP/1.1 302 Found │
│ Location: https://long.url │
│ │ │
│ │
│ 5. 浏览器自动跳转到长链接 │
│ │
└─────────────────────────────────────────────────────────────┘
2️⃣ 技术选型与权衡(Trade-off)
面试官喜欢问:"为什么选XXX,而不是YYY?"
你需要展示权衡分析能力:
示例:缓存选型
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| Redis | 性能极高(10万QPS)、数据结构丰富、持久化 | 内存成本高、数据量有限 | 热点数据、高频访问 |
| Memcached | 简单、性能高、多线程 | 无持久化、数据结构单一 | 纯缓存场景 |
| 本地缓存 | 无网络开销、性能最高 | 数据不共享、缓存不一致 | 静态数据、配置 |
权衡因素:
- 性能:Redis 10万QPS vs MySQL 1万QPS
- 成本:Redis内存贵 vs MySQL磁盘便宜
- 一致性:Redis最终一致 vs MySQL强一致
- 可用性:Redis高可用(哨兵)vs MySQL主从
- 功能:Redis数据结构丰富 vs MySQL SQL强大
结论:
选择Redis的原因:
1. 短链跳转是高频操作(3000 QPS),需要极致性能
2. 数据量小(几百GB),适合Redis
3. 允许最终一致性(缓存失效最多查一次DB)
4. 支持TTL,自动过期
3️⃣ 性能优化方案
常见优化点:
A. 缓存优化
1. 多级缓存
Browser Cache → CDN → Redis → MySQL
2. 缓存预热
系统启动时,预加载热点数据
3. 缓存穿透
布隆过滤器 → 拦截不存在的key
4. 缓存击穿
热点key加互斥锁,避免并发查DB
5. 缓存雪崩
过期时间加随机值,避免同时失效
B. 数据库优化
1. 索引优化
- 覆盖索引:SELECT时只查索引列
- 联合索引:遵循最左前缀原则
2. 查询优化
- 避免SELECT *
- 使用LIMIT分页
- 避免子查询,改用JOIN
3. 分库分表
- 垂直拆分:按业务拆表
- 水平拆分:按Hash/Range分表
4. 读写分离
- 主库写、从库读
- 延迟监控(主从延迟<1s)
C. 异步化改造
同步 → 异步:
1. 点击量统计:同步更新DB → 异步消息队列
2. 日志记录:同步写文件 → 异步批量写入
3. 通知推送:同步调用API → 异步任务队列
八、步骤7:总结收尾(Wrap Up)
最后3-5分钟,快速回顾设计,查漏补缺。
1️⃣ 回顾核心设计
简要总结:
1. 需求:支持1000万DAU,峰值3500 QPS
2. 架构:读写分离 + Redis缓存 + 异步统计
3. 优化:布隆过滤器防穿透,分库分表支持扩展
4. 监控:QPS、延迟、缓存命中率
2️⃣ 讨论未覆盖的点
主动提出可扩展的方向:
如果时间充足,我们还可以讨论:
1. 短链防刷:限流、风控、验证码
2. 自定义短链:冲突检测、敏感词过滤
3. 数据分析:BI报表、漏斗分析
4. 国际化:多地域部署、就近访问
3️⃣ 回答追问
面试官可能的追问:
| 追问 | 回答思路 |
|---|---|
| "如何防止短链被恶意刷?" | 限流(令牌桶)+ 风控(IP黑名单)+ 验证码 |
| "缓存和DB数据不一致怎么办?" | 允许短暂不一致 + 缓存失效 + 定期对账 |
| "单机Redis扛不住怎么办?" | Redis Cluster分片 + 客户端分片 + Proxy分片 |
| "如何支持自定义短链?" | 唯一性检查 + 敏感词过滤 + 付费功能 |
九、面试技巧与常见错误
1️⃣ 黄金法则
DO:
1. 澄清需求:不要假设,主动提问
2. 从简单开始:先MVP,再优化
3. 展示思考过程:说出你的推理
4. 画清晰的图:用标准符号,逻辑清晰
5. 讨论权衡:没有完美方案,只有合适的
6. 保持沟通:不要闷头设计,边画边讲
7. 举真实案例:"我在XX公司做过类似系统"
DON'T:
1. 不要一开始就设计复杂架构
2. 不要忽略非功能需求(性能、可用性)
3. 不要假设面试官知道你在想什么(要讲出来)
4. 不要纠结完美方案(时间有限,先完成核心)
5. 不要死记硬背(面试官会追问细节)
6. 不要说"我不知道"(可以说"我的理解是...")
2️⃣ 常见错误
| 错误 | 后果 | 正确做法 |
|---|---|---|
| 一开始就设计分布式系统 | 过度设计,浪费时间 | 先单体MVP,再演进 |
| 容量估算错误 | 架构设计不合理 | 记住常用数字,公式推导 |
| 画架构图太乱 | 面试官看不懂 | 用标准符号,分层清晰 |
| 只讲技术,不讲权衡 | 缺少深度思考 | 对比多个方案优缺点 |
| 忽略监控告警 | 不够生产级别 | 主动提出监控指标 |
3️⃣ 时间管理技巧
45分钟面试的时间分配:
┌────────────────────────────────────────┐
│ 0-5min: 需求澄清(问清楚要做什么) │
│ 5-10min: 容量估算(QPS、存储、带宽) │
│ 10-15min: 接口和数据模型(可选) │
│ 15-35min: 架构设计(核心,画图+讲解) │
│ 35-40min: 深入细节(技术选型、优化) │
│ 40-45min: 总结收尾(查漏补缺) │
└────────────────────────────────────────┘
如果时间不够:
- 跳过接口设计(直接说"API遵循RESTful规范")
- 简化数据模型(只讲核心表)
- 重点在架构图和核心流程
十、学习资源与练习
1️⃣ 推荐书籍
- 《System Design Interview》- Alex Xu()
- 《Designing Data-Intensive Applications》- Martin Kleppmann
- 《微服务架构设计模式》- Chris Richardson
2️⃣ 在线资源
- System Design Primer - GitHub 170k stars
- Grokking the System Design Interview
- ByteByteGo - Alex Xu的系统设计课程
3️⃣ 练习方法
阶段1:学习方法论(1周)
- 通读本章,理解完整流程
- 记住常用数字(QPS、存储、带宽)
- 学习画架构图的标准符号
阶段2:刷经典案例(2-4周)
- 每天1个案例,从简单到复杂
- 先看题目,自己设计30分钟
- 再看答案,对比差距
- 重点案例多刷几遍
阶段3:模拟面试(1-2周)
- 找朋友模拟面试,限时45分钟
- 录屏回放,找出问题
- 重点训练讲解能力