Grokking-System-Design
  • 译者
  • 0. 系统设计面试指南
  • 1. 设计类似TinyURL的短链服务
  • 2. 设计 Pastebin
  • 3. 设计Instagram
  • 4. 设计Dropbox
  • 5. 设计Facebook Messager
  • 6. 设计 Twitter
  • 7. 设计YouTube或Netflix
  • 8. 设计输入提示建议
  • 9. 设计API限流器
  • 10. 设计 Twitter 搜索
  • 11. 设计网络爬虫
  • 12. 设计 Facebook 的新闻信息流
  • 13. 设计 Yelp 或附近的朋友
  • 14. 设计 Uber 后端
  • 15. 设计票务大师
  • 16. 附加资源
  • 17. 分布式系统的核心特征
  • 18. 负载均衡
  • 19. 缓存
  • 20. 分片或数据分块
  • 21. 索引
  • 22. 代理
  • 23. 冗余和复制
  • 24. SQL 与 NoSQL
  • 25. CAP 理论
  • 26. 一致性哈希
  • 27. 长轮询 / WebSockets / 服务器发送事件
Powered by GitBook
On this page
  • 1. 什么是 Pastebin?
  • 2. 系统的要求和目标
  • 功能性要求
  • 非功能性要求
  • 扩展性要求
  • 3. 设计需要考虑的方面
  • 4. 容量估算和限制条件
  • 5. 系统 API
  • 6. 数据库设计
  • 7. 高阶设计
  • 8. 组件设计
  • a. 应用层
  • b. 数据存储层
  • 9. 清洗或数据库清理
  • 10. 数据分块和备份
  • 11. 缓存和负载均衡
  • 12. 安全和许可

2. 设计 Pastebin

难度等级:简单

让我们设计一个类 Pastebin 网络服务,用户可以存储纯文本。此服务的用户可以输入文本内容并得到一个随机生成的 URL 用于访问该文本内容。类似服务:pastebin.com,pasted.co,chopapp.com。

1. 什么是 Pastebin?

类 Pastebin 服务允许用户在网络(通常是因特网)上存储纯文本或图像,并生成唯一的 URL 用于访问上传的数据。这样的服务也用于在网络上快速共享数据,用户只需要发送 URL 让其他用户看到。

如果你之前没有用过 pastebin.com,请尝试在那里创建一个新的「粘贴」并花一些时间体验该服务提供的不同选项。这对你理解这一章有很大帮助。

2. 系统的要求和目标

Pastebin 服务应满足以下要求。

功能性要求

  1. 用户可以上传或「粘贴」数据并得到唯一的 URL 用于访问该数据。

  2. 用户只能上传文本。

  3. 数据和链接应该在特定时间间隔之后失效,用户可以设定失效时间。

  4. 用户可以非必须地给粘贴的内容取一个别名。

非功能性要求

  1. 系统高度可靠,任何上传的数据都不能丢失。

  2. 系统高度可用。因为如果服务宕机了,用户将不能访问他们的粘贴。

  3. 用户访问他们的粘贴应该是实时的,延时应该最小化。

  4. 粘贴链接不可以被猜测(不可预测)。

扩展性要求

  1. 数据分析,例如:一份粘贴被访问了多少次?

  2. 该服务也应该支持其他服务通过 REST API 访问该服务。

3. 设计需要考虑的方面

Pastebin 和「URL 缩短服务」有部分共同的要求,但是有一些额外的设计方面需要考虑。

用户每次可以粘贴的文本的大小限制应该是多少?可以要求用户不能粘贴超过 10MB 的文本,避免对服务的滥用。

是否应该在自定义 URL 中显示大小限制?由于服务支持自定义 URL,用户可以选择任何他们喜欢的 URL,但是提供自定义 URL 不是必须的。尽管如此,在自定义 URL 中显示大小限制是合理的(通常也是可取的),这样可以使得 URL 数据库一致。

4. 容量估算和限制条件

我们的服务将是重读(read-heavy)的,读需求比创建新粘贴更多。我们可以假设读和写之间的比例是 5:1。

