2. 设计 Pastebin
难度等级:简单
让我们设计一个类 Pastebin 网络服务,用户可以存储纯文本。此服务的用户可以输入文本内容并得到一个随机生成的 URL 用于访问该文本内容。类似服务:pastebin.com,pasted.co,chopapp.com。
1. 什么是 Pastebin?
类 Pastebin 服务允许用户在网络(通常是因特网)上存储纯文本或图像,并生成唯一的 URL 用于访问上传的数据。这样的服务也用于在网络上快速共享数据,用户只需要发送 URL 让其他用户看到。
如果你之前没有用过 pastebin.com,请尝试在那里创建一个新的「粘贴」并花一些时间体验该服务提供的不同选项。这对你理解这一章有很大帮助。
2. 系统的要求和目标
Pastebin 服务应满足以下要求。
功能性要求
用户可以上传或「粘贴」数据并得到唯一的 URL 用于访问该数据。
用户只能上传文本。
数据和链接应该在特定时间间隔之后失效,用户可以设定失效时间。
用户可以非必须地给粘贴的内容取一个别名。
非功能性要求
系统高度可靠,任何上传的数据都不能丢失。
系统高度可用。因为如果服务宕机了,用户将不能访问他们的粘贴。
用户访问他们的粘贴应该是实时的,延时应该最小化。
粘贴链接不可以被猜测(不可预测)。
扩展性要求
数据分析,例如:一份粘贴被访问了多少次?
该服务也应该支持其他服务通过 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 的定义:
参数: 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 如下:
其中 api_paste_key 是一个字符串,表示待获取的粘贴的关键字。这个 API 返回粘贴的文本数据。
删除成功返回 true,否则返回 false。
6. 数据库设计
关于我们存储的数据的性质的一些发现如下:
我们需要存储几十亿条记录。
我们存储的每个元数据对象的大小都很小(小于 100 字节)。
我们存储的每个粘贴对象的大小居中(几个 MB)。
记录之间没有关联,除非我们想要存储哪个用户创建了哪条粘贴。
我们的服务是重读的。
数据库模型
我们需要两张表格,其中一张表格用于存储粘贴信息,另一张表格用于存储用户数据。
Paste 表
PK
URLHash: varchar(16)
ContentKey: varchar(512)ExpirationDate: datatimeUserID: intCreationDate: datetime
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. 数据存储层
我们可以将数据存储层分成两部分:
元数据数据库:我们可以使用一个像 MySQL 的关系型数据库或者一个像 Dynamo 或 Cassandra 的分布式键值存储数据库。
对象存储:我们可以将内容存储在像 Amazon S3 的对象存储中。任何时候如果内容存储即将达到最大容量,可以通过增加更多的服务器很容易地增加容量。
Pastebin 的具体部件设计
9. 清洗或数据库清理
请查看「设计 URL 缩短服务」。
10. 数据分块和备份
请查看「设计 URL 缩短服务」。
11. 缓存和负载均衡
请查看「设计 URL 缩短服务」。
12. 安全和许可
请查看「设计 URL 缩短服务」。
Last updated