短网址服务你用过吗?如果我们在微博里发布一条带网址的信息,微博会把里面的网址转化成一个更短的网址。我们只要访问这个短网址,就相当于访问原始的网址。比如下面这两个网址,尽管长度不同,但是都可以跳转到我的一个 GitHub 开源项目里。其中,第二个网址就是通过新浪提供的短网址服务生成的。
原始网址:https://github.com/wangzheng0822/ratelimiter4j 短网址:http://t.cn/EtR9QEG
从功能上讲,短网址服务其实非常简单,就是把一个长的网址转化成一个短的网址。作为一名软件工程师,你是否思考过,这样一个简单的功能,是如何实现的呢?底层都依赖了哪些数据结构和算法呢?
# 短网址服务整体介绍
刚刚我们讲了,短网址服务的一个核心功能,就是把原始的长网址转化成短网址。除了这个功能之外,短网址服务还有另外一个必不可少的功能。那就是,当用户点击短网址的时候,短网址服务会将浏览器重定向为原始网址。这个过程是如何实现的呢?
为了方便你理解,我画了一张对比图,你可以看下。

从图中我们可以看出,浏览器会先访问短网址服务,通过短网址获取到原始网址,再通过原始网址访问到页面。不过这部分功能并不是我们今天要讲的重点。我们重点来看,如何将长网址转化成短网址?
如何通过哈希算法生成短网址? 我们前面学过哈希算法。哈希算法可以将一个不管多长的字符串,转化成一个长度固定的哈希值。我们可以利用哈希算法,来生成短网址。
前面我们已经提过一些哈希算法了,比如 MD5、SHA 等。但是,实际上,我们并不需要这些复杂的哈希算法。在生成短网址这个问题上,毕竟,我们不需要考虑反向解密的难度,所以我们只需要关心哈希算法的计算速度和冲突概率。
能够满足这样要求的哈希算法有很多,其中比较著名并且应用广泛的一个哈希算法,那就是MurmurHash 算法。尽管这个哈希算法在 2008 年才被发明出来,但现在它已经广泛应用到 Redis、MemCache、Cassandra、HBase、Lucene 等众多著名的软件中。
MurmurHash 算法提供了两种长度的哈希值,一种是 32bits,一种是 128bits。为了让最终生成的短网址尽可能短,我们可以选择 32bits 的哈希值。对于开头那个 GitHub 网址,经过 MurmurHash 计算后,得到的哈希值就是 181338494。我们再拼上短网址服务的域名,就变成了最终的短网址 http://t.cn/181338494(其中,http://t.cn 是短网址服务的域名)。
# 1. 如何让短网址更短?
不过,你可能已经看出来了,通过 MurmurHash 算法得到的短网址还是很长啊,而且跟我们开头那个网址的格式好像也不一样。别着急,我们只需要稍微改变一个哈希值的表示方法,就可以轻松把短网址变得更短些。
我们可以将 10 进制的哈希值,转化成更高进制的哈希值,这样哈希值就变短了。我们知道,16 进制中,我们用 A~E,来表示 10~15。在网址 URL 中,常用的合法字符有 0~9、a~z、A~Z 这样 62 个字符。为了让哈希值表示起来尽可能短,我们可以将 10 进制的哈希值转化成 62 进制。具体的计算过程,我写在这里了。最终用 62 进制表示的短网址就是http://t.cn/cgSqq。

# 2. 如何解决哈希冲突问题?
不过,我们前面讲过,哈希算法无法避免的一个问题,就是哈希冲突。尽管 MurmurHash 算法,冲突的概率非常低。但是,一旦冲突,就会导致两个原始网址被转化成同一个短网址。当用户访问短网址的时候,我们就无从判断,用户想要访问的是哪一个原始网址了。这个问题该如何解决呢?
一般情况下,我们会保存短网址跟原始网址之间的对应关系,以便后续用户在访问短网址的时候,可以根据对应关系,查找到原始网址。存储这种对应关系的方式有很多,比如我们自己设计存储系统或者利用现成的数据库。前面我们讲到的数据库有 MySQL、Redis。我们就拿 MySQL 来举例。假设短网址与原始网址之间的对应关系,就存储在 MySQL 数据库中。
当有一个新的原始网址需要生成短网址的时候,我们先利用 MurmurHash 算法,生成短网址。然后,我们拿这个新生成的短网址,在 MySQL 数据库中查找。
如果没有找到相同的短网址,这也就表明,这个新生成的短网址没有冲突。于是我们就将这个短网址返回给用户(请求生成短网址的用户),然后将这个短网址与原始网址之间的对应关系,存储到 MySQL 数据库中。
如果我们在数据库中,找到了相同的短网址,那也并不一定说明就冲突了。我们从数据库中,将这个短网址对应的原始网址也取出来。如果数据库中的原始网址,跟我们现在正在处理的原始网址是一样的,这就说明已经有人请求过这个原始网址的短网址了。我们就可以拿这个短网址直接用。如果数据库中记录的原始网址,跟我们正在处理的原始网址不一样,那就说明哈希算法发生了冲突。不同的原始网址,经过计算,得到的短网址重复了。这个时候,我们该怎么办呢?
我们可以给原始网址拼接一串特殊字符,比如“[DUPLICATED]”,然后跟再重新计算哈希值,两次哈希计算都冲突的概率,显然是非常低的。假设出现非常极端的情况,又发生冲突了,我们可以再换一个拼接字符串,比如“[OHMYGOD]”,再计算哈希值。然后把计算得到的哈希值,跟原始网址拼