流量估算:Pastebin 服务的预期流量不如 Twitter 或 Facebook。这里假设每天有 100 万(1M)条新的粘贴添加到我们的系统中,每天有 500 万(5M)次读操作。

每秒添加的新粘贴:

1M / (24 hours * 3600 seconds) ~= 12 pastes/sec

每秒的读操作:

5M / (24 hours * 3600 seconds) ~= 58 reads/sec

存储估算:用户最多可以上传 10MB 的数据。一般而言,类 Pastbin 服务用于共享源代码、配置或日志。这些文本不会很大,因此假设平均每条粘贴的大小是 10KB。

按照这个速率,我们每天要存储 10GB 的数据。

1M * 10KB => 10 GB/day

如果我们想要将这些数据存储 10 年,我们需要的总存储容量是 36TB。

根据每天 100 万条粘贴计算,10 年内将会有 36 亿(36B)条粘贴。我们需要生成和存储关键字用于唯一地识别这些粘贴。如果我们使用 base64 编码([A-Z, a-z, 0-9, ., -]),我们需要使用 6 个字母的字符串:

64^6 ~= 687 亿个不同的字符串

如果每个字符使用一个字节存储,存储 36 亿个关键字的总大小将是:

3.6B * 6 => 22GB

22GB 和 36TB 相比是微不足道的。为了保留一些余地,我们假设使用 70% 容量模型(在任何时刻我们都不想使用超过总存储容量的 70%),这使得我们的存储需求增加到 51.4TB。

带宽估算:对于写请求,我们预期每秒有 12 条新粘贴,因此每秒有 120KB 的数据传入。

12 * 10KB => 120KB/s

对于读请求,我们预期每秒有 58 个请求。因此,总的数据传出(发送给用户)将是 0.6MB/s。

58 * 10KB => 0.6MB/s

虽然总的传入和传出并不大,但是我们需要在设计我们的服务时牢记这些数字。

内存估算:我们可以缓存一些被频繁访问的热门粘贴。根据 80-20 法则,20% 的热门粘贴产生 80% 的流量,我们想缓存这些 20% 的粘贴。

由于我们每天有 500 万个读请求,为了存储其中的 20% 的请求,我们需要:

0.2 * 5M * 10KB ~= 10GB

5. 系统 API

我们可以使用 SOAP 或 REST API 将我们的服务的函数公开。以下为对粘贴的创建/获取/删除的 API 的定义:

addPaste(api_dev_key, paste_data, custom_url=None, user_name=None, paste_name=None, expire_date=None)

参数: api_dev_key(string):一个已注册的帐号的 API 开发者关键字。关键字将和其他字段一起根据用户分配的额度限制用户。 paste_data(string):粘贴的文本数据。 custom_url(string):(可选)自定义 URL。 user_name(string):(可选)用于生成 URL 的用户名。 paste_name(string):(可选)粘贴的别名。 expire_date(string):(可选)粘贴的失效日期。

返回:(string) 如果插入粘贴成功,返回可以访问该粘贴的 URL。否则,返回错误码。

相似地,获取和删除粘贴的 API 如下:

getPaste(api_dev_key, api_paste_key)

其中 api_paste_key 是一个字符串,表示待获取的粘贴的关键字。这个 API 返回粘贴的文本数据。

deletePaste(api_dev_key, api_paste_key)

删除成功返回 true,否则返回 false。

6. 数据库设计

关于我们存储的数据的性质的一些发现如下:

  1. 我们需要存储几十亿条记录。

  2. 我们存储的每个元数据对象的大小都很小(小于 100 字节)。

  3. 我们存储的每个粘贴对象的大小居中(几个 MB)。

  4. 记录之间没有关联,除非我们想要存储哪个用户创建了哪条粘贴。

  5. 我们的服务是重读的。

数据库模型

我们需要两张表格,其中一张表格用于存储粘贴信息,另一张表格用于存储用户数据。

Paste 表

Paste

PK

URLHash: varchar(16)

ContentKey: varchar(512)ExpirationDate: datatimeUserID: intCreationDate: datetime

User 表

User

PK

UserID: int

Name: varchar(20)Email: varchar(32)CreationDate: datetimeLastLogin: datatime

