让我们设计一个类似TinyURL的短URL服务。此服务将提供重定向到长URL的短URL别名。类似服务:bit.ly、goo.gl、qlink.me等。

1.为什么我们需要缩短URL?

URL缩短用于为长URL创建较短的别名。我们把这些缩短的别名称为“短链接”。当用户点击这些短链接时,将重定向到原始URL。当显示,打印,发送信息或发送推特时,短链接节省了大量的空间。此外,用户不太可能错误键入更短的URL。
例如,如果我们通过TinyURL缩短这个页面:https://www.educative.io/collection/page/5668639101419520/5649050225344512/5668600916475904/
我们会得到:
http://tinyurl.com/jlg8zpc
短URL的大小几乎是实际URL的三分之一。短URL用于优化跨设备的链接,跟踪单个链接以分析受众和比较表现,和隐藏关联的原始URL。

如果您以前没有使用过tinyurl.com,请尝试创建一个新的短URL,并花一些时间研究他们的服务提供的各种选项。这将对你理解这一章有很大帮助。

2.系统的要求和目标

你应该在面试一开始就明确要求。一定要问找出面试官想要的系统的确切范围的问题。我们的URL缩短系统应该满足以下要求:

功能需求:
1.给定一个URL,我们的服务应该生成一个更短和唯一的别名。这就是所谓的短链接。
2.当用户访问短链接时,我们的服务应该将他们重定向到原来的链接。
3.用户可以选择为他们的URL选择一个自定义的短链接。
4.链接将在标准默认时间段后过期。用户应该能够指定过期时间。

非功能性需求:
1.系统要实现高可用性。这是必需的,因为如果我们的服务关闭了,所有URL重定向将开始失败。
2.URL重定向应实时进行,保证延迟最小.
3.缩短的链接不应是可猜测的(不可预测的)。

额外需求:
1.分析;例如,重定向发生了多少次?
2.我们的服务也应该可以通过REST APIs被其他服务访问。

3.容量大小估计和约束

我们系统的读请求会很多。与新的短URL链接相比,将会有很多重定向请求。我们假设读写比率为100:1

流量估计:
假设我们每月将有5亿个新的URL链接,以及100:1的读/写比率,那么在同一时期内,我们可以期望发生了500亿次重定向:
100*5亿=>500亿
对于我们的系统,每秒的查询数(QPS)是什么?每秒新的短URL数:
5亿/(30天*24小时*3600秒)=~200个URL/s
考虑到100:1的读写比,URL每秒将重定向:
100*200 URL/s=20K/s

存储估计:
假设我们将每个URL缩短请求(以及相关的短链接)存储5年。由于我们预计每个月将有5亿个新的URL,我们预计存储的对象总数将达到300亿:
5亿*5年*12个月=300亿
让我们假设每个存储的对象大约是500个字节(只是一个大概的估计-我们稍后将深入研究它)。我们需要15 TB的总储存量:
300亿*500个字节=15 TB

带宽估计:
对于写请求,由于我们期望每秒有200个新URL,我们服务的总传入数据将是每秒100 kb:
200*500字节=100 KB/s
对于读取请求,由于我们每秒钟期望~20K URL重定向,我们的服务的总传出数据将是每秒10 MB:
20k*500个字节=~10 MB/s

内存估计:
如果我们想缓存一些经常被访问的热URL,我们需要如何存储它们?如果我们遵循80-20规则,即20%的URL产生80%的流量,我们希望缓存这20%的热URL。由于每秒有20K请求,我们将每天收到17亿个请求:
20k*3600秒*24小时=17亿
要缓存这些请求的20%,我们需要170 GB的内存。
0.2*17亿*500个字节=~170 GB
这里需要注意的一点是,由于会有大量重复请求(相同URL),因此,我们的实际内存使用量将小于170 GB。

高层级估计:
假设每月新增5亿个URL,以及100:1的读:写比率,以下是我们服务的高层级估计值的摘要:
新URL 200 个/s
URL重定向 20 K/s
Incoming数据 100 KB/s
Outgoing数据 10 MB/s
Storage 5年 15 TB
Memory for Cache 170 GB

4.系统API

一旦确定了需求,定义系统API总是一个好主意。这应该明确说明对系统的期望。

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

createURL(api_dev_key, original_url, custom_alias=None, user_name=None, expire_date=None)

