系统设计面试题学习笔记
通用模板
系统设计的面试题是一个开放式的对话,大体可以分成四个步骤:
第一步:明确需求
- 需求是无止境的,需要明确一个范畴,专注讨论范畴内的需求用例
- 目标用户是谁,用户规模大概多大
- 有哪些功能,核心功能的输入输出是什么
- 有哪些边界情况需要注意
- 预期的数据量和处理速度
第二步:设计架构
- 做一个高层次设计,画出核心组件的架构
- 用连线代表核心组件之间的交互
第三步:细化实现
- 具体到每个系统组件,提供具体的解决方案
- 演示所有功能的交互流程和解决方案
第四步:扩展设计
- 不用直接得出最终设计,通过 benchmark/load/profile 迭代分析瓶颈
- 可参考方案:DNS、CDN、负载均衡、水平扩展、反向代理、应用层、缓存、主从复制
- 论述可能的解决办法和代价,每件事情需要权衡利弊做出取舍
案例一:粘贴板分享平台
第一步:明确需求
我们将问题的范畴限定在如下用例:
- 用户:输入文本,保存后得到一个随机链接
- 默认不会过期
- 设置过期时间
- 用户:输入一个链接,可以查看分享的内容
- 服务:用户行为统计与分析,可以使用外部服务(Google Analytics)
- 服务:自动删除过期的内容
- 服务:高可用,冗余+自动故障转移
范畴之外的用例:
- 用户:可以注册、登录、查看历史记录
- 用户:可以设置可见性、过期时间
状态假设:
- 访问流量不是均匀分布的
- 打开一个短链接应该是很快的
- pastes 只能是文本
- 页面访问分析数据可以不用实时
- 一千万的用户量,每个月一千万的 paste 写入量,一亿的 paste 读取量,读写比例在 10:1
估算使用情况:
- 每个 paste 的大小,每条数据大约 2kb
- 每个月新的 paste 内容在 20GB
- 平均 4 paste/s 的写入频率
- 平均 40 paste/s 的读取频率
第二步:设计架构
可以先实现一个比较通用的架构:Client -> Web Server -> API -> SQL/Store -> Analytics

第三步:细化实现
- 用一个关系型数据库作为哈希表,用来把生成的 url 映射到一个包含 paste 文件的文件服务器和路径上
- 为了避免托管一个文件服务器,我们可以用一个托管的对象存储,比如 Amazon 的 S3
用例1:输入文本,保存后得到一个随机链接
- Client 发送一个创建 paste 的请求到反向代理
- 反向代理转发请求给写接口服务器
- 写接口服务器执行如下操作:
- 生成一个唯一的 url
- MD5 做哈希,Base62 做编码
- 检查这个 url 在 SQL 数据库 里面是否是唯一的
- 如果这个 url 不是唯一的,生成另外一个 url
- 如果我们支持自定义 url,我们可以使用用户提供的 url(也需要检查是否重复)
- 把生成的 url 存储到 SQL 数据库 的 pastes 表里面
- 存储 paste 的内容数据到 对象存储 里面
- 返回生成的 url
- 生成一个唯一的 url
用例2:输入一个链接,可以查看分享的内容
- Client 发送一个获取 paste 的请求到反向代理
- 反向代理转发请求给读接口服务器
- 读接口服务器执行如下操作:
- 在 SQL 数据库 检查这个生成的 url
- 如果这个 url 在 SQL 数据库 里面,则从 对象存储 获取这个 paste 的内容
- 否则,返回一个错误页面给用户
用例3:用户行为统计与分析
非实时分析的功能可以通过 MapReduce 之类的服务来计算点击率之类的数据
第四步:扩展设计
- 迭代地执行这样的操作:
- Benchmark/Load 测试
- Profile 出瓶颈
- 在评估替代方案和权衡时解决瓶颈
- 重复前面
- 重要的是讨论在初始设计中可能遇到的瓶颈,以及如何解决每个瓶颈

上面给出的方案中,主要是优化了以下几个方面:
- DNS:根据用户所在地址分配到不同的服务器上
- CDN:内容分发加速,客户端访问速度更快
- Load Balancer:反向代理层做负载均衡,缓解单个 web server 的访问压力
- Memory Cache:高频热数据放在内存缓存中,缓解数据库查询压力
- SQL Write Master-Slave & Read Replicas:主从分离提高数据安全性和可用性,slave 上做查询可以缓解生产服务器访问压力过大的问题。
- SQL Analytics:分析 SQL 的执行耗时,优化应用层的 SQL 语句性能
附录一:如何实现高可用
方法论上,高可用是通过冗余+自动故障转移来实现的。
整个互联网分层系统架构的高可用,又是通过每一层的冗余+自动故障转移来综合实现的,具体的:
(1)【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余实现的,常见实践是keepalived + virtual IP自动故障转移
(2)【反向代理层】到【站点层】的高可用,是通过站点层的冗余实现的,常见实践是 nginx 与 web-server 之间的存活性探测与自动故障转移
(3)【站点层】到【服务层】的高可用,是通过服务层的冗余实现的,常见实践是通过 service-connection-pool 来保证自动故障转移
(4)【服务层】到【缓存层】的高可用,是通过缓存数据的冗余实现的,常见实践是缓存客户端双读双写,或者利用缓存集群的主从数据同步与 sentinel 保活与自动故障转移;更多的业务场景,对缓存没有高可用要求,可以使用缓存服务化来对调用方屏蔽底层复杂性
(5)【服务层】到【数据库读】的高可用,是通过读库的冗余实现的,常见实践是通过 db-connection-pool 来保证自动故障转移
(6)【服务层】到【数据库写】的高可用,是通过写库的冗余实现的,常见实践是 keepalived + virtual IP 自动故障转移
附录二:常用的优化方案和原理
比如,在多个 Web 服务器上添加 负载平衡器可以解决哪些问题? CDN 解决哪些问题?Master-Slave Replicas 解决哪些问题? 替代方案是什么?怎么对每一个替代方案进行权衡比较?
- DNS
- CDN
- 负载均衡器
- 水平扩展
- 反向代理(web 服务器)
- 应用层
- 缓存
- 关系型数据库管理系统 (RDBMS)
- SQL write master-slave failover
- 主从复制
- 一致性模式
- 可用性模式
参考资料: