高性能短链设计

前言

今天,咱们来谈谈如何设计一个高性能短链系统,短链系统设计看起来很简单,但每一个点都能展开不少知识点,也是在面试中很是适合考察侯选人的一道设计题,本文将会结合咱们生产上稳定运行两年之久的高性能短链系统给你们简单介绍下设计这套系统所涉及的一些思路,但愿对你们能有一些帮助。html

本文将会从如下几个方面来说解,每一个点包含的信息量都很多,相信你们看完确定有收获mysql

  • 短链有啥好处,为啥要设计它,用长链不香吗
  • 短链跳转的基本原理
  • 短链生成的几种方法
  • 短链的架构支撑

注:里面涉及到很多布隆过滤器,snowflake 等技术,因为不是本文重点,因此建议你们看完后再本身去深刻了解,否则展开讲篇幅会很长面试

短链有啥好处,用长链不香吗

来看下如下极客时间发个人营销短信,点击下方蓝色的连接(短链)redis

浏览器的地址栏上最终会显示一条以下的长链。算法

那么为啥要用短链表示,直接用长链不行吗,用短链的话有以下好外sql

一、连接变短,在对内容长度有限制的平台发文,可编辑的文字就变多了shell

最典型的就是微博,限定了只能发 140 个字,若是一串长链直接怼上去,其余可编辑的内容就所剩无几了,用短链的话,连接长度大大减小,天然可编辑的文字多了很多。数据库

再好比通常短信发文有长度限度,若是用长链,一条短信极可能要拆分红两三条发,原本一条一毛的短信费变成了两三毛,何苦呢。另外用短链在内容排版上也更美观。浏览器

二、咱们常常须要将连接转成二维码的形式分享给他人,若是是长链的话二维码密集难识别,短链就不存在这个问题了,如图示缓存

三、连接太长在有些平台上没法自动识别为超连接

如图示,在钉钉上,就没法识别以下长连接,只能识别部分,用短地址无此问题

短链跳转的基本原理

从上文可知,短链好处多多,那么它是如何工做的呢。咱们在浏览器抓下包看看

能够看到请求后,返回了状态码 302(重定向)与 location 值为长链的响应,而后浏览器会再请求这个长链以获得最终的响应,整个交互流程图以下

主要步骤就是访问短网址后重定向访问 B,那么问题来了,301 和 302 都是重定向,到底该用哪一个,这里须要注意一下 301 和 302 的区别

  • 301,表明 永久重定向,也就是说第一次请求拿到长连接后,下次浏览器再去请求短链的话,不会向短网址服务器请求了,而是直接从浏览器的缓存里拿,这样在 server 层面就没法获取到短网址的点击数了,若是这个连接恰好是某个活动的连接,也就没法分析此活动的效果。因此咱们通常不采用 301。
  • 302,表明 临时重定向,也就是说每次去请求短链都会去请求短网址服务器(除非响应中用 Cache-Control 或 Expired 暗示浏览器缓存),这样就便于 server 统计点击数,因此虽然用 302 会给 server 增长一点压力,但在数据异常重要的今天,这点代码是值得的,因此推荐使用 302!

短链生成的几种方法

一、哈希算法

怎样才能生成短链,仔细观察上例中的短链,显然它是由固定短链域名 + 长链映射成的一串字母组成,那么长链怎么才能映射成一串字母呢,哈希函数不就用来干这事的吗,因而咱们有了如下设计思路

那么这个哈希函数该怎么取呢,相信确定有不少人说用 MD5,SHA 等算法,其实这样作有点杀鸡用牛刀了,并且既然是加密就意味着性能上会有损失,咱们其实不关心反向解密的难度,反而更关心的是哈希的运算速度和冲突几率。

可以知足这样的哈希算法有不少,这里推荐 Google 出品的 MurmurHash 算法,MurmurHash 是一种非加密型哈希函数,适用于通常的哈希检索操做。与其它流行的哈希函数相比,对于规律性较强的 key,MurmurHash 的随机分布特征表现更良好。非加密意味着着相比 MD5,SHA 这些函数它的性能确定更高(实际上性能是 MD5 等加密算法的十倍以上),也正是因为它的这些优势,因此虽然它出现于 2008,但目前已经普遍应用到 Redis、MemCache、Cassandra、HBase、Lucene 等众多著名的软件中。

画外音:这里有个小插曲,MurmurHash 成名后,做者拿到了 Google 的 offer,因此多作些开源的项目,说不定成名后你也能不经意间收到 Google 的 offer ^_^。