参数:
api_dev_key(string):一个注册帐户的API 开发者的key。这将用于根据用户分配的份额来限流用户。
original_url(string):要被缩短的原始URL。
custom_alias(string):URL的可选自定义键:user_name(string):可选用户名将用于编码
expect_date(String):短URL的可选过期日期

返回值:(string)
一个成功的插入会返回短URL;否则,它将返回一个错误代码。

deleteURL(api_dev_key, url_key)

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

我们如何发现和防止滥用?
在当前设计中,恶意用户可以通过耗尽所有URL键使我们的服务崩溃。为了防止滥用,我们可以通过api_dev_key限制用户。每个api_dev_key可以被限制为限定时间段内只有限量的URL创建次数和重定向次数(限定时间可以设置为:每个开发人员键有不同的持续时间)。

5.数据库设计

在面试的早期阶段定义数据库模式将有助于我们理解不同组件之间的数据流向,在以后的流程将指导我们进行数据分区。
关于我们将存储的数据的性质的一些观察:
1.我们需要存储数十亿份记录。
2.我们存储的每一个对象都很小(小于1K)。
3.除了存储哪个用户创建了URL之外,记录之间没有任何关系。
4.我们的服务阅读量很大。

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

Hash表
主键:URL: varchar(16)
OriginalURL: varchar(512)
CreationDate: datetime
ExpirationDate: datatime用户表
主键:UserID: int
Name:varchar(20)
Email: varchar(32)
CreationDate:datetime
LastLogin: datatime

我们应该使用什么样的数据库?
由于我们预计存储数十亿行,而且不需要使用对象之间的关系。NoSQL键值存储,比如DynamoDB、Cassandra或Riak是一个更好的选择。NoSQL的选择也更容易扩展。
有关详细信息,请参阅SQLvs NoSQL。

6.基本系统设计与算法

这里我们要解决的问题是,如何为给定的URL生成一个简短且唯一的密钥。在第一节中的tinyurl示例中,缩写的url是“http://tinyurl.com/jlg8zpc”。此URL的后六个字符是我们要生成的短键。

在这里,我们将探讨两种解决方案:
A.对实际的URL编码
我们可以为给定URL计算一个唯一的哈希散列(例如MD5或SHA 256等)。然后,可以对这个哈希散列进行编码以进行显示。这种编码可以是base 36([a-z,0-9]),也可以是base 62([A-Z,a-z,0-9])。如果我们还算上‘-’和‘’,我们可以使用base 64编码。
一个合理的问题是,短键的长度应该是多少?6、8或10个字符。
使用base 64编码,6个字母长键将产生64^6=687亿个可能的字符串;使用base 64编码,8个字母长键将产生64^8=~281万亿个可能的字符串。6个字母键可以产生687亿个唯一字符串,那就假设6个字母键对我们的系统来说就足够了。
如果我们使用MD5算法作为散列函数,它将产生128位哈希值。在base 64编码之后,我们将得到一个包含超过21个字符的字符串(因为每个base 64字符编码了6位的哈希值)。既然我们每个短键只有8个字符的空格,我们将如何选择我们的键?我们可以用前6个字母(或8个字母)作为密钥,但是,这可能会导致密钥重复。在此基础上,我们可以从编码字符串之外选择其他字符或交换一些字符。
我们的解决方案存在什么问题?
我们的编码方案有以下几个问题:
1.如果多个用户输入相同的URL,则可以获得相同的缩短URL,这是不可接受的。
2. 如果URL的某一部分是URL编码呢? e.g., http://www.educative.io/distributed.php?id=design, 和http://www.educative.io/distributed.php%3Fid%3Ddesign 除了URL编码之外,其他内容都是相同的。

解决这些问题的方法:
我们可以在每个输入URL中附加一个递增的序列号,以使其惟一,然后生成它的散列。不过,我们不需要将这个序列号存储在数据库中。这种方法可能存在的问题可能是一个不断增加的序列号。会溢出吗?增加序列号也会影响服务的性能。
另一个解决方案是将用户id(应该是唯一的)附加到输入URL中。但是,如果用户没有登录,我们将不得不要求用户选择一个唯一性密钥。在这之后但凡我们有一个冲突,我们就必须继续生成一个密钥,直到我们得到一个唯一的钥匙。

