系统设计面试的综合参考,涵盖核心概念、模式和常见问题。
目录
可扩展性基础
可扩展性是系统通过增加资源来处理更大负载的能力。
| 方面 | 垂直扩展 | 水平扩展 |
|---|
| 方法 | 给单台机器增加 CPU/RAM | 增加更多机器 |
| 限制 | 硬件上限 | 实际上无限 |
| 成本 | 每单位指数增长 | 每单位线性增长 |
| 复杂度 | 简单 | 需要分布式逻辑 |
| 停机时间 | 通常需要 | 可实现零停机 |
| 单点故障 | 是 | 否 |
关键原则
- 无状态 - 保持应用服务器无状态,使任何请求可以到达任何服务器。
- 松耦合 - 服务应通过明确定义的接口通信。
- 冗余 - 在每一层消除单点故障。
- 异步处理 - 将长时间运行的任务卸载到后台工作进程。
按层扩展
| 层 | 扩展策略 |
|---|
| Client | CDN、浏览器缓存、压缩 |
| Web tier | 负载均衡器 + 无状态应用服务器 |
| Application tier | 水平扩展、微服务 |
| Database | 读副本、分片、缓存 |
| Storage | 分布式文件系统、对象存储 |
负载均衡
负载均衡器将传入流量分配到多台服务器。
负载均衡算法
| 算法 | 描述 | 最适合 |
|---|
| Round Robin | 按顺序分配请求 | 均匀服务器 |
| Weighted Round Robin | 按权重比例分配 | 混合容量服务器 |
| Least Connections | 路由到活跃连接最少的服务器 | 变化请求时长 |
| IP Hash | 基于客户端 IP 路由 | 会话亲和 |
| Least Response Time | 路由到响应最快的服务器 | 延迟敏感应用 |
| Random | 随机选择服务器 | 简单无状态设置 |
负载均衡器类型
- Layer 4 (传输层) - 基于 IP 和端口路由。快速,无内容检查。示例:AWS NLB、HAProxy (TCP 模式)。
- Layer 7 (应用层) - 基于 HTTP 头、URL 路径、Cookie 路由。更灵活。示例:AWS ALB、Nginx、Envoy。
健康检查
负载均衡器定期检查服务器健康状况。不健康的服务器从池中移除。
| 检查类型 | 方法 | 间隔 |
|---|
| Active | LB 发送探测请求 | 每 5-30 秒 |
| Passive | LB 监控真实流量错误 | 持续 |
| Deep | 检查下游依赖 | 每 30-60 秒 |
缓存策略
缓存将频繁访问的数据存储在更靠近消费者的位置,以减少延迟和后端负载。
缓存拓扑
| 拓扑 | 描述 | 优点 | 缺点 |
|---|
| Local cache | 进程内内存 | 最快,无网络 | 大小有限,不共享 |
| Distributed cache | 独立集群(Redis, Memcached) | 共享,可扩展 | 网络开销 |
| CDN cache | 全球边缘位置 | 靠近用户 | 仅静态内容 |
缓存模式
| 模式 | 工作原理 | 使用场景 |
|---|
| Cache-Aside | 应用先检查缓存,未命中时从 DB 加载 | 通用 |
| Write-Through | 同时写入缓存和 DB | 一致性关键 |
| Write-Behind | 写入缓存,异步刷新到 DB | 写入密集 |
| Read-Through | 缓存未命中时自动从 DB 加载 | 简化应用逻辑 |
| Refresh-Ahead | 在过期前主动刷新 | 可预测的访问模式 |
缓存淘汰策略
| 策略 | 描述 |
|---|
| LRU | 最近最少使用 - 淘汰最早访问的项 |
| LFU | 最不常用 - 淘汰最不受欢迎的项 |
| FIFO | 先进先出 - 淘汰最早的项 |
| TTL | 生存时间 - 固定时长后淘汰 |
缓存失效挑战
- 惊群效应 - 缓存未命中时大量请求同时到达 DB。解决方案:锁定或提前重算。
- 缓存踩踏 - 热门 Key 过期导致 DB 过载。解决方案:概率性过期。
- 过时数据 - 缓存持有过期值。解决方案:短 TTL 或事件驱动失效。
数据库类型
具有 ACID 事务的结构化数据。示例:PostgreSQL、MySQL、Oracle。
| 功能 | 描述 |
|---|
| Schema | 固定的预定义结构 |
| Query language | SQL |
| Transactions | 完全 ACID 支持 |
| Scaling | 垂直为主,读副本用于水平 |
| Best for | 结构化数据、复杂查询、关系 |
| 类型 | 示例 | 最适合 | 数据模型 |
|---|
| Key-Value | Redis, DynamoDB | 缓存、会话存储 | Key 映射到 Value |
| Document | MongoDB, CouchDB | 灵活 Schema、内容 | 类 JSON 文档 |
| Column-Family | Cassandra, HBase | 时间序列、高写入吞吐 | 按行分组的列 |
| Graph | Neo4j, Amazon Neptune | 关系、推荐 | 节点和边 |
数据库扩展
| 策略 | 描述 | 权衡 |
|---|
| Read replicas | 将数据复制到多个只读实例 | 最终一致性 |
| Sharding | 将数据拆分到多个数据库 | 跨分片复杂查询 |
| Federation | 按功能拆分(用户 DB、产品 DB) | 跨功能查询 |
| Denormalization | 复制数据以减少 JOIN | 数据一致性开销 |
| SQL tuning | 索引、查询优化 | 收益递减 |
消息队列
消息队列实现服务间的异步通信。
关键概念
| 概念 | 描述 |
|---|
| Producer | 向队列发送消息 |
| Consumer | 读取和处理消息 |
| Queue | 存储消息的缓冲区 |
| Topic | 发布/订阅消息的命名通道 |
| Acknowledgment | 消费者确认成功处理 |
常见消息队列系统
| 系统 | 类型 | 优势 |
|---|
| Apache Kafka | 分布式日志 | 高吞吐、可重放、持久 |
| RabbitMQ | 传统代理 | 灵活路由、成熟 |
| Amazon SQS | 托管队列 | 完全托管、简单 |
| Redis Streams | 轻量流 | 低延迟、简单设置 |
何时使用消息队列
- 解耦 - 服务无直接依赖通信。
- 缓冲 - 平滑流量峰值。
- 异步处理 - 卸载慢操作(邮件发送、图片处理)。
- 扇出 - 将同一消息传递给多个消费者。
- 排序 - 保证处理顺序(Kafka 分区)。
投递保证
| 保证 | 描述 | 实现 |
|---|
| At-most-once | 消息投递零次或一次 | 发送即忘 |
| At-least-once | 消息投递一次或多次 | 带确认的重试 |
| Exactly-once | 消息恰好投递一次 | 幂等消费者 + 事务 |
微服务
一种应用由小型、独立可部署服务组成的架构。
| 方面 | 单体 | 微服务 |
|---|
| Deployment | 单一单元 | 每服务独立 |
| Scaling | 扩展所有 | 按服务扩展 |
| Technology | 单一栈 | 可多语言 |
| Complexity | 初期简单 | 规模化时复杂 |
| Team structure | 共享代码库 | 服务所有权 |
| Failure isolation | 级联故障 | 隔离故障 |
通信模式
| 模式 | 协议 | 使用场景 |
|---|
| Synchronous | HTTP/REST, gRPC | 实时请求/响应 |
| Asynchronous | 消息队列、事件 | 发送即忘、事件驱动 |
| Service mesh | Envoy, Istio | 横切关注点(mTLS、可观测性) |
关键挑战
- 服务发现 - 服务如何找到彼此。工具:Consul、etcd、Kubernetes DNS。
- 分布式事务 - 多服务操作的 Saga 模式。
- 可观测性 - 分布式追踪(Jaeger)、集中日志(ELK)、指标(Prometheus)。
- 数据一致性 - 通过事件源或 Outbox 模式实现最终一致性。
- 配置管理 - 集中配置服务器或基于环境的配置。
分布式系统最多只能保证三个属性中的两个。
三个属性
| 属性 | 含义 |
|---|
| Consistency | 每次读取都收到最近的写入 |
| Availability | 每个请求都收到响应 |
| Partition Tolerance | 系统在网络分区时继续运行 |
常见组合
| 选择 | 权衡 | 示例 |
|---|
| CP | 牺牲可用性换取一致性 | HBase, MongoDB(强一致性模式) |
| AP | 牺牲一致性换取可用性 | Cassandra, DynamoDB |
| CA | 在分布式系统中不现实 | 单节点数据库 |
如果分区 (P):在可用性 (A) 和一致性 (C) 之间选择。
否则 (E):在延迟 (L) 和一致性 (C) 之间选择。
| 系统 | 分区 | 正常 |
|---|
| Cassandra | AP | EL |
| MongoDB | CP | EC |
| DynamoDB | AP | EL |
一致性哈希
一种在集群中分配数据的技术,当节点变化时重新分配最小化。
工作原理
- 将服务器和 Key 映射到哈希环(0 到 232 - 1)。
- 每个 Key 分配到环上顺时针方向的下一个服务器。
- 当服务器添加或移除时,仅相邻 Key 被重新映射。
虚拟节点
为避免不均匀分布,每台物理服务器在环上获得多个虚拟位置。
| 物理服务器 | 虚拟节点 | 分布方差 |
|---|
| 3 | 每台 1 个 | 高 |
| 3 | 每台 100 个 | 低 |
| 3 | 每台 200 个 | 非常低 |
优势
| 优势 | 描述 |
|---|
| 最小重分配 | 节点变化时平均仅移动 K/n 个 Key |
| 可扩展性 | 易于添加/移除节点 |
| 负载均衡 | 虚拟节点确保均匀分布 |
使用场景
- 分布式缓存(Memcached, Redis Cluster)
- 数据库分片
- CDN 边缘服务器路由
- 分布式哈希表
速率限制
控制客户端在时间窗口内可以发出的请求数量。
算法
| 算法 | 描述 | 优点 | 缺点 |
|---|
| Token Bucket | 以固定速率添加 Token,每次请求消耗 | 允许突发 | 突发流量 |
| Leaky Bucket | 以固定速率处理请求 | 平滑输出 | 无突发容忍 |
| Fixed Window | 在固定时间窗口计数请求 | 简单 | 边界突发问题 |
| Sliding Window Log | 记录时间戳,在滚动窗口内计数 | 准确 | 内存密集 |
| Sliding Window Counter | 当前和前一窗口的加权计数 | 准确、高效 | 轻微近似 |
速率限制响应
| HTTP Code | 含义 | 使用时机 |
|---|
| 429 | Too Many Requests | 客户端超出限制 |
| Retry-After header | 距下次允许请求的秒数 | 告知客户端等待时间 |
分布式速率限制
| 方法 | 描述 | 权衡 |
|---|
| Centralized counter (Redis) | 单一事实来源 | 单点故障 |
| Local + sync | 每节点本地跟踪,定期同步 | 轻微不准确 |
| Sticky sessions | 将客户端路由到同一节点 | 减少可用性 |
速率限制头
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1623456789
内容分发网络在全球边缘位置缓存内容以减少延迟。
- 用户从源服务器请求内容。
- DNS 解析到最近的边缘服务器。
- 边缘服务器检查其缓存。
- 缓存命中时,直接提供内容。
- 缓存未命中时,边缘从源获取,缓存后提供。
| 类型 | 描述 | 使用场景 |
|---|
| Push CDN | 您上传内容到 CDN | 大文件、不频繁更新 |
| Pull CDN | 首次请求时 CDN 从源获取 | 动态内容、简单设置 |
| 策略 | 描述 |
|---|
| Cache-Control headers | max-age, s-maxage, no-cache 指令 |
| Versioned URLs | /app.v2.js 在部署时强制缓存刷新 |
| Purge | 手动失效缓存内容 |
| Stale-while-revalidate | 提供旧内容,后台刷新 |
优势
| 优势 | 影响 |
|---|
| 减少延迟 | 从最近的边缘提供内容 |
| 减少源负载 | 大多数请求在边缘处理 |
| DDoS 保护 | 流量分布在边缘 |
| 高可用 | 冗余边缘服务器 |
| 实践 | 示例 |
|---|
| 使用名词作为资源 | GET /users 而非 GET/getUsers |
| HTTP 方法映射到 CRUD | GET=读取, POST=创建, PUT=更新, DELETE=删除 |
| 版本化 API | /api/v1/users |
| 一致命名 | /users/{id}/posts 用于嵌套资源 |
| 分页 | ?page=2&limit=20 |
| 筛选 | ?status=active&sort=created_at |
| Code | 含义 | 使用时机 |
|---|
| 200 | OK | 成功读取/更新 |
| 201 | Created | 成功创建资源 |
| 204 | No Content | 成功删除 |
| 400 | Bad Request | 无效输入 |
| 401 | Unauthorized | 需要认证 |
| 403 | Forbidden | 权限不足 |
| 404 | Not Found | 资源不存在 |
| 409 | Conflict | 重复或冲突状态 |
| 429 | Too Many Requests | 超出速率限制 |
| 500 | Internal Server Error | 意外服务器故障 |
| 风格 | 协议 | 格式 | 最适合 |
|---|
| REST | HTTP | JSON | CRUD、Web API |
| GraphQL | HTTP | JSON | 复杂查询、移动端 |
| gRPC | HTTP/2 | Protobuf | 微服务、低延迟 |
| WebSocket | WS | Any | 实时、双向 |
错误响应格式
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Email is required",
"details": [
{ "field": "email", "issue": "missing required field" }
]
}
}
常见系统设计问题
需求:给定长 URL,生成短 URL。将短 URL 重定向到原始 URL。
| 组件 | 选择 | 原因 |
|---|
| Key 生成 | 自增 ID 的 Base62 编码 | 短、唯一、确定性 |
| Database | Key-Value 存储(DynamoDB) | 按短码简单查找 |
| Cache | Redis | 热门 URL 缓存以快速重定向 |
| Encoding | 哈希前 7 个字符 | 627 = ~3.5 万亿唯一 URL |
请求流程:
- 客户端发送长 URL 到 API 服务器。
- 服务器生成唯一短码(基于哈希或 ID)。
- 存储映射到数据库。
- 返回短 URL。
读取流程:
- 用户访问短 URL。
- 在缓存中查找短码,然后 DB。
- 返回 301 重定向到原始 URL。
容量估算(示例):
- 每月 1 亿 URL,10:1 读写比
- 存储:1 亿 * 500 字节 = 每月 50 GB
- 读取 QPS:~400 次/秒
聊天系统
需求:支持一对一和群组消息,带送达回执。
| 组件 | 技术 | 用途 |
|---|
| Protocol | WebSocket | 持久双向连接 |
| Message store | Cassandra | 写入密集、时间有序数据 |
| User sessions | Redis | 跟踪在线状态 |
| Message queue | Kafka | 可靠消息投递 |
| Notification | Push service (APNs/FCM) | 离线用户通知 |
关键设计决策:
| 决策 | 选择 | 权衡 |
|---|
| Message ordering | 每对话序列号 | 需要协调 |
| Group chat | 写入时扇出 | 写放大,读简单 |
| Delivery tracking | 每接收者状态 | 存储开销 |
| Message persistence | 追加日志 | 快写,不可变历史 |
连接管理:
- 每个用户维护与聊天服务器的 WebSocket 连接。
- 连接状态存储在 Redis(user -> server 映射)。
- 当用户 A 发送给用户 B 时,在 Redis 中查找 B 的服务器,转发消息。
新闻推送
需求:显示来自关注用户的帖子的个性化推送。
| 组件 | 技术 | 用途 |
|---|
| Post storage | MySQL/PostgreSQL | 结构化帖子数据 |
| Feed generation | Redis sorted sets | 每用户预计算推送 |
| Fan-out service | Async workers | 将帖子分发给关注者 |
| Ranking service | ML model | 个性化排序 |
| CDN | CloudFront/Cloudflare | 媒体内容分发 |
两种方法:
| 方法 | 工作原理 | 优点 | 缺点 |
|---|
| Fan-out on write | 推送到所有关注者的推送 | 快读 | 名人用户慢 |
| Fan-out on read | 按需计算推送 | 无写放大 | 慢读 |
| Hybrid | 普通用户写,名人读 | 均衡 | 实现复杂 |
推送生成管道:
- 用户发布帖子。
- 帖子存储在数据库。
- Fan-out 服务入队投递任务。
- 工作进程将帖子 ID 推送到每个关注者的推送(Redis sorted set)。
- 当关注者打开应用时,获取预计算的推送。
- Ranking service 按相关性重新排序。
关键优化:
- 限制每用户推送存储为最近 N 个帖子。
- 对大型推送使用候选生成 + 排名。
- 激进缓存热门推送。
- 仅为活跃用户预计算推送。
面试技巧
| 阶段 | 操作 | 时间 |
|---|
| Requirements | 澄清功能和非功能需求 | 5 分钟 |
| Estimation | 计算 QPS、存储、带宽 | 5 分钟 |
| High-level design | 画出主要组件和数据流 | 10 分钟 |
| Deep dive | 详细设计关键组件 | 15 分钟 |
| Tradeoffs | 讨论替代方案并证明选择 | 5 分钟 |
要问的问题
- 预期规模是多少?(用户、QPS、数据量)
- 延迟要求是什么?
- 一致性还是可用性更重要?
- 读写比是多少?
- 有什么具体约束(成本、技术)?