MurmurHash 提供了两种长度的哈希值,32 bit,128 bit,为了让网址尽可通地短,咱们选择 32 bit 的哈希值,32 bit 能表示的最大值近 43 亿,对于中小型公司的业务而言绰绰有余。对上文提到的极客长链作 MurmurHash 计算,获得的哈希值为 3002604296,因而咱们如今获得的短链为 固定短链域名+哈希值 = gk.link/a/300260429…

如何缩短域名?

有人说人这个域名仍是有点长,还有一招,3002604296 获得的这个哈希值是十进制的,那咱们把它转为 62 进制可缩短它的长度,10 进制转 62 进制以下:

因而咱们有 (3002604296)10 = (3hcCxy)10,一下从 10 位缩短到了 6 位!因而如今获得了咱们的短链为 gk.link/a/3hcCxy

画外音:6 位 62 进制数可表示 568 亿的数,应付长链转换绰绰有余

如何解决哈希冲突的问题?

既然是哈希函数,不可避免地会产生哈希冲突(尽管几率很低),该怎么解决呢。

咱们知道既然访问访问短链能跳转到长链,那么二者以前这种映射关系必定是要保存起来的,能够用 Redis 或 Mysql 等,这里咱们选择用 Mysql 来存储。表结构应该以下所示

CREATE TABLE `short_url_map` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `lurl` varchar(160) DEFAULT NULL COMMENT '长地址',
  `surl` varchar(10) DEFAULT NULL COMMENT '短地址',
  `gmt_create` int(11) DEFAULT NULL COMMENT '建立时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

因而咱们有了如下设计思路。

  1. 将长链(lurl)通过 MurmurHash 后获得短链。
  2. 再根据短链去 short_url_map 表中查找看是否存在相关记录,若是不存在,将长链与短链对应关系插入数据库中,存储。
  3. 若是存在,说明已经有相关记录了,此时在长串上拼接一个自定义好的字段,好比「DUPLICATE」,而后再对接接的字段串「lurl + DUPLICATE」作第一步操做,若是最后仍是重复呢,再拼一个字段串啊,只要到时根据短链取出长链的时候把这些自定义好的字符串移除便是原来的长链。

以上步骤显然是要优化的,插入一条记录竟然要通过两次 sql 查询(根据短链查记录,将长短链对应关系插入数据库中),若是在高并发下,显然会成为瓶颈。

画外音:通常数据库和应用服务(只作计算不作存储)会部署在两台不一样的 server 上,执行两条 sql 就须要两次网络通讯,这两次网络通讯与两次 sql 执行是整个短链系统的性能瓶颈所在!

因此该怎么优化呢

  1. 首先咱们须要给短链字段 surl 加上惟一索引
  2. 当长链通过 MurmurHash 获得短链后,直接将长短链对应关系插入 db 中,若是 db 里不含有此短链的记录,则插入,若是包含了,说明违反了惟一性索引,此时只要给长链再加上咱们上文说的自定义字段「DUPLICATE」,从新 hash 再插入便可,看起来在违反惟一性索引的状况下是多执行了步骤,但咱们要知道 MurmurHash 发生冲突的几率是很是低的,基本上不太可能发生,因此这种方案是能够接受的。

固然若是在数据量很大的状况下,冲突的几率会增大,此时咱们能够加布隆过滤器来进行优化。

用全部生成的短网址构建布隆过滤器,当一个新的长链生成短链后,先将此短链在布隆过滤器中进行查找,若是不存在,说明 db 里不存在此短网址,能够插入!

画外音:布隆过滤器是一种很是省内存的数据结构,长度为 10 亿的布隆过滤器,只须要 125 M 的内存空间。

综上,若是用哈希函数来设计,整体的设计思路以下

用哈希算法生成的短链其实已经能知足咱们的业务需求,接下来咱们再来看看如何用自增序列的方式来生成短链

二、自增序列算法

咱们能够维护一个 ID 自增生成器,好比 1,2,3 这样的整数递增 ID,当收到一个长链转短链的请求时,ID 生成器为其分配一个 ID,再将其转化为 62 进制,拼接到短链域名后面就获得了最终的短网址,那么这样的 ID 自增生成器该如何设计呢。若是在低峰期发号还好,高并发下,ID 自增生成器的的 ID 生成可能会系统瓶颈,因此它的设计就显得尤其重要。

主要有如下四种获取 id 的方法

一、类 uuid

简单地说就是用 UUID uuid = UUID.randomUUID(); 这种方式生成的 UUID,UUID(Universally Unique Identifier)全局惟一标识符,是指在一台机器上生成的数字,它保证对在同一时空中的全部机器都是惟一的,但这种方式生成的 id 比较长,且无序,在插入 db 时可能会频繁致使页分裂,影响插入性能。

