【系统设计】如何设计一个Pastebin.com?

如何设计一个Pastebin.com?

1.用例

我们将问题的范畴限定在如下用例

  • 用户 输入一段文本,然后得到一个随机生成的链接
    • 过期设置
      • 默认的设置是不会过期的
      • 可以选择设置一个过期的时间
  • 用户 输入一个 paste 的 url 后,可以看到它存储的内容
  • 用户 是匿名的
  • Service 跟踪页面分析
    • 一个月的访问统计
  • Service 删除过期的 pastes
  • Service 需要高可用

超出范畴的用例

  • 用户 可以注册一个账户
    • 用户 通过验证邮箱
  • 用户 可以用注册的账户登录
    • 用户 可以编辑文档
  • 用户 可以设置可见性
  • 用户 可以设置短链接

2.约束和状态假设

  • 流量分布不均
  • 跟随一个短链接应该很快
  • 粘贴只是文本
  • 页面浏览分析不需要是实时的
  • 1000万用户
  • 每月 1000 万次粘贴写入
  • 每月 1 亿次粘贴读取
  • 10:1 读写比

大致计算一下

  • 每个粘贴的大小
    • 每个粘贴 1 KB 内容
    • shortlink- 7 个字节
    • expiration_length_in_minutes- 4字节
    • created_at- 5 个字节
    • paste_path- 255 字节
    • 总计 = ~1.27 KB
  • 每月 12.7 GB 的新粘贴内容
    • 每次粘贴 1.27 KB * 每月 1000 万次粘贴
    • 3 年内约 450 GB 的新粘贴内容
    • 3 年 3.6 亿个短链接
    • 假设大多数是新粘贴而不是对现有粘贴的更新
  • 平均每秒 4 次粘贴写入
  • 平均每秒 40 个读取请求

方便的转换指南:

  • 每月 250 万秒
  • 每秒 1 个请求 = 每月 250 万个请求
  • 每秒 40 个请求 = 每月 1 亿个请求
  • 每秒 400 个请求 = 每月 10 亿个请求

3.系统设计

1.基础框架

img

这是系统最基础的框架,后续所有优化都基于此进行

主要分为三层

  • 第一层:客户端+webserver层,负责提供web服务并接收转发来自客户端的请求
  • 第二层:Write API + Read API +Analytics层:负责专门处理上一层的读写请求
  • 第三层:SQL+对象储存层:负责储存业务数据和储存文件对象

2.用例

用例1:用户输入一段文本并获得一个随机生成的链接

可以将关系型数据库当做大型的Hash表,将生成的URL映射到对象存储上的文件路径,同样,我们也可以将关系型数据库改为No SQL,下面讨论关系型数据库的使用方法

  • 客户端向作为反向代理运行的 Web服务器 发送创建粘贴请求
  • Web 服务器将请求转发到Write API服务器

  • Write API服务器执行以下操作:

    • 生成一个唯一的 url
      • 通过查看SQL 数据库中的重复项来检查 url 是否唯一
      • 如果 url 不唯一,则生成另一个 url
      • 如果我们支持自定义 url,我们可以使用用户提供的(也检查重复)
    • 保存到SQL 数据库 pastes
    • 将粘贴数据保存到对象存储
    • 返回url

上述pastes表可以具有以下结构:

shortlink char(7) NOT NULL
expiration_length_in_minutes int NOT NULL
created_at datetime NOT NULL
paste_path varchar(255) NOT NULL
PRIMARY KEY(shortlink)

将主键设置为基于shortlink列会创建一个索引,数据库使用该索引来强制唯一性。我们将创建一个额外的索引created_at来加快查找速度(记录时间而不是扫描整个表)并将数据保存在内存中。

从内存中顺序读取 1 MB 大约需要 250 微秒,而从 SSD 读取需要 4 倍,从磁盘读取需要 80 倍以上。

