系统设计方法论

一个面试现场的崩溃

候选人小王被问到:"设计一个短链系统。"

他深吸一口气,直接开始画架构图,嘴里念念有词: "用 Redis 做缓存...用 MySQL 存数据...用消息队列做异步处理..."

画了 5 分钟,面试官打断他:"你还没告诉我为什么要用 Redis,也没告诉我需要支持多少并发。"

小王愣住了。

他开始东拼西凑:"嗯...10万 QPS?Redis 应该够了..."

面试官继续追问:"Redis 挂了怎么办?数据怎么保证不丢?"

小王的额头开始冒汗。

这个场景暴露了系统设计面试中最常见的问题:没有方法论,想到哪说到哪。

系统设计不是"背方案",而是一套分析和决策的方法


二、系统设计四步法🔴

2.1 第一步:需求澄清(Scope Definition)

核心问题:这个系统要做什么?不做什么?

澄清清单:
□ 核心功能是什么?
□ 系统需要服务多少用户?
□ 预期的 QPS/TPS 是多少?
□ 需要支持多长的 URL?
□ 短链有效期是多久?
□ 需要支持自定义短链吗?
□ 需要统计点击量吗?

必须问清楚的问题(以短链系统为例):

问题回答方向
DAU 是多少?100万 vs 1亿,决定架构规模
点击率(CTR)是多少?决定读/写比例
平均 URL 长度?决定存储设计
需要支持国际域名吗?影响 ID 生成策略
过期策略是什么?影响存储清理

2.2 第二步:高层设计(High-Level Design)

核心问题:系统有哪些组件?它们如何交互?

高层设计要点:
□ 主要 API 设计(接口清单)
□ 数据模型(核心实体)
□ 服务划分(微服务?单体?)
□ 存储选型(数据库类型)
□ 流量入口(负载均衡策略)

以短链系统为例:

用户请求短链

DNS + CDN(全球加速)

API Gateway(统一入口、限流、鉴权)

┌─────────────────────────────────────┐
│            ShortURL Service          │
│  ┌──────────┐      ┌──────────┐     │
│  │ Redirect │      │  Create  │     │
│  │ Service  │      │ Service  │     │
│  └────┬─────┘      └────┬─────┘     │
│       │                 │           │
│  ┌────▼─────┐      ┌────▼─────┐     │
│  │  Redis   │      │   MySQL  │     │
│  │  Cache   │      │ 持久存储 │     │
│  └──────────┘      └──────────┘     │
└─────────────────────────────────────┘

2.3 第三步:核心细节设计(Detailed Design)

核心问题:每个组件的内部实现是什么?

核心设计点:
□ ID 生成算法(如何生成不重复的短链 ID?)
□ 哈希算法(如何从长链计算短链?)
□ 存储结构(MySQL 表结构设计)
□ 缓存策略(Redis 缓存哪些数据?)
□ 可用性设计(如何做故障转移?)
□ 扩展性设计(如何应对流量峰值?)

2.4 第四步:权衡与扩展(Trade-offs and Scalability)

核心问题:这个设计有什么局限?如何改进?

权衡讨论:
□ 一致性 vs 可用性(CP vs AP)
□ 延迟 vs 吞吐量(低延迟 vs 高吞吐)
□ 复杂度 vs 可维护性(过度设计 vs 欠设计)
□ 成本 vs 性能(资源投入 vs 系统表现)

扩展方向:
□ 容量规划(数据量、存储量、QPS)
□ 监控告警(哪些指标需要监控?)
□ 降级策略(哪些功能可以降级?)
□ 容灾方案(单点故障如何避免?)

三、功能优先级框架🔴

3.1 MVP vs 完整系统

面试中的核心问题:先做 MVP,还是直接设计完整系统?

推荐策略:先 MVP,面试官满意后再扩展。

MVP(最短路径):
1. 短链生成(ID 生成 + 存储)
2. 短链跳转(查询 + 重定向)

扩展(根据面试官要求):
3. 自定义短链
4. 点击统计
5. 过期时间
6. 多租户隔离
7. 全球分布

3.2 YAGNI 原则

You Aren't Gonna Need It:不要为可能永远不会来的需求提前付出代价。

// ❌ 过早优化:支持无限多的用户
class URLShortener {
    // 预留了 10 亿用户容量,复杂度爆炸
    private static final int USER_CAPACITY = 1_000_000_000;
}

// ✅ YAGNI:先用简单方案
class URLShortener {
    // 先支持 1000 万用户,不够再扩展
    private static final int USER_CAPACITY = 10_000_000;
}

四、常见权衡模式🟡

4.1 CAP 理论权衡

系统设计中的 CAP 选择:

CP 系统(一致性 + 分区容忍):
- Zookeeper
- etcd
- HBase
- MySQL 强一致性

AP 系统(可用性 + 分区容忍):
- Cassandra
- DynamoDB
- Redis 主从(最终一致)
- DNS

面试中的决策:
- 金融交易系统 → CP
- 社交Feed系统 → AP
- 电商下单系统 → CP(但可以局部 AP)
- 缓存查询系统 → AP

4.2 一致性级别选择

一致性级别适用场景实现成本
强一致性账户余额、库存
最终一致性社交点赞、阅读数
因果一致性评论系统
读己之所写用户自己的数据

4.3 延迟 vs 吞吐量

低延迟场景(< 10ms):
- 用户查询自己的数据 → 缓存优先
- 实时推荐 → 本地缓存
- 支付回调 → 同步处理