其中,URLHash 是等价于 TinyURL 的 URL,ContentKey 是存储粘贴信息的对象关键字。

7. 高阶设计

在高阶层次上,我们需要一个提供所有写和读请求的应用层。应用层将和存储层联系,存储和取回数据。我们可以将存储层分离,一个部分是存储每条粘贴、用户等信息的元数据数据库,另一个部分在对象存储(像 Amazon S3)中存储粘贴的内容。数据的划分将允许我们分别测量不同部分的数据。

8. 组件设计

a. 应用层

我们的应用层将处理所有的传入和传出请求。应用服务器将和后端数据存储部件联系,处理请求。

如何处理写请求?当收到一个写请求时,我们的应用服务器将会生成一个由 6 个随机字母组成的字符串,作为粘贴的关键字(如果用户没有提供自定义关键字)。应用服务器然后将粘贴的内容和生成的关键字存入数据库。在成功插入之后,服务器可以将关键字返回给用户。一个可能的问题是由于重复的关键字导致插入失败。由于我们随机生成关键字,新生成的关键字可能和已有的关键字重复。在这种情况下,我们应给重新生成关键字并再次尝试。我们应该重复生成关键字的操作,直到不再出现因为关键字重复而导致插入失败。如果用户自定义的关键字在数据库中已经存在,我们应该给用户返回一个错误。

上述问题的另一个解决方案是运行独立的关键字生成服务(KGS),该服务事先生成随机的 6 个字母的字符串并将这些字符串存入一个数据库(称为关键字数据库)。任何时候当我们想要存储一条新的粘贴时,我们只要选择并使用一个已经生成的关键字。这样的方案可以将问题简单和快速地解决,因为我们不需要担心重复或冲突。KGS 可以保证所有插入关键字数据库地关键字都是唯一的。KGS 可以使用两张表格存储关键字,一张表格存储尚未使用的关键字,另一张表格存储全部已使用的关键字。当 KGS 将一些关键字发给一个应用服务器时,即可将这些关键字移动到已使用关键字的表格中。KGS 总是可以在内存中存储一些关键字,任何时候当一个服务器需要关键字时即可快速提供关键字。当 KGS 将一些关键字加载到内存中时,即可将这些关键字移动到已使用关键字的表格中,这种方法可以保证每个服务器接收到的都是唯一的关键字。如果 KGS 在使用完加载到内存中的所有关键字之前已经终止,则这些关键字将被浪费。我们可以忽略这些关键字,因为我们有很多关键字。

KGS 是不是单一故障点?使得。为了解决这个问题,我们可以有一个 KGS 的备用复制品,任何时候当主服务器终止时,备用复制品可以继续工作,生成和提供关键字。

每个应用服务器是否可以从关键字数据库中缓存一些关键字?是的,这样做当然可以提升速度。虽然在这种情况下,如果应用服务器在消费完所有的关键字之前已经终止,我们将失去这些关键字。这个结果可以几首,因为我们有 680 亿个不同的 6 个字母组成的关键字,远超过我们需要的。

如何处理粘贴读请求?当接收到读粘贴的请求时,应用服务层联系数据存储。数据存储搜索关键字,如果找到关键字,则返回粘贴的内容。否则,返回一个错误码。

b. 数据存储层

我们可以将数据存储层分成两部分:

  1. 元数据数据库:我们可以使用一个像 MySQL 的关系型数据库或者一个像 Dynamo 或 Cassandra 的分布式键值存储数据库。

  2. 对象存储:我们可以将内容存储在像 Amazon S3 的对象存储中。任何时候如果内容存储即将达到最大容量,可以通过增加更多的服务器很容易地增加容量。

Pastebin 的具体部件设计

9. 清洗或数据库清理

请查看「设计 URL 缩短服务」。

10. 数据分块和备份

请查看「设计 URL 缩短服务」。

11. 缓存和负载均衡

请查看「设计 URL 缩短服务」。

12. 安全和许可

请查看「设计 URL 缩短服务」。

Previous1. 设计类似TinyURL的短链服务Next3. 设计Instagram

Last updated 3 years ago