二、Redis

用 Redis 是个不错的选择,性能好,单机可支撑 10 w+ 请求,足以应付大部分的业务场景,但有人说若是一台机器扛不住呢,能够设置多台嘛,好比我布置 10 台机器,每台机器分别只生成尾号0,1,2,... 9 的 ID, 每次加 10便可,只要设置一个 ID 生成器代理随机分配给发号器生成 ID 就好了。

不过用 Redis 这种方案,须要考虑持久化(短链 ID 总不能同样吧),灾备,成本有点高。

三、Snowflake

用 Snowflake 也是个不错的选择,不过 Snowflake 依赖于系统时钟的一致性。若是某台机器的系统时钟回拨,有可能形成 ID 冲突,或者 ID 乱序。

四、Mysql 自增主键

这种方式使用简单,扩展方便,因此咱们使用 Mysql 的自增主键来做为短链的 id。简单总结以下:

那么问题来了,若是用 Mysql 自增 id 做为短链 ID,在高并发下,db 的写压力会很大,这种状况该怎么办呢。

考虑一下,必定要在用到的时候去生成 id 吗,是否能够提早生成这些自增 id ?

方案以下:

设计一个专门的发号表,每插入一条记录,为短链 id 预留 (主键 id * 1000 - 999) 到 (主键 id * 1000) 的号段,以下

发号表:url_sender_num

如图示:tmp_start_num 表明短链的起始 id,tmp_end_num 表明短链的终止 id。

当长链转短链的请求打到某台机器时,先看这台机器是否分配了短链号段,未分配就往发号表插入一条记录,则这台机器将为短链分配范围在 tmp_start_num 到 tmp_end_num 之间的 id。从 tmp_start_num 开始分配,一直分配到 tmp_end_num,若是发号 id 达到了 tmp_end_num,说明这个区间段的 id 已经分配完了,则再往发号表插入一条记录就又获取了一个发号 id 区间。

画外音:思考一下这个自增短链 id 在机器上该怎么实现呢, 能够用 redis, 不过更简单的方案是用 AtomicLong,单机上性能不错,也保证了并发的安全性,固然若是并发量很大,AtomicLong 的表现就不太行了,能够考虑用 LongAdder,在高并发下表现更加优秀。

总体设计图以下

解决了发号器问题,接下来就简单了,从发号器拿过来的 id ,即为短链 id,接下来咱们再建立一个长短链的映射表便可, 短链 id 即为主键,不过这里有个须要注意的地方,咱们可能须要防止屡次相同的长链生成不一样的短链 id 这种状况,这就须要每次先根据长链来查找 db 看是否存在相关记录,通常的作法是根据长链作索引,但这样的话索引的空间会很大,因此咱们能够对长链适当的压缩,好比 MD5,再对长链的 MD5 字段作索引,这样索引就会小不少。这样只要根据长链的 md5 去表里查是否存在相同的记录便可。因此咱们设计的表以下

CREATE TABLE `short_url_map` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '短链 id',
  `lurl` varchar(10) DEFAULT NULL COMMENT '长链',
  `md5` char(32) DEFAULT NULL COMMENT '长链md5',
  `gmt_create` int(11) DEFAULT NULL COMMENT '建立时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码

固然了,数据量若是很大的话,后期就须要分区或分库分表了。

请求短链的高并发架构设计

在电商公司,常常有不少活动,秒杀,抢红包等等,在某个时间点的 QPS 会很高,考虑到这种状况,咱们引入了 openResty,它是一个基于 Nginx 与 Lua 的高性能 Web 平台,因为 Nginx 的非阻塞IO模型,使用 openResty 能够轻松支持 100 w + 的并发数,通常状况下你只要部署一台便可,同时 openResty 也自带了缓存机制,集成了 redis 这些缓存模块,也能够直接连 mysql。不须要再经过业务层连这些中间件,性能天然会高很多

如图示,使用 openResty 省去了业务层这一步,直达缓存层与数据库层,也提高了很多性能。

总结

本文对短链设计方案做了详细地剖析,旨在给你们提供几种不一样的短链设计思路,文中涉及到挺多像布隆过滤器,openRestry 等技术,文中没有展开讲,建议你们回头能够去再详细了解一下。再好比文中提到的 Mysql 页分裂也须要对底层使用的 B+ tree 数据结构,操做系统按页获取等知识有比较详细地了解,相信你们各个知识点都吃透后会收获不小。

巨人的肩膀

www.cnblogs.com/rjzheng/p/1…

time.geekbang.org/column/arti…

欢迎你们扫码关注公众号,一块儿探讨

相关文章
相关标签/搜索