如何设计一个支持千万用户的内容分享系统?
点击关注公众号,AI,编程干货及时送达
设计Pastebin
让我们设计一个类似Pastebin的网络服务,用户可以在其中存储纯文本。用户会输入一段文本并获得一个随机生成的URL以访问它。
类似的服务:pastebin.com, pasted.co, chopapp.com
难度级别:中等
1. 什么是Pastebin?
Pastebin之类的服务使用户可以在网络上(通常是Internet)存储纯文本或图像,并生成唯一的URL来访问上传的数据。此类服务也用于快速在网络上共享数据,用户只需要传递URL,其他用户就可以看到它。
如果你以前没有使用过pastebin.com,请试着在那里创建一个新的'Paste',并花一些时间研究他们服务提供的不同选项。这将帮助你更好地理解这个章节。
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. 容量估计和约束
我们的服务将是读取密集型的;与新建粘贴的请求相比,将有更多的读取请求。我们可以假设读和写之间的比例为5:1。
流量估计:Pastebin服务预计不会有像Twitter或Facebook那样的流量,我们这里假设每天有一百万新粘贴添加到我们的系统。这使我们每天有五百万次的读取。
每秒新粘贴数量:1M / (24小时 * 3600秒) ~= 12 粘贴/秒
每秒粘贴读取数量:5M / (24小时 * 3600秒) ~= 58 读取/秒
存储估计:用户可以上传最大10MB的数据;通常,Pastebin之类的服务用于分享源代码,配置或日志。这样的文本不会很大,所以我们假设每个粘贴平均包含10KB。
按照这个速率,我们每天将存储10GB的数据。1M * 10KB => 10 GB/天 bash
如果我们想将这些数据存储十年,我们将需要总共36TB的存储容量。
每天有1M粘贴,十年后我们将有36亿粘贴。我们需要生成和存储键来唯一标识这些粘贴。如果我们使用base64编码([A-Z,a-z,0-9,.,-]),我们将需要六个字母的字符串:64^6 ~= 687亿个唯一字符串
如果需要一个字节来存储一个字符,存储36亿键所需的总大小将是:3.6B * 6 => 22 GB
22GB与36TB相比微不足道。为了保留一些余量,我们将假定一个70%的容量模型(意味着我们在任何时点都不想使用超过总存储容量的70%),这将使我们的存储需求增加到51.4TB。
带宽估计:对于写请求,我们预计每秒有12个新粘贴,导致每秒进入120KB。12 * 10KB => 120 KB/s bash
对于读取请求,我们预计每秒有58个请求。因此,发送给用户的总数据出口将是0.6 MB/s。58 * 10KB => 0.6 MB/s
尽管总的入口和出口不大,但我们在设计我们的服务时应该记住这些数字。
内存估计:我们可以缓存一些经常访问的热门粘贴。按照80-20规则,也就是20%的热门粘贴产生80%的流量,我们希望缓存这20%的粘贴
因为我们每天有5M的读取请求,要缓存这些请求的20%,我们需要:0.2 * 5M * 10KB ~= 10 GB markdown
5.系统设计和算法
在我们的服务中,用户将上传他们的粘贴,并获取一个唯一的URL来访问它。当用户用这个URL访问我们的服务时,他们将能看到粘贴的内容。
我们将使用一个hash表来存储数据,其中key是我们的URL,value是粘贴内容。为了让这个设计工作,我们需要一个用来生成key的hash函数。然后,我们可以将粘贴的内容和过期时间(如果有的话)存储在数据库中。
Hash函数
在我们的场景中,我们需要一个能为每个粘贴生成一个唯一key的hash函数。但是,这个函数必须满足一下要求:
• 均匀分布:我们需要保证所有的key都能均匀分布在hash表中,以便于我们平均分摊读写负载。
• 快速计算:因为我们的服务可能会同时处理大量请求,我们需要一个能快速计算key的hash函数。
• 不可预测:为了安全考虑,我们不能让外部用户预测出未来可能的key。
在实践中,我们通常会使用一些已经存在的、经过大量测试的hash函数,例如MD5或SHA-256。这些函数都是经过设计的,能满足我们上面提到的要求。
数据库设计
为了满足我们的需求,我们需要一个支持快速key-value查询的数据库。NoSQL数据库是一个不错的选择,因为它们通常在这种场景下表现得更好。我们可以使用诸如Redis或Cassandra这样的NoSQL数据库。
对于每个粘贴,我们需要保存以下信息:
• PasteId(Key):一个唯一的标识符,用于获取粘贴内容。
• Content:用户上传的粘贴内容。
• ExpirationDate:粘贴过期的日期和时间。
缓存
我们可以在数据库和客户端之间引入一个缓存层,以加速频繁请求的响应时间。对于这种用途,我们可以使用像Memcached或Redis这样的内存数据存储系统。当用户请求一个粘贴时,我们首先查看是否在缓存中。如果在,我们直接返回缓存的数据;否则,我们从数据库中取出数据,放入缓存,并返回给用户。
我们还需要实现一种策略来决定何时从缓存中删除条目。一种常用的策略是LRU(最近最少使用),它将最近最少使用的条目从缓存中删除。
6. 数据一致性
我们的系统可能会在多个服务器上运行,因此我们需要考虑数据一致性问题。如果一个用户在服务器A上创建了一个新的粘贴,然后立即在服务器B上请求这个粘贴,服务器B需要有这个粘贴的最新数据。
一种解决这个问题的方法是使用强一致性模型。在这种模型中,一旦一个写操作完成,所有的读操作都能看到这个写操作的结果。然而,这种模型可能会降低系统的性能,因为它需要在所有的服务器上同步数据。
另一种方法是使用最终一致性模型。在这种模型中,系统不保证在所有服务器上立即同步数据。然而,如果没有新的更新,最终所有的副本都会达到一致。这种模型允许更高的性能,但可能导致短时间内的数据不一致。
根据我们的系统需求,我们可能会选择最终一致性模型,因为我们的系统不需要立即看到所有的更新。当用户创建一个新的粘贴时,我们可以告诉他们可能需要一些时间才能在所有服务器上看到他们的粘贴。
7. 可扩展性
我们的设计应该能够处理增加的负载。为了实现这一点,我们可以使用以下技术:
• 分片:我们可以通过将数据分布在多个数据库服务器上来增加系统的容量。我们可以使用一种称为一致性哈希的技术来实现这一点。
• 负载均衡:我们可以在我们的服务器和数据库之间使用负载均衡器来分发请求。这可以帮助我们均匀地分配负载,并在服务器出现问题时提供冗余。
以上就是关于如何设计一个类似Pastebin的网络服务的简单概述。在实际设计和实现过程中,可能还需要考虑更多的细节和特殊情况。
8. 组件设计
a. 应用层
我们的应用层将处理所有的入站和出站请求。应用服务器将与后端数据存储组件交流以满足请求。
如何处理写请求?在收到写请求后,我们的应用服务器将生成一个六位随机字符串,这将作为粘贴的键(如果用户没有提供自定义键)。然后,应用服务器将把粘贴的内容和生成的键存储在数据库中。成功插入后,服务器可以将键返回给用户。这里可能存在的一个问题是因为重复的键导致插入失败。由于我们生成的是随机键,新生成的键可能与现有的键相匹配。在这种情况下,我们应该重新生成一个新的键并尝试再次插入。我们应该一直尝试,直到我们看不到由于重复键导致的失败。如果用户提供的自定义键已经存在于我们的数据库中,我们应该向用户返回一个错误。
上述问题的另一个解决方案可能是运行一个独立的键生成服务(KGS),它提前生成随机的六位字母字符串,并将它们存储在数据库中(我们称之为key-DB)。每当我们想要存储一个新的粘贴,我们只需要取一个已经生成的键并使用它。这种方式会使事情变得相当简单和快速,因为我们不必担心重复或冲突。KGS会确保所有插入到key-DB的键都是唯一的。KGS可以使用两个表来存储键,一个用于尚未使用的键,另一个用于所有已使用的键。只要KGS将一些键提供给应用服务器,就可以将这些键移动到已使用的键表中。KGS可以始终在内存中保留一些键,以便无论何时服务器需要它们,都能快速提供。只要KGS将一些键加载到内存中,就可以将它们移动到已使用的键表中,这样我们就可以确保每个服务器得到的键都是唯一的。如果KGS在使用所有加载到内存中的键之前死掉,我们将浪费这些键。鉴于我们有大量的键,我们可以忽略这些键。
KGS是不是一个单点故障? 是的,确实是。为了解决这个问题,我们可以有一个备用的KGS,只要主服务器死机,它就可以接管生成和提供键的工作。
每个应用服务器能从key-DB缓存一些键吗?是的,这肯定可以加快速度。尽管在这种情况下,如果应用服务器在消耗所有键之前死机,我们将最终丢失这些键。这可能是可以接受的,因为我们有68B个唯一的六字母键,这比我们需要的要多得多。
它是如何处理粘贴读取请求的?在接收到读取粘贴请求后,应用服务层会联系数据存储。数据存储搜索该键,如果找到,就返回粘贴的内容。否则,返回一个错误代码。
b. 数据存储层
我们可以将我们的数据存储层分为两部分:
1. 元数据数据库:我们可以使用关系数据库,如MySQL,或者分布式键值存储,如Dynamo或Cassandra。
2. 对象存储:我们可以在对象存储中存储我们的内容,如Amazon的S3。只要我们感觉即将达到内容存储的全容量,我们就可以通过添加更多的服务器来轻易增加它。
9. 清理或数据库清理
请参见设计URL缩短服务。
10. 数据分区和复制
请参见设计URL缩短服务。
11. 缓存和负载均衡器
请参见设计URL缩短服务。
12. 安全和权限
请参见设计URL缩短服务。
推荐阅读
你好,我是拾叁,7年开发老司机、互联网两年外企5年。怼得过阿三老美,也被PR comments搞崩溃过。这些年我打过工,创过业,接过私活,也混过upwork。赚过钱也亏过钱。一路过来,给我最深的感受就是不管学什么,一定要不断学习。只要你能坚持下来,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯和编程知识,帮你积累弯道超车的资本。