需求:
设计一个类似Pastebin的web服务,用户可以在其中存储纯文本。该服务的用户将输入一段文本并获得一个随机生成的URL来访问它。类似服务:pastebin.com、pasted.co、chopapp.com
难度等级:容易
1.什么是Pastebin?
类似Pastebin的服务使用户能够通过网络(通常是Internet)存储纯文本或图像,并生成唯一的URL来访问上载的数据。这类服务还用于通过网络快速共享数据,因为用户只需传递URL即可让其他用户看到。
2.系统的要求和目标
我们的Pastebin服务应满足以下要求:
功能要求:
- 1.用户应该能够上传或“粘贴”他们的数据,并获得一个唯一的URL来访问它。
- 2.用户只能上传文本。
- 3.数据和链接将在特定时间间隔后自动过期;用户还应该能够指定过期时间。
- 4.用户可以选择为粘贴选择自定义别名。
非功能性要求:
- 1.系统应高度可靠,上传的任何数据都不应丢失。
- 2.该系统应具有高可用性。这是必需的,因为如果我们的服务关闭,用户将无法访问其粘贴。
- 3.用户应该能够以最小的延迟实时访问他们的粘贴。
- 4.粘贴链接不应该是可猜测的(不可预测的)。
扩展要求:
- 1.分析,例如,访问粘贴的次数?
- 2.其他服务也应该可以通过RESTAPI访问我们的服务。
3.一些设计注意事项
Pastebin与URL缩短服务有一些相同的要求,但我们还应该记住一些额外的设计注意事项。用户一次可以粘贴的文本量的限制是什么?我们可以限制用户的粘贴大小不超过10MB,以防止滥用该服务。我们应该对自定义URL施加大小限制吗?由于我们的服务支持自定义URL,用户可以选择他们喜欢的任何URL,但提供自定义URL不是强制性的。然而,对自定义URL施加大小限制是合理的(并且通常是可取的),这样我们就有了一个一致的URL数据库。
补充一点:这里目前博客网站经常会有个功能叫做自定义域名,而这个自定义域名可以支持我们去访问博客内容,走博客自己的地址IP也可以访问。
4.容量估计和限制
我们的服务将被大量阅读;与新建粘贴相比,将有更多的读取请求。我们可以假设读写比为5:1。
流量估计:
Pastebin服务预计不会有类似于Twitter或Facebook的流量,让我们假设每天有一百万个新的粘贴添加到我们的系统中。这使我们每天有500万次阅读。
每秒新粘贴次数:
1M / (24 hours * 3600 seconds) ~= 12 pastes/sec
每秒新粘贴阅读次数:
5M / (24 hours * 3600 seconds) ~= 58 reads/sec
存储估计:
用户最多可以上传10MB的数据;通常类似Pastebin的服务是用于共享源代码、配置或日志。这样的文本不是很大,所以让我们假设每个粘贴平均包含10KB。按照这个速度,我们每天将存储10GB的数据。
1M * 10KB => 10 GB/day
如果我们想将这些数据存储十年,我们需要36TB的总存储容量。
每天有100万张贴纸,10年后我们将有36亿张贴纸。我们需要生成和存储密钥来唯一地标识这些粘贴。如果我们使用base64编码([A-Z,A-Z,0-9,,,-]),我们将需要六个字母字符串:
64^6 ~= 68.7 billion unique strings
如果存储一个字符需要一个字节,则存储3.6B密钥所需的总大小为:
3.6B * 6 => 22 GB
与36TB相比,22GB可以忽略不计。为了保持一定的空闲空间,我们将采用70%的容量模型(这意味着我们不希望在任何时候使用超过总存储容量70%的容量),这将使我们的存储需求增加到51.4TB。
带宽估计:
对于写请求,我们预计每秒会有12个新的粘贴,导致每秒120KB的进入。
12 * 10KB => 120 KB/s
对于读取请求,我们预计每秒有58个请求。因此,总数据出口(发送给用户)将为0.6 MB/s。
58 * 10KB => 0.6 MB/s
虽然总的进出量不大,但我们在设计服务时应该记住这些数字。
内存估计:
我们可以缓存一些经常访问的热粘贴。按照80-20规则,即20%的热粘贴产生80%的流量,我们希望缓存这20%的粘贴
由于我们每天有500万个读取请求,要缓存其中20%的请求,我们需要:
0.2 * 5M * 10KB ~= 10 GB
5.系统API
我们可以使用SOAP或RESTAPI来公开服务的功能。以下可能是创建/检索/删除粘贴的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(字符串):粘贴的文本数据。
custom_url(字符串):可选的自定义url。
user_name(字符串):用于生成URL的可选用户名。粘贴名称(字符串):粘贴的可选名称
expire_date(字符串):粘贴的可选过期日期。
Returns:(字符串)
成功插入将返回可通过其访问粘贴的URL,否则将返回错误代码。
同样,我们可以检索和删除粘贴API:
getPaste(api_dev_key, api_paste_key)
其中,“api_粘贴_键”是一个字符串,表示要检索的粘贴的粘贴键。此API将返回粘贴的文本数据。
deletePaste(api_dev_key, api_paste_key)
成功删除返回“true”,否则返回“false”。
6.数据库设计
关于我们存储的数据性质的一些观察结果:
1.我们需要存储数十亿条记录。
2.我们存储的每个元数据对象都很小(小于100字节)。
3.我们存储的每个粘贴对象都可以是中等大小(可以是几MB)。
4.记录之间没有关系,除非我们想存储哪个用户创建了什么粘贴
5.我们的服务质量很高。
数据库架构:
我们需要两个表,一个用于存储有关粘贴的信息,另一个用于存储用户的数据。
这里,“URlHash”是TinyURL的URL等价物,“ContentKey”是存储粘贴内容的对象键。
7.高级设计
在较高的层次上,我们需要一个应用层来服务所有的读写请求。应用层将与存储层通信以存储和检索数据。我们可以将存储层与一个数据库分离,其中一个数据库存储与每个粘贴、用户等相关的元数据,而另一个数据库将粘贴内容存储在一些对象存储中(如AmazonS3)。这种数据划分也将允许我们单独对其进行缩放。
8.组件设计
A.应用层
我们的应用层将处理所有传入和传出的请求。应用服务器将与后端数据存储组件进行通信,以满足请求。
如何处理写请求?
收到写请求后,我们的应用服务器将生成一个六个字母的随机字符串,该字符串将用作粘贴的密钥(如果用户没有提供自定义密钥)。然后,应用服务器将在数据库中存储粘贴内容和生成的密钥。成功插入后,服务器可以将密钥返回给用户。这里一个可能的问题可能是由于重复的密钥而导致插入失败。因为我们正在生成一个随机密钥,所以新生成的密钥可能与现有密钥匹配。在这种情况下,我们应该重新生成一个新密钥并重试。我们应该不断重试,直到没有看到由于重复密钥而导致的失败。如果用户提供的自定义密钥已经存在于我们的数据库中,我们应该向用户返回一个错误。
上述问题的另一个解决方案是运行独立密钥生成服务(KGS),该服务预先生成随机的六个字母字符串,并将它们存储在数据库中(我们称之为密钥数据库)。每当我们想要存储一个新的粘贴时,我们将只获取一个已经生成的键并使用它。这种方法将使事情变得非常简单和快速,因为我们不会担心重复或冲突。KGS将确保插入密钥数据库的所有密钥都是唯一的。KGS可以使用两个表来存储密钥,一个用于尚未使用的密钥,另一个用于所有已使用的密钥。只要KGS向应用服务器提供一些密钥,它就可以将这些密钥移动到used keys表中。KG总是可以在内存中保留一些密钥,以便服务器需要时可以快速提供这些密钥。KGS一旦在内存中加载一些密钥,就可以将它们移动到used keys表中,这样我们就可以确保每个服务器都获得唯一的密钥。如果KGS在使用内存中加载的所有密钥之前死亡,我们将浪费这些密钥。我们可以忽略这些键,因为我们有大量的键。
KGS不是单点故障吗?
是的。为了解决这个问题,我们可以有一个KGS的备用副本,只要主服务器死亡,它就可以接管生成和提供密钥。
每个应用服务器能否缓存密钥数据库中的一些密钥?
是的,这肯定能加快速度。尽管在这种情况下,如果应用程序服务器在使用所有密钥之前死亡,我们最终将丢失这些密钥。这是可以接受的,因为我们有68B唯一的六个字母的钥匙,这比我们需要的多得多。
它如何处理粘贴读取请求?
在接收到读粘贴请求后,应用程序服务层将联系数据存储。数据存储将搜索该键,如果找到,则返回粘贴的内容。否则,将返回错误代码。
B数据存储层
我们可以将数据存储层分为两层:
1.元数据数据库:
我们可以使用关系数据库(如MySQL)或分布式键值存储(如Dynamo或Cassandra)。
2.对象存储:
我们可以将内容存储在像Amazon的S3这样的对象存储中。每当我们想要在内容存储上达到最大容量时,我们都可以通过添加更多服务器轻松增加容量。
9清除或数据库清除
请参阅URL短链设计。
10数据分区和复制
请参阅URL短链设计。
11缓存和负载均衡器
请参阅URL短链设计。
12安全性和权限
请参阅URL短链设计。
参考资料
grok_system_design_interview.pdf