要生成唯一的 url,我们可以:

  • 第一步:取用户 ip_address + timestamp 的MD5 hash

    • MD5 是一种广泛使用的散列函数,可产生 128 位散列值
    • MD5 是均匀分布的
    • 或者,我们也可以采用随机生成数据的 MD5 哈希
  • 第二步:Base 62 来编码 MD5 哈希

    • Base 62 编码[a-zA-Z0-9]适用于 url,无需转义特殊字符

    • 原始输入只有一个哈希结果,Base 62 是确定性的(不涉及随机性)

    • Base 64 是另一种流行的编码,但由于额外的+/字符而为 url 提供了问题

    • 以下Base 62 伪代码在 O(k) 时间内运行,其中 k 是位数 = 7

      def base_encode(num, base=62):
          digits = []
          while num > 0
            remainder = modulo(num, base)
            digits.push(remainder)
            num = divide(num, base)
          digits = digits.reverse
  • 第三步:取输出的前 7 个字符,这会产生 62^7 个可能的值,应该足以处理我们在 3 年内 3.6 亿个短链接的约束

    url  =  base_encode ( md5 ( ip_address + timestamp ))[: URL_LENGTH ]
  • 第四步:使用公共的REST API返回url,对于内部响应,我们可以采用grpc

REST API

curl -X POST --data '{ "expiration_length_in_minutes": "60", \
    "paste_contents": "Hello World!" }' https://pastebin.com/api/v1/paste

响应

{
    "shortlink": "foobar"
}
用例2:用户输入粘贴的 url 并查看内容
  • 客户端Web 服务器发送获取粘贴请求
  • Web 服务器将请求转发到读取 API服务器
  • 读取 API 服务器执行以下操作 :
    • 检查 SQL 数据库中生成的 url
      • 如果 url 在SQL 数据库中,则从对象存储中获取粘贴内容
      • 否则,为用户返回错误消息
$ curl https://pastebin.com/api/v1/paste?shortlink=foobar
{
    "paste_contents": "Hello World"
    "created_at": "YYYY-MM-DD HH:MM:SS"
    "expiration_length_in_minutes": "60"
}
用例3:服务跟踪页面分析

由于不需要实时分析,我们可以简单地MapReduce Web服务器日志来生成命中计数。

只需要分析web服务的日志即可

class HitCounts(MRJob):

    def extract_url(self, line):
        """从日志行中提取生成的url"""
        ...

    def extract_year_month(self, line):
        """返回时间戳的年份和月份部分"""
        ...

    def mapper(self, _, line):
        """解析每个日志行,提取和转换相关行
        
        发出以下形式的键值对
        
        (2016-01, url0), 1
        (2016-01, url0), 1
        (2016-01, url1), 1
        """
        url = self.extract_url(line)
        period = self.extract_year_month(line)
        yield (period, url), 1

    def reducer(self, key, values):
        """每个键的总和值.

        (2016-01, url0), 2
        (2016-01, url1), 1
        """
        yield key, sum(values)
用例4:服务删除过期的粘贴

要删除过期的paste,我们可以扫描SQL 数据库中所有过期时间戳早于当前时间戳的条目。然后将从表中删除所有过期条目(或标记为过期)。

4.系统架构优化

img1

优化要点如下:

  • DNS:使用DNS优化域名到IP的查找
  • CDN:使用CDN内容分发,利用全局负载技术将用户的访问指向距离最近的正常工作的缓存服务器上,由缓存服务器直接响应用户请求,可以减少打到源服务集群的流量
  • 添加一个Memory Cache,交给Read API 服务器使用,加速read类型的响应
  • 添加一个 SQL Analytics数据库,专门用于Analytics分析服务
  • SQL Write采用主从模式,SQL Read采用多副本分散压力

Analytics SQL可以采用数据仓库的解决方案,例如 Amazon Redshift 或 Google BigQuery。

像 Amazon S3 这样的对象存储可以轻松处理每月 12.7 GB 新内容的限制。

为了解决每秒40 个*平均读取请求(峰值更高),流行内容的流量应该由内存缓存而不是数据库来处理。内存缓存对于处理不均匀分布的流量和流量峰值也很有用。SQL 只读副本应该能够处理缓存未命中,只要副本不会因复制写入而陷入困境。

对于单个SQL Write Master-Slave,每秒4次平均粘贴写入(峰值更高)应该是可行的。否则,我们将需要使用额外的 SQL 扩展模式:

  • 分片
  • sql调优

我们还应该考虑将一些数据移动到NoSQL 数据库

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值