缩短URL的请求流9个中有1个

B.脱机生成密钥
我们可以有一个独立的密钥生成服务(KGS),它预先生成随机的6个字母字符串,并将它们存储在数据库中(让我们称之为key-DB)。每当我们想要一个短URL,我们只需要拿出一个已经生成的键并使用即可。这种方法将使事情变得非常简单和快速。我们不仅没有编码URL,而且不必担心重复或冲突。KGS将确保插入到key-DB中的所有密钥都是uniqueDB。
那么并发性会导致问题吗?
一旦使用了密钥,就应该在数据库中标记它,以确保不再使用它。如果有多个服务器同时读取密钥,则可能会出现这样的情况,即两个或多个服务器尝试从数据库读取相同的密钥。
如何解决这个并发问题?
服务器可以使用KGS读取/标记数据库中的密钥。KGS可以使用两个表来存储键:一个用于尚未使用的键,另一个用于所有已使用的键。一旦KGS给出其中一个服务器的密钥,它就可以将它们移动到已使用的键表中。KGS总是可以将一些键保存在内存中,以便在服务器需要时能够快速地提供它们。
为了简单起见,只要KGS在内存中加载了一些键,它就可以将它们移动到已使用的键表中。这可以确保每个服务器都获得唯一的密钥。如果KGS在将所有加载的密钥分配给某个服务器之前就挂了,我们将浪费这些密钥–这是可以接受的,因为我们拥有大量的密钥。

KGS还必须确保不向多个服务器提供相同的密钥。为此,它必须在从其中移除键并将键交给一个服务器之前,使用同步(或锁)数据结构来持有这个键

key-DB大小将是多少?
使用base 64编码,我们可以生成68.7B个唯一的6个字母组成的键。如果我们需要一个字节来存储一个字母或数字字符,我们可以将所有这些键存储在:
6(每键字符)*68.7B(唯一键)=412 GB

KGS不是一个单点故障?
是的。为了解决这个问题,我们可以有一个备用的KGS副本。每当主服务器死后,备用服务器就可以负责生成和提供密钥。

每个应用服务器可以从key-DB缓存一些密钥吗?
是的,这肯定能加快速度。尽管在这种情况下,如果应用程序服务器在使用所有密钥之前就死了,我们最终会丢失这些键。这是可以接受的,因为我们有68B唯一的6个字母键。

我们如何执行一个键查找?
我们可以在数据库或键值存储中查找密钥,以获得完整的URL。如果存在,则向浏览器发出“HTTP302重定向”状态,将请求的“位置”字段中存储的URL传递给浏览器。如果该密钥不存在于我们的系统中,则发出“HTTP404未找到”状态或将用户重定向回主页。

我们应该对自定义别名施加大小限制吗?
我们的服务支持自定义别名。用户可以选择他们喜欢的任何“密钥”,但是提供一个自定义别名并不是强制性的。但是,对自定义别名施加大小限制是合理的(而且通常是明智的),以确保我们有一个一致的URL数据库。假设用户可以为每个客户键指定最多16个字符(如上面的数据库模式所示)。

7.数据分区和复制

为了扩展我们的DB,我们需要对它进行分区,以便它能够存储关于数十亿URL的信息。我们需要提出一个分区方案,将我们的数据分割并存储到不同的DB服务器上。

A.基于范围的分区:
我们可以根据URL的第一个字母或哈希键在独立的分区中存储URL。因此,我们在一个分区中保存以字母‘A’开头的所有URL,在另一个分区中保存以字母‘B’开头的URL等等。这种方法称为基于范围的分区。我们甚至可以将某些不常见的字母组合到一个数据库分区中。我们应该想出一个静态的分区方案,这样我们就可以以可预测的方式存储/查找一个文件。
这种方法的主要问题是它会导致服务器的不平衡。例如:我们决定将以字母‘E’开头的所有URL放在一个DB分区中,但后来我们意识到,我们有太多的URL以字母‘E’开头。

B.基于哈希的分区:
在此方案中,我们接受存储对象的散列。然后根据散列计算要使用的分区。在我们的例子中,我们可以使用‘Key’的散列或实际的URL来确定存储数据对象的分区。
我们的散列函数将随机地将URL分布到不同的分区(例如,我们的散列函数总是可以将任何键映射到[1…之间的数字])。),这个数字表示我们存储对象的分区,这种方法仍然可以导致重载分区,这可以通过使用一致的散列来解决。

