zoukankan      html  css  js  c++  java
  • 系统设计实践(02)- 文本存储服务

    前言

    系统设计实践篇的文章将会根据《系统设计面试的万金油》为前置模板,讲解数十个常见系统的设计思路。

    前置阅读:

    设计目标

    让我们设计一个类似于Pastebin的网站,用户可以在其中存储纯文本。该服务的用户将输入一段文本,并获得一个随机生成的URL来访问它。

    一. 什么是Pastebin?

    Pastebin是一个文本存储的网站,用户可以在网站上储存(粘贴)纯文本 ,例如代码片段,生成一个网址,打开该网址就可以看到对应的文字。可以选择文字的类型(代码所属的编程语言)、文字保存的时间(1天、7天、30天、阅后即焚等等)、文字分享者的昵称等信息。因为第一个文本分享网站叫 http://pastebin.com,所以文本存储网站也常被称为Pastebin。

    二. 系统的需求与目标

    Pastebin服务应满足以下要求:

    功能性需求
    1. 用户应该能够上传或粘贴他们的文本数据,并获得访问它的唯一URL。
    2. 用户只能上传文本。
    3. 数据和链接地址将在特定时间间隔后自动过期; 用户可以指定过期时间。
    4. 用户可以为他们的文本内容选择一个自定义的别名。
    非功能性需求
    1. 系统应该是高度可靠的,任何上传的数据都不应该丢失。
    2. 系统应该是高度可用的。这是必须的,因为如果我们的服务关闭,用户将无法访问他们的粘贴内容。
    3. 用户应该能够以最小的延迟实时访问他们的粘贴。
    4. 粘贴链接地址不应该是可猜测的(不可预测的)。
    扩展需求
    1. 分析,例如,粘贴地址被访问多少次
    2. 我们的服务也应该可以通过REST API被其他服务访问。

    三. 系统相似性

    Pastebin与上一篇《系统设计实践(01) - 短链服务》有很多相似性的地方,所以我建议在开始阅读前再去读一读短链服务那篇文章,此外还有一些额外的设计注意事项。

    用户一次可以粘贴的文本数量的限制是什么?

    我们可以限制用户的粘贴不超过10MB,以防止滥用服务。

    我们应该对自定义url施加大小限制吗?

    由于我们的服务支持自定义URL,用户可以自定义他们喜欢URL,但提供自定义URL不是强制性的。然而,对自定义URL施加大小限制是合理的(通常也是可取的),这样我们就有了一致的URL数据库。

    四. 容量估算与约束

    与短链服务类似,我们的服务读请求会更多,与创建新的粘贴相比,将有更多的读取请求。我们可以假设读和写的比例是5:1。

    流量估计

    我们假设系统每天有100万新粘贴生成, 这样我们每天就有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

    如果我们想将这些数据存储10年,我们需要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%的粘贴,因为我们每天有5M的读请求,要缓存这些请求的20%,我们需要

    0.2 * 5M * 10KB ~= 10 GB

    五. 系统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): 可选的过期时间.
    返回

    成功将返回可以访问粘贴的URL,否则将返回错误代码。

    getPaste(api_dev_key, api_paste_key)

    其中api粘贴键是一个字符串,表示要检索的粘贴键。这个API将返回粘贴的文本数据。

    deletePaste(api_dev_key, api_paste_key)

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

    六. 数据库设计

    关于我们正在存储的数据的性质的一些观察

    • 我们需要存储数十亿条记录。
    • 我们存储的每个元数据对象都很小(小于100字节)
    • 我们存储的每个粘贴对象可以是中等大小(可以是几MB)。
    • 记录之间没有关系,除非我们想要存储哪个用户创建了什么粘贴。
    • 我们的服务读请求很多

    数据库选型

    我们需要两张表,一个用于存储关于paste的信息,另一个用于存储用户数据。

    Paste User
    [PK] URL Hash: varchar(16) [PK] UserID: int
    ContentKey: varchar(512) Name: varchar(20)
    CreationDate: datetime Email: varchar(20)
    ExpirationDate: datatime CreationDate: datetime
    LastLoginDate: datetime

    在这里,URl Hash是TinyURL的URL等价物,ContentKey是存储粘贴内容的对象键。

    七. 高级设计

    在更高的层次上,我们需要一个应用程序层来服务于所有的读写请求。应用层将与存储层通信以存储和检索数据。我们可以隔离存储层,一个数据库存储与每个粘贴、用户等相关的元数据,而另一个数据库将粘贴内容存储在某些对象存储中(如Amazon S3)。这种数据划分也将允许我们对它们进行单独的缩放。

    八. 组件设计

    应用层

    我们的应用层将处理所有传入和传出的请求。应用服务器将与后端数据存储组件通信来处理请求。

    如何处理写请求?

    在接收到写请求时,我们的应用服务器将生成一个6个字母的随机字符串,它将作为粘贴的密钥(如果用户没有提供自定义密钥)。然后,应用程序服务器将在数据库中存储粘贴的内容和生成的键。成功插入后,服务器可以将密钥返回给用户。这里的一个可能问题是,由于密钥重复,插入失败。因为我们生成了一个随机密钥,所以新生成的密钥有可能与现有密钥匹配。在这种情况下,我们应该重新生成一个新的密钥并再试一次,直到没有发现因为重复密钥。如果用户提供的自定义键已经存在于数据库中,则应该向用户返回一个错误。

    上述问题的另一个解决方案是运行一个独立的密钥生成服务(KGS),它事先生成随机的6个字母字符串,并将它们存储在一个数据库中(我们称之为Key-db)。每当我们想要存储一个新的粘贴时,我们只需要一个已经生成的键并使用它。这种方法将使事情变得非常简单和快速,因为我们不需要担心重复或碰撞。KGS将确保插入到key-DB中的所有键是唯一的。KGS可以使用两个表来存储键,一个用于尚未使用的键,另一个用于所有已使用的键。一旦KGS向应用服务器提供了一些键,它就可以将这些键移动到所使用的键表中。KGS可以在内存中保存一些密钥,以便每当服务器需要它们时,它可以快速提供它们。一旦KGS在内存中加载了一些键,它就可以将它们移动到已使用的键表中,这样我们就可以确保每个服务器获得唯一的键。如果KGS在使用内存中加载的所有键之前宕机,这些键会被浪费,不过可以忽略,因为KGS中6个字母可生成的字符串足够多。

    KGS不是单点故障吗?

    是的。为了解决这个问题,我们可以有一个KGS的备用副本,每当主服务器死亡时,它可以接管生成并提供密钥。

    每个应用服务器是否可以从key-DB中缓存一些key?

    是的,这肯定能加快响应速度。尽管在这种情况下,如果应用服务器在使用所有密钥之前就挂掉了,我们最终会丢失这些密钥。这是可以接受的,因为我们有68B唯一的6个字母的钥匙,这比我们需要的多得多。

    它如何处理粘贴读请求?

    在接收到读粘贴请求后,应用程序服务层请求数据存储。数据存储搜索密钥,如果找到,返回粘贴的内容。否则,返回错误代码。

    数据层

    我们可以讲数据存储划为两层。

    • 元数据数据库:我们可以使用关系数据库如MySQL或分布式键值存储如Dynamo或Cassandra。
    • 对象存储:可以像Amazon S3一样将内容存储在对象存储中。当我们想要在内容存储上达到最大容量时,我们可以通过添加更多服务器来轻松增加容量。

    作者: AntzUhl

    首发地址博客园:http://www.cnblogs.com/LexMoon/

    代码均可在Github上找到(求Star) : Github

    个人博客 : http://antzuhl.cn/

    公众号

    赞助

    支付宝

    微信

    随意随意,要是我的文章对你有帮助,可以考虑请我喝瓶阔落。

  • 相关阅读:
    Windows10下配置单机zookeeper(连接服务器上的zookeeper)
    尚硅谷Zookeeper教程学习讲义
    腾讯云服务器在防火墙上添加可访问端口(CentOS7)
    uniPaaS 2.0新功能
    uniPaas RIA 安装与使用方法
    unipaas2.0发布了,有需要试的请与我联系QQ:79982575
    UNIPAAS抢滩Web 2.0
    最简便最快速的开发工具—Magic eDeveloper/DBMAGIC/UNIPAAS
    RIA技术的应用(UNIPAAS)
    数据库开发工具Magic教程基本操作(以Magic eDeveloper V10为例,其他版本会有差异)
  • 原文地址:https://www.cnblogs.com/LexMoon/p/15266503.html
Copyright © 2011-2022 走看看