原文链接:
https://www.educative.io/courses/grokking-the-system-design-interview/m2ygV4E81AR

编者注:本文以一道经典的系统设计面试题:《如何设计TinyURL》的参考答案和解析为例,帮助读者更深入地了解在系统需求分析和设计中,需要考虑的各个方面的细节。

本文将为大家详细讲解如何设计一个类似于TinyURL的URL缩短服务。URL缩短服务提供一个非常短小的URL以代替原来的可能较长的URL,将长的URL地址缩短。

类似的服务有:bit.ly,goo.gl,qlink.me,等等。

一、为什么需要URL缩短服务?

URL缩短服务是用来为长URL创建简短别名的。我们称这些缩短的别名为“短网址”。当用户点击这些短网址时,能够重定向到原始的URL。相较于原始URL,短网址在显示、打印、发送消息或微博时能够节省大量空间。另外,用户输错短网址的可能性也比较小。

举个例子,如果我们通过TinyURL来缩短下面这个网址:

https://www.educative.io/collection/page/5668639101419520/5649050225344512/5668600916475904/

我们可以得到以下的短网址:

http://tinyurl.com/jlg8zpc

上面这个短网址的长度几乎只有实际URL的三分之一。

URL缩短用于优化跨设备的链接,通过跟踪单个链接来分析受众和活动性能,并隐藏关联的原始URL。

如果你以前没有使用过http://tinyurl.com/这个网站,请尝试设计并创建一个新的网址缩短系统,然后,花一些时间浏览它们提供的各种服务选项。这会帮助你更好地理解本章节的内容。

二、系统的需求和目标

设计的URL缩短系统应符合以下需求:

功能性需求:

1、给定一个原始URL,该系统能够生成一个较短且唯一的短网址。这个短网址的长度应该足够短,以便轻松复制和粘贴到应用程序中。

2、当用户访问一个短网址时,该系统应该将它们重定向到原始链接。

3、用户可以为他们的URL选择一个自定义的短网址。

4、在标准的默认时间间隔后,对应的短网址过期。过期时间允许用户设置。

非功能性需求:

1、该系统必须是高度可用的。因为一旦我们的服务宕机,那么所有的URL重定向都会失败。

2、URL重定向应该在最小延迟的情况下实时进行。

3、短网址应该是不可预测

扩展需求:

1、能够进行分析;例如,重定向发生了多少次。

2、其他系统可以通过REST API访问我们的服务。

三、容量估计及限制

设计的系统将包含大量读取操作。与新的URL缩短相比,将会有大量的重定向请求。假设读和写的比率为100:1。

流量估计:

假设每个月将有5亿个新的短网址产生,读/写比率为100:1的话,预计在同一时期将有500亿个重定向请求:

100 * 5 亿  ≥ 500 亿

对于我们设计的系统,每秒查询数(QPS)又是多少呢?每秒新的短网址数:

5 亿 / (30 天 * 24 小时 * 3600 秒 ) ≌ 200 URLs/s

考虑到100:1的读/写比例,每秒URL重定向请求数将是:

100 * 200 URLs/s = 20 K/s

存储估计:

假设将每个短网址请求存储5年,每个月将有5 亿个新的短网址产生,由此可以得出,预计存储的对象总数将达到300亿个:

5 亿 * 5 年 * 12 月 = 300 亿

假设每个存储对象的大小大约是500 字节(这只是一个大致的估计——我们稍后将深入研究它)。那我们需要15 TB 的总存储:

300 亿 * 500 字节 = 15 TB

带宽估计:

对于写请求,前面我们计算出,预计每秒有200个新短网址产生,由此得出,总输入数据将为100 KB/s:

200 * 500 字节 ≌ 100 KB/s

对于读请求,前面我们计算出,预计每秒URL重定向请求是2 万个,由此得出,总输出数据为10MB/s:

2 万 * 500 字节 ≌10 MB/s

内存估计:

如果想缓存一些经常访问的热门URL,那需要多少内存来存储它们呢?

这里,我们遵循80-20规则,也就是说20%的URL产生80%的流量,我们想要缓存这20%的热门URL。

由于每秒有2万个重定向请求,那么每天收到的重定向请求有17亿个:

2 万 * 3600 秒 * 24 小时 ≌ 17 亿

缓存其中20%的请求所需要的内存则为170 GB:

0.2 * 17 亿 * 500 字节 ≌ 170GB

这里需要注意一点,由于会有许多重复的请求(也就是相同的URL),因此,实际内存使用量将少于170 GB。

高级别估计:

假设每月有5亿个新URL,读/写比例100:1,下面是对此服务的估计概要:

四、系统API

一旦确定了系统的需求,那么定义系统API总是一个好主意。它应该明确地说明我们对系统的期望。

我们可以使用SOAP或REST API来公开服务的功能。以下是用于创建和删除URL的API的定义:

<section style="margin-top: 10px;margin-bottom: 20px;"><code class=""><span style="font-size: 15px;">createURL(api_dev_key, original_url, custom_alias=None, user_name=None, expire_date=None)</span></code></section>

相关参数:

api_dev_key(字符串):注册账户的API开发人员密钥。除此之外,它还将用于根据分配的配额限制用户。

original_url(字符串):要缩短的原始URL。

custom_alias(字符串):URL的可选自定义键。

user_name(字符串):可选的用户名,用于编码。

expire_date(字符串):可选过期日期的缩短URL。

返回:(字符串)

成功插入将返回短网址;否则,将返回错误代码。

<section style="margin-top: 10px;margin-bottom: 20px;"><code class=""><span style="font-size: 15px;">deleteURL(api_dev_key, url_key)</span></code></section>

其中“url_key”表示的是要检索的缩短URL的字符串。成功删除将返回“URL Removed”。

那我们要如何发现和防止滥用呢?恶意用户可以通过使用当前设计中的所有URL密钥让我们失去业务。为了防止滥用,我们可以通过api_dev_key来限制用户。每个api_dev_key可以在某个时间段内限制URL创建和重定向的数量(每个开发人员密钥可以设置为不同的持续时间)。

五、数据库设计

对于我们将要存储的数据,对其性质做一些观察:

1、需要存储数十亿的记录。

2、存储的每个对象都很小(小于1K)。

3、除了存储创建URL的用户之外,记录之间没有任何关系。

4、需要大量的读取操作。

数据库架构:

我们需要两个表:一个用于存储有关URL映射的信息,另一个用于存储创建短网址的用户数据。

preview

那我们应该使用什么样的数据库呢?因为我们预计存储数十亿的数据,而且我们不需要使用对象之间的关系,所以像DynamoDB,Cassandra或者Riak这样的通过key-value形式存储的NoSQL是更好的选择。选择NoSQL也更容易进行扩展。

(未完待续)

10-16 01:49