8.缓存

我们可以缓存经常访问的URL。我们可以使用一些现成的解决方案,比如memcache,它可以用各自的散列存储完整的URL。在进入后端存储之前,应用服务器可以快速检查缓存是否有所需的URL。

我们应该有多少缓存?
我们可以从每天20%的流量开始,根据客户端的使用模式,我们可以调整我们需要多少高速缓存服务器。如上所述,我们需要170 GB内存来缓存20%的每日流量。因为现代的服务器可以有256 GB的内存,所以我们可以很容易地将所有的缓存安装在一台机器上。或者,我们可以使用一些较小的服务器来存储所有这些热URL。

哪种缓存驱逐策略最适合我们的需要?
当缓存已满,并且我们希望用一个更新的/更热的URL替换链接时,我们将如何选择?最近使用最少的LRU对于我们的系统来说是一种合理的策略。在此策略下,我们首先放弃最近使用最少的URL。我们可以使用链接的Hash Map或类似的数据结构来存储我们的URL和散列,这也将跟踪最近访问过的URL。
为了进一步提高效率,我们可以备份缓存服务器,从而在这些服务器之间分配负载。

如何更新每个缓存副本
每当缓存丢失时,我们的服务器就会命中后端数据库。无论何时发生这种情况,我们都可以更新缓存并将新条目传递给所有缓存副本。每个副本都可以通过添加新条目来更新它们的缓存。如果一个副本已经有了这个条目,它可以简单地忽略它。

9.负载均衡器(LB)

我们可以在系统中的三个位置添加一个负载均衡层:
1.客户端与应用程序服务器之间
2.应用服务器与数据库服务器之间
3.应用服务器与缓存服务器之间

最初,我们可以使用一种简单的RoundRobin方法,在后端服务器之间平均分配传入的请求。该LB协议实现简单,不引入任何开销。这种方法的另一个好处是,如果一个服务器挂了,LB就会把它从轮转中取出来,并停止向它发送任何流量。
RoundRobin LB的一个问题是没有考虑到服务器负载。如果服务器超载或缓慢,LB将不会停止向该服务器发送新请求。
为了处理这个问题,可以放置一个更智能的LB解决方案,定期查询后端服务器的负载,并在此基础上调整流量。

10.清除或清除数据库

数据库条目应该永远存在还是应该被清除?如果达到了用户指定的过期时间,那么链接应该发生什么情况呢?
如果我们选择主动搜索过期的链接来删除它们,这将给我们的数据库带来很大的压力。相反,我们可以慢慢删除过期的链接,并做一个懒惰的清理。我们的服务将确保只有过期的链接将被删除,虽然一些过期的链接可能有更长的寿命,但永远不会返回给用户。

·每当用户试图访问过期链接时,我们都可以删除链接并将错误返回给用户。
·一个单独的清理服务可以定期运行,以便从我们的存储中删除过期的链接,并且缓存。此服务应该非常轻量级,并且只能在预期用户流量较低时才能运行。
·我们可以为每个链接设定一个默认的过期时间(例如,两年)。
·删除过期链接后,我们可以将密钥放回要重用的密钥DB中。
·我们是否应该删除一段时间内没有访问过的链接,比如六个月?这可能很棘手。由于存储越来越便宜,我们可以决定永远保持链接。

11.遥测

一个短URL被使用了多少次,用户位置是什么等等?我们将如何存储这些产品?如果它是在每个视图上更新的DB行的一部分,那么当一个热点URL被大量并发请求破坏时会发生什么?一些值得跟踪的统计数据:访问者的国家、访问日期和时间、引用访问页面的单击、浏览器或平台的网页。

12.安全和许可

用户可以创建私有URL或者允许一组特定的用户访问URL吗?
我们可以在数据库中的每个URL中存储权限级别(公共/私有)。我们还可以创建一个单独的表来存储具有查看特定URL的权限的UserID。如果用户没有权限并试图访问URL,我们可以发送一个错误(HTTP 401)返回。考虑到我们使用像Cassandra这样的NoSQL宽列数据库中存储数据,存储权限的表,它的键将是‘Hash’(或KGS生成的‘Key’),列将用来存储那些具有查看URL权限的用户的用户ID。

