Data Structures and Algorithm读书简友广场

如何实现一个短网址系统?

2022-04-08  本文已影响0人  Sun东辉

短网址服务你用过吗?如果我们在微博里发布一条带网址的信息,微博会把里面的网址转化成一个更短的网址。我们只要访问这个短网址,就相当于访问原始的网址。比如下面这两个网址,尽管长度不同,但是都可以跳转到 GitHub Docker 项目里。其中,第二个网址就是通过短网址服务生成的。

原网址: <https://github.com/jenkinsci/docker>
短网址: <https://reurl.cc/2DnrQ9>

从功能上讲,短网址服务其实非常简单,就是把一个长的网址转化成一个短的网址。作为一名软件工程师,你是否思考过,这样一个简单的功能,是如何实现的呢?底层都依赖了哪些数据结构和算法呢?

短网址服务整体介绍

短网址服务的一个核心功能,就是把原始的长网址转化成短网址。除了这个功能之外,短网址服务还有另外一个必不可少的功能。那就是,当用户点击短网址的时候,短网址服务会将浏览器重定向为原始网址。这个过程是如何实现的呢?

其实,原理并不复杂:浏览器会先访问短网址服务,通过短网址获取到原始网址,再通过原始网址访问到页面。现在,我们重点来讨论,如何将长网址转化成短网址?

如何通过哈希算法生成短网址?

哈希算法可以将一个不管多长的字符串,转化成一个长度固定的哈希值。我们可以利用哈希算法,来生成短网址,比如 MD5、SHA 等。但是,实际上,我们并不需要这些复杂的哈希算法。在生成短网址这个问题上,毕竟,我们不需要考虑反向解密的难度,我们只需要关心哈希算法的计算速度和冲突概率。能够满足这样要求的哈希算法有很多,其中比较著名并且应用广泛的一个哈希算法,那就是MurmurHash 算法。尽管这个哈希算法在 2008 年才被发明出来,但现在它已经广泛应用到 Redis、MemCache、Cassandra、HBase、Lucene 等众多著名的软件中。

1. 如何让短网址更短?

MurmurHash 算法提供了两种长度的哈希值,一种是 32bits,一种是 128bits。为了让最终生成的短网址尽可能短,我们可以选择 32bits 的哈希值。但即使是选择 32 bits 的哈希值,通过 MurmurHash 算法得到的短网址还是很长,而且跟我们开头那个网址的格式好像也不一样。别着急,我们只需要稍微改变一个哈希值的表示方法,就可以轻松把短网址变得更短些。

我们可以将 10 进制的哈希值,转化成更高进制的哈希值,这样哈希值就变短了。我们知道,16 进制中,我们用 A~F,来表示 10~15。在网址 URL 中,常用的合法字符有 0~9、a~z、A~Z 这样 62 个字符。为了让哈希值表示起来尽可能短,我们可以将 10 进制的哈希值转化成 62 进制。

2. 如何解决哈希冲突问题?

我们都知道,哈希算法无法避免的一个问题,就是哈希冲突。尽管 MurmurHash 算法,冲突的概率非常低。但是,一旦冲突,就会导致两个原始网址被转化成同一个短网址。当用户访问短网址的时候,我们就无从判断,用户想要访问的是哪一个原始网址了。这个问题该如何解决呢?

一般情况下,我们会保存短网址跟原始网址之间的对应关系,以便后续用户在访问短网址的时候,可以根据对应关系,查找到原始网址。存储这种对应关系的方式有很多,比如我们自己设计存储系统或者利用现成的数据库。

我们就拿 MySQL 来举例,假设短网址与原始网址之间的对应关系,就存储在 MySQL 数据库中。当有一个新的原始网址需要生成短网址的时候,我们先利用 MurmurHash 算法,生成短网址。然后,我们拿这个新生成的短网址,在 MySQL 数据库中查找。

当用户访问短网址的时候,短网址服务先通过短网址,在数据库中查找到对应的原始网址。如果原始网址有拼接特殊字符(这个很容易通过字符串匹配算法找到),我们就先将特殊字符去掉,然后再将不包含特殊字符的原始网址返回给浏览器。

3. 如何优化哈希算法生成短网址的性能?

