技术干货 | 高性能短链设计与实现

什么是URL短链

URL 短链,就是把原来较长的网址,转换成比较短的网址。以下面这条短信为例:

上图中,https://dx.10086.cn/looGDg 就是一条短链。用户点击蓝色的链接,就可以在浏览器中看到它对应的原网址:

http://wap.zj.10086.cn/case/mould/produce/9b032ecfcb9b42f6a8fc411d160d91e320200722001_750.html?chid_code=9c6d64&WT.mc_id=20210205zthgcsdx

为何要使用短链?先来看看短链能带来哪些好处:

  • 内容平台上(如微博、Twitter、小红书等),往往发文有长度限制,短链带来的好处不言而喻: 网址短、美观、便于发布、传播,能写更多正文了;

  • 短信,如果超过字数限制,就会被拆成多条发送,如果短信量大也可以省下不少钱;

  • 二维码,本质上也是一串 URL,长链的话二维码密集难识别,而短链则不会存在这种问题;

  • 安全性,不想过于直观的暴露原始网址;

  • 统一经过平台重定向,能对数据进行运营分析

短链跳转的原理

我们还是以上面的短链为例,原理如图:

流程可以分为三步:

第一步:浏览器发起请求,请求地址:https://dx.10086.cn/looGDg

第二步: 短链服务器收到请求后,通过短链接地址找到原始长链接。返回http状态码是301或302,同时也通过 location 响应头告知客户端:你要访问的其实是下面这个长网址:http://wap.zj.10086.cn/case/mould/produce/9b032ecfcb9b42f6a8fc411d160d91e320200722001_750.html?chid_code=9c6d64&WT.mc_id=20210205zthgcsdx

第三步: 客户端收到短链服务器的应答后,再跳转至长网址:http://wap.zj.10086.cn/case/mould/produce/9b032ecfcb9b42f6a8fc411d160d91e320200722001_750.html?chid_code=9c6d64&WT.mc_id=20210205zthgcsdx

实际浏览器中的网络请求如下图

 

这里简单回顾一下http状态码301和302的区别:

  • 301,代表 永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链的话,不会向短网址服务器请求了,而是直接从浏览器的缓存里拿,在 server 层面就无法获取到短网址的点击数了,如果这个链接刚好是某个活动需要统计点击率,那结果只可能是0或1了。所以一般不采用 301。

  • 302,代表 临时重定向,也就是说每次去请求短链都会去请求短网址服务器,便于 server 统计点击数。虽然用 302 会给 server 增加一点压力,但是更符合业务需要,所以推荐使用 302。

短链场需求分析

短链服务需求分析:

功能性需求:

  • 原始URL转短链
  • 访问短链跳转到原始URL
  • 短URL访问过期
  • 短URL非法失效
  • 自定义短链接
  • 短链的批处理
  • 访问统计
  • 支持B端多租户

非功能性需求:

  • 高可用:支持分布式服务下的高可用
  • 高性能:无论是生成短链,还是访问短链,服务都要支持高并发、低延时
  • 安全性:短链不可被预测,避免接口被轮询导致数据泄露等风险
系统容量预估

读写估算、存储估算、带宽估算、内存估算、缓存估算、总体估算:

存储预估:

  1. 假设,系统接入的某个业务一天生成 100万短链接。那么一年下来预估为:100万*30 *12 = 3.6亿
  2. 假设短链需要保存五年,且类似体量业务接入10个。存储五年的量预估为: 3.6* 5 * 10=180亿数据量
  3. 假设一条核心数据(长URL、短URL、创建时间和过期时间等信息)大小为0.5kb。那么存储180亿短链所需:180 * 0.5KB = 18T

读写预估:

假设这一百万数据需要在一小时内发完,那么我们单机能承受负载可以这么来估算:

  • 每秒写请求:

1000000 / 1 hour* 60 min * 60 sec = 278(Req/S)

  • 每秒读请求(读多写少,我们按照读写100 : 1 来考虑):

278 * 100 = 27800 (Req/S)

缓存预估:

缓存这部分计算可以参照读写请求的预估值,考虑业务的集中访问时间进行衡量。另外,如上文所提的场景,读远高于写的请求可以考虑加内存缓存,命中率会非常的高,这样对读性能又是一个显著提升。

短链长度预估:6位,可使用 短链 568亿

详细设计

短链生成算法

设计短链生成算法,本质就是找到f(x),寻找“长”、“短”之间的映射关系,这就是短链算法的根本。

 

方案1:Hash 算法

首先,最容易想到的就是MD5、SHA等算法。

输入的长 URL 经过 MD5 算法的处理,会输出一串长度为 128 bit 的字符串。128 bit 的 MD5 通过 Base62的规则编码(A-Za-z0-9),会生成22位的字符串。显然22位还是太长了。那么如何拿到6位短链呢?

首先想到截取,假设按照转换出来截取它的前六位或者后六位,两个完全不一样的长链转换出来的字符它的前6位或者后6位一样的概率还是很高的。就会导致上面的“关系”错乱,两个长链对应了一个短链上。

另外有同学会说,在发现冲突的时候加随机或者加时间戳,或者加IP等等。方案改进可以降低冲突概率,但是还是会存在。如果冲突一直在,就要重复执行这段逻辑。