系统设计 (二)设计类似TinyURL的短URL服务相关推荐

  1. 阿里二面:如何设计与实现短URL服务?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! https://juejin.im/post/684490 ...

  2. jfinal获取url链接上面传来的string类型的值_每个公司都会用的短 URL 服务,怎么设计与实现?...

    点击上方"芋道源码",选择"设为星标" 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 8:55 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | J ...

  3. 答面试官问:如何设计短url服务

    什么是短url 短url, 顾名思义,就是将长网址缩短到一个很短的网址,用户访问这个短网址可以重定向到原本的长网址(还原).这样可以达到易于记忆.转换的目的,还有隐藏链接参数,利于短信推广的作用,常用 ...

  4. 每个公司都会用的短 URL 服务,怎么设计与实现?

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 来源:http://t.cn/AilftMhR 前言 短URL基础原理 服务设计 实现 前言 ...

  5. 短 URL 服务的设计以及实现

      背景  想必大家也经常收到垃圾短信吧--短信中的链接一般都是短链接,类似于下图这样: 为什么这里面的URL都是短的呢?有什么好处呢?怎么做到的呢? 端 URL 的好处 短信和许多平台(微博)有字数 ...

  6. 短 URL 服务,怎么设计与实现?

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 作者 | 呼延十 来源 | http://t.cn/Ai ...

  7. 短URL服务的设计以及实现

    点击上方"后端技术精选",选择"置顶公众号" 技术文章第一时间送达! 作者:呼延十 juejin.im/post/5d10ecab518825795a4d380 ...

  8. 短url服务java_Serverless-实现一个短网址服务(二)

    要点 上一篇 Serverless-实现一个短网址服务(一)实现了一个生成短链接的云函数,这一篇完成后半部分,实现短链接的跳转.主要需要注意的内容有: 腾讯云函数的API调用中如何读取path中的参数 ...

  9. 短 URL 生成器设计:百亿短 URL 怎样做到无冲突

    你好,我是李智慧. 从这节课开始,我们将结合具体的案例,来看看怎么设计高并发系统,你也可以学习具体的软件设计文档写法了.这个模块,我们先来看看,当高并发遇到海量数据处理时的架构. 在社交媒体上,人们经 ...

最新文章

  1. mac 键盘映射优化配置
  2. PLM Integration
  3. Exchange server 2010系列教程之三 发送邮件测试
  4. ASP.NET教程5
  5. IntelliJ IDEA 部署 Web 项目,终于搞懂了!
  6. 微信小程序开发学习笔记002--微信小程序框架解密
  7. 《软件测试技术实战:设计、工具及管理》—第2章 2.7节测试用例不应该包含实际的数据...
  8. 原始尺寸_三维扫描检测,铸件三维全尺寸检测,铸件三维扫描服务
  9. 【CS229】代价函数与梯度下降
  10. java中map的使用和排序使用
  11. 蚂蚁课堂视频笔记思维导图-3期 八、互联网安全架构
  12. 捻花成佛:成为股市高手需要读多少书
  13. Spyder安装教程只需三步_保姆式无基础 2020/11/7最新版
  14. (三)微信小程序云开发之微信支付全解
  15. intern() 方法
  16. 数字化教学资源平台html,数字化教学资源
  17. 关于iOS中UITableView下拉距离短刷新没事,下拉距离长就会崩溃的问题解决方案
  18. 用python画小鸭,Python 处理输入法字库(五笔极点字库转小鸭字库)
  19. 可以学计算机知识的手游,玩手游还能学知识?寓教于乐的游戏可不止《大航海时代》一个...
  20. 里程碑:SpaceX首次成功实现海上回收火箭

热门文章

  1. 计算机学院公章,计算机与信息技术学院公章使用管理条例.DOC
  2. 人工智能机器人实现教学自动化的方法
  3. rhel8系统如何挂载最新的exfat格式u盘
  4. linux删除history记录
  5. 人工智能在未来是怎样改变大都市生活的?
  6. cisco STP协议介绍
  7. 文科生学python数据挖掘难吗_【经验分享】我是如何从文科生零基础一步步走上数据分析之路的...
  8. 2023年不能错过的RPG手游,新老IP齐亮相,哪款最出彩?
  9. 聚簇索引和非聚簇索引的区别
  10. B. Petya and Square