系统设计问题
分步指南
步骤 1:明确需求
让我们用一个设计类似 Twitter 服务的实际例子来扩展一下。在进入下一步之前,需要先回答以下几个 Twitter 设计问题:
- 我们服务的用户是否可以发布推文并关注其他人?
- 我们是否还应该设计创建和显示用户的时间线?
- 推文会包含照片和视频吗?
- 我们是否只关注后端,还是也开发前端?
- 用户能够搜索推文吗?
- 我们需要展示热门趋势话题吗?
- 会有新的(或重要的)推文推送通知吗?
第 2 步:粗略估计
- 该系统的预期规模是多少(例如,新推文的数量、推文浏览量、每秒时间线生成的数量等)?
- 我们需要多少存储空间?如果用户推文中可以包含照片和视频,我们的存储空间需求会有所不同。
- 我们预计的网络带宽使用量是多少?这对于决定我们如何管理流量以及平衡服务器之间的负载至关重要。
步骤3:系统接口定义
定义系统需要哪些 API。这将建立系统所需的确切契约,并确保我们没有弄错任何需求。
postTweet(user_id, tweet_data, tweet_location, user_location, timestamp, …)
generateTimeline(user_id, current_time, user_location, …)
markTweetFavorite(user_id, tweet_id, timestamp, …)
步骤4:定义数据模型
用户:用户 ID、姓名、电子邮件、出生日期、创建日期、上次登录等。 推文:推文 ID、内容、推文位置、点赞数、时间戳等。 用户关注:用户 ID1、用户 ID2 收藏推文:用户 ID、推文 ID、时间戳
第五步:高层设计
对于 Twitter,从总体上讲,我们需要多台应用服务器来处理所有读/写请求,并在它们前面部署负载均衡器来分配流量。如果我们假设读取流量远高于写入流量,我们可以决定使用单独的服务器来处理这些场景。在后端,我们需要一个高效的数据库,能够存储所有推文并支持大量的读取操作。我们还需要一个分布式文件存储系统来存储照片和视频。
步骤6:详细设计
- 由于我们将存储大量数据,我们应该如何分区数据以将其分发到多个数据库?我们应该尝试将一个用户的所有数据存储在同一个数据库中吗?这会导致什么问题?
- 我们将如何处理那些发布大量推文或关注大量用户的热门用户?
- 由于用户的时间线将包含最新(和相关)的推文,我们是否应该尝试存储我们的数据,以便优化扫描最新推文?
- 我们应该在哪一层引入多少缓存来加快速度?
- 哪些组件需要更好的负载平衡?
步骤 7:识别并解决瓶颈
- 我们的系统是否存在单点故障?我们正在采取什么措施来缓解它?
- 我们是否拥有足够的数据副本,以便在丢失几台服务器的情况下仍能为用户提供服务?
- 同样,我们是否有足够的不同服务副本在运行,以至于少数故障不会导致整个系统关闭?
- 我们如何监控服务的性能?关键组件发生故障或性能下降时,我们会收到警报吗?
CDN(拓展)
解释:CDN 就是一个全球连锁的“快递驿站”网络,它把你网站的“货”(图片、视频)提前放到离用户最近的驿站,让用户可以“就近取货”,从而实现秒开。
三大核心技术:
- 智能 DNS (请求路由) :根据用户 IP,返回最近、最快的边缘节点 IP 地址。
- 反向代理缓存 (内容缓存) :边缘节点缓存源站内容,后续请求直接由边缘节点响应,无需回源。
- 全球负载均衡 (内容分发) :在全球部署大量节点,协同工作,共同承担访问压力。
后端开发者注意事项
-
动静分离:
- 动态请求 (个性化数据) -> 自己处理 (Go API)
- 静态资源 (图片/视频/JS/CSS) -> 交给 CDN
- 具体做法:API 返回的资源 URL 必须是 CDN 的域名,而不是自己服务器的。
-
指挥缓存 (HTTP Headers) :
- 通过在 Go 代码中设置 Cache-Control 等 HTTP 响应头,来精确地告诉 CDN 这个内容可以缓存多久。
-
主动管理 (API 调用) :
- 当内容更新时,使用 Go 代码调用 CDN 服务商提供的 API,来主动刷新 (Purge) 或 预热 (Prefetch) 缓存,保证内容的时效性
系统设计主模板
社交媒体的一致性不用那么强
总结:
| 组件 | 核心作用 | 回答什么问题? | 数据形态 | 典型工具 |
|---|---|---|---|---|
| Coordination | 指挥与协调 | “我们该听谁的?” | 结构化的键值对 | Zookeeper, etcd |
| Logging | 事后排查 | “刚才发生了什么?” | 非结构化的文本 | ELK, Loki |
| Tracing | 性能分析 | “这个请求慢在哪了?” | 带有父子关系的 Span | Jaeger, Zipkin |
| Monitoring | 实时告警 | “系统现在还好吗?” | 带标签的时间序列数字 | Prometheus, VictoriaMetrics |
一个典型的线上问题排查流程:
- Monitoring 的警报响了:“API 错误率在下午3点突然从 0.1% 飙升到 15%!”
- 运维人员打开 Tracing 系统,找到大量出错请求的调用链,发现大部分请求都卡在了“订单服务”上,延迟高达5秒。
- 工程师立刻切换到 Logging 系统,筛选出“订单服务”在下午3点左右的所有 error 级别的日志。
- 日志显示大量的“数据库连接超时”错误。
- 最终,工程师可能会去 Coordination 服务 (etcd) 检查数据库的配置信息是否被错误地修改了。
名词解释
1.域名系统(DNS)
在 Web 浏览器中输入域名时,DNS 负责查找对应的 IP 地址,并将请求转发到相应的服务器
2.负载均衡器
将传入的网络流量分配到多台服务器。
3. API网关
API 网关充当服务器或服务,充当外部客户端与应用程序内部微服务或基于 API 的后端服务之间的中介
4. CDN
内容分发网络 (CDN) 是一个分布式服务器网络,用于存储内容(例如图片、视频、样式表和脚本),并将其分发给地理位置较近的用户
5.正向代理与反向代理
6.缓存
可发生在客户端、DNS、CDN、负载均衡器、API 网关、服务器、数据库等
7.数据分区
- 水平分区(分片)指将表的行划分为更小的表,等价分区
- 垂直分区涉及将表的列拆分为单独的表(某一列拎出来)
8.数据库复制
用于在不同服务器或位置维护同一数据库的多个副本的方法
9.分布式消息系统
通过解耦发送方和接收方组件来促进通信,使它们能够独立开发和运行(Apache Kafka 和 RabbitMQ)
10.微服务
应用程序被组织成由小型、松耦合且可自主部署的服务组成的集合
11. NoSQL 数据库
- 基于文档的数据库(MongoDB):以类似文档的结构(例如 JSON 或 BSON)存储数据
- 键值对:键充当唯一标识符,值保存关联数据(Redis 和 Amazon DynamoDB)
- 列族:处理写入密集型工作负载(Apache Cassandra 和 HBase)
- 基于图的数据库:专为存储和查询具有复杂关系和互连结构的数据,例如社交网络或推荐系统(Neo4j 和 Amazon Neptune)
12.数据库索引
数据库索引是一种数据结构,可以提高数据库内查询操作的速度和效率,B 树索引是最常用的类型。
权衡:额外的空间 + 降低写入性能(写索引)
13.分布式文件系统
旨在管理和授予跨多个服务器、节点或机器(通常分布在网络中)的文件和目录的访问权限
14.通知系统
用于向用户发送通知或警报,例如电子邮件、推送通知或短信
15.全文搜索
允许用户在应用程序或网站内搜索特定的单词或短语(Elastic Search)
就是通过 “空间换时间” 的策略,预先建立一张 “关键词 -> 文档列表” 的倒排索引。当用户搜索时,搜索引擎不再是去原文里大海捞针,而是通过查这张索引表查找
16.分布式协调服务
管理并同步分布式应用程序、服务或节点操作的系统
Apache ZooKeeper、etcd 和 Consul
17.心跳
每个服务器都会定期向中央监控服务器或系统中的其他服务器发送心跳消息,以表明它仍然活跃并且正在运行
18. 校验和
分布式系统要确保数据的完整性,从而确保客户端收到的是错误数据而不是损坏的数据。
MD5要计算校验和,需要使用诸如、SHA-1、SHA-256或 之类的加密哈希函数SHA-512。