可以看出MD5这种方式不是很优雅,而且在效率上也不高。

方案2:自增序列算法

区别于Hash算法,全局自增ID方案的好处是不需要关心原始长链接。天然支持一对一这种映射关系。不会存在之前Hash方案中一对多这种问题。

所以初步的想法通过一个ID自增生成器,可以通过数据库ID自增、zookeeper计数或者 redis的 incby或者自己实现一个自增计数器都行。程序收到请求后去拿一个ID,然后将这个ID通过Base62转换成短链,然后插入这个映射关系。

但是方案2会存在以下几个问题。

问题1:每次来一个请求,比如说长链是一样的。那势必也会生成一个ID。那如果有人刷接口或者重复提交,那很快ID资源将会耗尽。

问题2:如果是自增的ID,规律性太强,很容易被人预测,导致信息泄露。

该如何解决这些问题呢。

首先是问题1,可以通过对长链加唯一索引解决,但是会加大存储空间,另一面因字段内容过多会导致数据库page记录减少,当发生插入或者删除等操作很容易产生分页,影响性能。所以可以考虑用 布隆过滤器(Bloom Filter),当长链不存在时程序才给分配。

问题2相对容易一点,可以增加一些随机性,在规则上做一些操作。比如加上数据中心,机器IP等,而不是简单的加一加二。

所以综上考虑使用 Snowflake + Bloom Filter 来解决核心算法问题。篇幅有限,如果不清楚细节的可以自行深入了解下。

架构设计

 

整体架构如图。自上而下,首先网络层,在Nginx做读写域名分离,考虑到响应、安全等问题,创建、访问统计、详情等请求走内网域名,访问短链跳转走公网域名。

整个短链中心结构大体分为三层,系统安全、应用安全及业务处理。首先请求进入限流中心,规则基于服务流控、业务线流控及租户维度流控,当系统发生异常时我们可动态调节系统流控,保证整体系统的稳定,提供能力。当某个业务线或者某个租户出现异常流量或者被刷等情况之后,我们可以考虑对某个租户限制访问,限制该租户的请求,避免连锁反应,导致系统崩盘。安全中心将对进入的短链进行一些规则校验,比如长度、字符内容等,如一些非法恶意的请求直接做拦截处理。

如果该请求是创建等接口,将会对传入的Appkey和AppSecret进行鉴权校验,如果校验通过,基于短链生成算法,生成短链接,存储到数据库,完成数据主从集群备份,同时异步更新到Redis缓存集群并且加载到内存缓存,同时更新到过滤中心服务。如果该请求是访问查询请求,程序将先判断该短链是否在布隆过滤器内。若存在,则先走内存缓存然后Redis最终到DB,中间存在则立即返回;若不存在,则直接认定为非法短链。所以过滤中心非常重要,是维稳下层中间件安全的核心能力,防止异常流量对缓存或者数据库造成压力,进而引起雪崩。

数据库表结构设计

短链表设计核心字段如下:

保障系统稳定性

作为一个基础组件,必须要保证高并发的同时,还要考虑服务的稳定性及高可用。

首先是安全:

在服务上线之初,需要考虑几点。接口分类上大体分成两类,第一类是用户可以访问的短链接,另一类是用户无需感知且不能被感知的比如创建、查看详情、访问统计等等。首先要做的就是做好网络隔离,将用户可以访问的提供公网访问,不可访问的将网络限制机房或者办公网访问等。其次,接入的业务方需要分配指定的Appkey和AppSercert,核心接口调用进行必要的签名校验。防止一些恶意攻击流量对系统造成影响。

其次是限流:

为了让系统更加的健壮,这个时候就要考虑一些限流策略。可以针对整个服务或者接口做一些限流策略,可以基于Guava的 RateLimiter或者在网关层做一些限制。防止业务流量飙升的时候,保证服务是稳定可用的。

最后就是防刷:

首先过滤掉一批非法请求,将传入的短链进行正则检验,不满足直接过滤。满足校验规则,通过布隆过滤器,如果外部请求某个短链不存在系统中,那直接过滤掉,避免一些恶意的请求进入到系统造成影响。短链需要考虑接入一些安全服务,对长链内容进行一些检测,防止涉黄涉暴等内容发生,封禁之后影响整个域名的可用性。所以需要在设计之初,考虑快速拉黑失效短链的能力。

总结

本文详细介绍了短链的设计方案,阐述了短链设计原理、容量评估、具体设计方案及稳定性等方面的内容。我们基于业务场景需求分析,针对如何设计一款基础好用的短链服务这一话题进行了讨论。笔者基于文中原理使用Go语言编写了一套短链服务,在性能及稳定性上都有很强的表现。目前该服务已接入多个业务,线上运行稳定,逐步取代原先的单体服务。

想要开发一款基础服务,在功能、性能和安全上都必须进行多方位考虑。文中简要提到了 布隆过滤器、SnowFlake、Mysql 页分裂等技术,有兴趣的开发者们可以持续关注。

本文对短链服务设计做了详细地剖析,旨在给大家提供短链设计思路。如果你正在设计或者考虑设计一款短链服务,希望本文对你有所帮助。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值