为了判断生成的短网址是否冲突,我们需要拿生成的短网址,在数据库中查找。如果数据库中存储的数据非常多,那查找起来就会非常慢,势必影响短网址服务的性能。那有没有什么优化的手段呢?

还记得我们之前讲的 MySQL 数据库索引吗?我们可以给短网址字段添加 B+ 树索引。这样通过短网址查询原始网址的速度就提高了很多。实际上,在真实的软件开发中,我们还可以通过一个小技巧,来进一步提高速度。

在短网址生成的过程中,我们会跟数据库打两次交道,也就是会执行两条 SQL 语句。第一个 SQL 语句是通过短网址查询短网址与原始网址的对应关系,第二个 SQL 语句是将新生成的短网址和原始网址之间的对应关系存储到数据库。我们知道,一般情况下,数据库和应用服务(只做计算不存储数据的业务逻辑部分)会部署在两个独立的服务器或者虚拟服务器上。那两条 SQL 语句的执行就需要两次网络通信。这种 IO 通信耗时以及 SQL 语句的执行,才是整个短网址服务的性能瓶颈所在。所以,为了提高性能,我们需要尽量减少 SQL 语句。那又该如何减少 SQL 语句呢?

我们可以给数据库中的短网址字段,添加一个唯一索引(不只是索引,还要求表中不能有重复的数据)。当有新的原始网址需要生成短网址的时候,我们并不会先拿生成的短网址,在数据库中查找判重,而是直接将生成的短网址与对应的原始网址,尝试存储到数据库中。如果数据库能够将数据正常写入,那说明并没有违反唯一索引,也就是说,这个新生成的短网址并没有冲突。

当然,如果数据库反馈违反唯一性索引异常,那我们还得重新执行刚刚讲过的“查询、写入”过程,SQL 语句执行的次数不减反增。但是,如果业务中违反唯一性索引的概率很小,大多数情况下,我们只需要把新生成的短网址和对应的原始网址,插入到数据库,并不会出现冲突。所以,大部分情况下,我们只需要执行一条写入的 SQL 语句就可以了。所以,从整体上看,总的 SQL 语句执行次数会大大减少。

实际上,我们还有另外一个优化 SQL 语句次数的方法,那就是借助布隆过滤器。我们把已经生成的短网址,构建成布隆过滤器。我们知道,布隆过滤器是比较节省内存的一种存储结构,长度是 10 亿的布隆过滤器,也只需要 125MB 左右的内存空间。

当有新的短网址生成的时候,我们先拿这个新生成的短网址,在布隆过滤器中查找。如果查找的结果是不存在,那就说明这个新生成的短网址并没有冲突。这个时候,我们只需要再执行写入短网址和对应原始网页的 SQL 语句就可以了。通过先查询布隆过滤器,总的 SQL 语句的执行次数减少了。

如何通过 ID 生成器生成短网址?

上面已经讨论了如何利用哈希算法来生成短网址的思路,实际上,我们还有另外一种短网址的生成算法,那就是利用自增的 ID 生成器来生成短网址。我们接下来就看一下,这种算法是如何工作的?对于哈希算法生成短网址来说,它又有什么优势和劣势?

我们可以维护一个 ID 自增生成器。它可以生成 1、2、3…这样自增的整数 ID。当短网址服务接收到一个原始网址转化成短网址的请求之后,它先从 ID 生成器中取一个号码,然后将其转化成 62 进制表示法,拼接到短网址服务的域名后面,就形成了最终的短网址。最后,我们还是会把生成的短网址和对应的原始网址存储到数据库中。

理论非常简单好理解。不过,这里有几个细节问题需要处理。

1. 相同的原始网址可能会对应不同的短网址

每次新来一个原始网址,我们就生成一个新的短网址,这种做法就会导致两个相同的原始网址生成了不同的短网址。这个该如何处理呢?实际上,我们有两种处理思路。

2. 如何实现高性能的 ID 生成器?

实现 ID 生成器的方法有很多,比如利用数据库自增字段。当然我们也可以自己维护一个计数器,不停地加一加一。但是,一个计数器来应对频繁的短网址生成请求,显然是有点吃力的(因为计数器必须保证生成的 ID 不重复,笼统概念上讲,就是需要加锁)。如何提高 ID 生成器的性能呢?关于这个问题,实际上,有很多解决思路。我这里给出两种思路。

上一篇下一篇

猜你喜欢

热点阅读