高吞吐场景(> 10万 QPS):
- 日志收集 → 批量处理
- 数据统计 → 异步聚合
- 文件上传 → 流式处理

【架构权衡】 没有免费的午餐。选择低延迟意味着更多的资源投入(如 L1/L2/L3 多级缓存),选择高吞吐意味着更长的处理周期(如批量处理)。系统设计的关键是找到业务需求和工程成本的最佳平衡点


五、数据估算框架🔴

5.1 容量估算

容量估算公式:

存储量 = 用户数 × 平均数据量 × 保留时间
QPS = DAU × 人均请求数 / 86400秒
带宽 = QPS × 平均请求大小

短链系统估算示例

指标估算
DAU1 亿
每天新增短链1 亿 × 10% = 1000 万
每天点击量1000 万 × 100 点击 = 10 亿
峰值 QPS(点击)10亿 / 86400 × 10(峰值系数)≈ 12000 QPS
平均数据量64 字节(短链)+ 256 字节(长链)= 320 字节
每天存储增量1000万 × 320 字节 = 3.2 GB
一年存储量3.2 GB × 365 ≈ 1.2 TB
Redis 缓存(热数据)1 亿 × 10% × 320 字节 = 32 GB

5.2 资源规划

// 根据 QPS 估算服务器数量

估算参数:
- 单机处理能力:2000 QPS
- 峰值系数:5倍
- 可用率:70%(高峰期利用率)

服务器数量 = 峰值QPS / 单机能力 / 可用率
         = 12000 / 2000 / 0.7
9

结论:至少需要 9 台服务器

六、面试常见陷阱🟡

6.1 陷阱一:不澄清需求就动手

// ❌ 直接开始画架构
public class URLShortener {
    // ...
}

// ✅ 先问清楚
/**
 * 澄清问题:
 * 1. DAU 是多少?
 * 2. 点击率是多少?
 * 3. 需要支持自定义短链吗?
 * 4. 有效期是多长?
 */

6.2 陷阱二:过早优化

// ❌ 面试开始就说
"我们用三机房容灾、跨地域同步、TiDB 分布式数据库..."

// ✅ 先说简单方案,再根据要求扩展
"第一版我先用 MySQL + Redis 缓存,如果数据量超过 X,再考虑分库分表。"

6.3 陷阱三:忽视可扩展性

// ❌ 固定写死
class URLShortener {
    private static final int MAX_SHORT_URL = 62^6; // 560 亿
    // 560 亿肯定够用...但数据库能存这么多吗?
}

// ✅ 可扩展设计
class URLShortener {
    // 根据数据量动态扩容
    // 初期 6 位,容量不够时切换到 7 位
}

6.4 陷阱四:不讨论失败场景

面试官最喜欢追问:"如果 Redis 挂了怎么办?"

失败场景讨论清单:
□ 单点故障(如何避免?)
□ 服务过载(如何限流?)
□ 数据库故障(如何降级?)
□ 网络分区(如何处理?)
□ 数据不一致(如何修复?)

七、方案对比框架

7.1 评估维度

维度说明
功能完整性能否满足所有需求?
可用性SLA 是多少?
性能延迟、吞吐如何?
成本开发、维护、资源成本
可扩展性未来如何演进?
复杂度实现难度如何?

7.2 方案选择决策树

性能要求 < 1000 QPS?
    ├── 是 → 单机 + 简单缓存
    └── 否 → 需要分布式

            数据量 < 1TB?
            ├── 是 → 单库 + 缓存
            └── 否 → 需要分库分表

                    一致性要求高?
                    ├── 是 → 强一致性方案
                    └── 否 → 最终一致方案

八、面试话术模板

8.1 开场白

"好的,让我先澄清一下需求..."
"在开始设计之前,我想确认几个问题..."

8.2 方案陈述

"我计划分三个阶段设计..."
"第一阶段我们先做 MVP,满足核心功能..."
"如果这个版本运行良好,可以根据需要扩展到..."

8.3 权衡讨论

"这里有一个权衡点..."
"如果我们追求强一致性,就需要牺牲一些可用性..."
"考虑到 XX 场景,我认为 XX 方案更合适,因为..."

8.4 被追问时

"这是一个很好的问题..."
"这个问题我之前的设计中有所考虑..."
"如果遇到这个情况,我的方案是..."

九、实战:短链系统设计

9.1 需求澄清

✅ 确认:
- DAU: 1亿
- 每天新增短链: 1000万
- 点击量: 每天10亿次
- 峰值QPS: 约12万
- 短链格式: 6位62进制(a-zA-Z0-9)
- 有效期: 永久

❌ 排除:
- 自定义短链(第二阶段)
- 短链过期(暂不考虑)
- 点击分析(第二阶段)

9.2 高层设计

API:
- POST /shorten  创建短链
- GET  /{shortUrl}  跳转

存储:
- MySQL: 短链详情
- Redis: 热点数据缓存
- KV存储: 高性能查询

组件:
- API Gateway
- ShortURL Service
- Redis Cache
- MySQL

9.3 核心设计

ID生成:
- 方案: 分布式ID生成器(雪花算法)
- 62进制编码 → 短链

缓存策略:
- 热点数据: Redis L1 (100万热点)
- 兜底: MySQL L2

跳转优化:
- 302 重定向(省服务器资源)
- 预热热点数据

9.4 可扩展性

容量不够时:
1. Redis 扩容
2. MySQL 分库分表
3. 增加短链位数(6→7→8)
4. 多活部署