网页短链接实现原理探究
2019-12-01

事情是这样的,今天一人问我一个问题,但是我懒得在说,就在网上找了一篇博客通过QQ发送给他,但是在发送链接时我发现之前很长的链接变成了短链接,且这个短链接能够正常访问之前的长链接,好奇之下就有了这篇文章.

什么是短链接?

我的理解就是通过一定的算法和技术实现将原本很长的网址转换为较短的网址,从而便于用户记忆和在互联网上的传播.常用于有字数约束的微博,二维码等场景.

现在很多公司都提供了短链接服务,比如百度,新浪微博等等,以供用户自由方便的生成短链接.

短链接的大致整体流程

今天上午我找的原本链接是这个:

https://blog.wpjam.com/m/scripts-and-plugins-for-analyzing-website-traffic-stats/生成的短链接(长期的话可能会无效):

http://url.cn/5r8GoSZ大致流程是这样的:我复制(输入)了一个长链接,通过腾讯服务器的转化后得到一个以http://url.cn开头的短链接,然后我可以将该网址在互联网上进行分享和传播,其他人在访问该短网址可以进入到之前原本长网址对应的页面.

所以要想将生成短链接,我们需要注意两个问题:

    如何将任意长的字符串转化为较短长或者固定长的字符串. 如何将短链接还原成之前的长链接,使之能够访问.

算法实现

Hash实现

通过一定方式将任意长的文本转化为一个固定长的字符串,只要目标文本长度适当,那么我们对于不同的输入通过哈希几乎(注意是几乎)不可能得到对应同一个字符串.通过对长链接进行Hash运算,将Hash值作为这个长链接的唯一标示.但是通过Hash实现可能会造成碰撞.不一样的长网址缩短成了同一个短网址,那也就做不到复原了.

对于碰撞问题,有一种缓冲方法就是在呈现碰撞了以后后边在增加随机字符,随机字符的增加能够缓解碰撞的疑问,但是这终究是一种缓冲的办法,没有彻底解决碰撞. 

自增序列算法(永不重复算法)

我们可以设置一个自增id,对于每一个新的长链接给他一个不重复的id.

原理:当服务器接收到一个网址时首先检验这个网址在服务器中是否再存,如果不存在,存储这个新网址并分发一个id,这个id设置成自增,保证了每一个存储的网址的id都是唯一标示.比如上面的,当一个链接过来时,给这个链接发一个0,再有一个链接过来时,给后面这个链接一个1,以此类推.

数据实现:我们发现短链接后面的参数好像都是定长的,但是如果通过id进行时,参数不定长,且随着id的自增,可能会出现这种情况:url.cn/10000000.我们可以将十进制的id转化为多进制,比如在以"0-9a-z"这36个字符表示的36进制中,一亿可以被表示为1njchs,基本实现不重复够用.如果数据量更大,我们可以采用62进制进行转化:

短址的长度一般设为 6 位,而每一位是由 [a - z, A - Z, 0 - 9] 总共 62 个字母组成的,所以 6 位的话,总共会有 62^6 ~= 568亿种组合。

存储实现:

对于小型系统,简单的mysql系统的表自增索引即可实现(注意自增id数据类型,int只能到65535)

大型系统可以搭建分布式key-value系统进行存储.

我使用mysql简单建了一张表,用于保存长网址的数据,只有两个字段,一个是主键用于保存id,一个url字段用于存放原始的长网址.在进行长网址转换时,先检查数据表中是否存在该长网址,如果存在直接获取该记录的id,否在创建一条新的记录并返回该记录的id,对于这id进行进制转化处理后拼接到准备好的域名后面得到一个对应的短网址返回给用户.

这里我简单模仿了一个转换短链接的功能:

url.php:用于模拟数据库存储

<?phpreturn array( "http://test1.com/12345vn6", "http://test2.com/1234gf56", "http://teat1.com/123ssgg456", "http://test1.com/1234svss56", "http://tefasfd1.com/123456", "http://tesddt1.com/12sss3456", "http://tehghdgst1.com/123dsaf456", "http://tedddst1.com/12SDsd3456", "http://testaa1bb.com/1234ccgfryh56", "http://testaa1dfd.com/1234ccgfryh56", "http://testaa1.com/1234ccgfryh56", "http://testaa1.cssom/1234ccgfryh56", "http://testaa1.com/1234ccgfryh56", "http://testraa1.com/1234ccgfryzh56", "http://teffstaa1.com/1234ccgfsryh56", "http://testaxxa1.com/1234ccgfrsryh56", "http://testaa1ll.com/1234ccgyrfryh56", "http://testaa1.com/1234ccgfryyh56", "http://tesbbtaa1.com/1ss234cjcgfryh56",)?>get.php:用于模拟生成短链接

<?php require "./jinzhi.php"; $arr = include("./url.php"); $host = "http://url.cn/"; $url = $_SERVER["HTTP_HOST"] . $_SERVER["SCRIPT_NAME"]; $keyId = in_array($url, $arr) ? array_search($url, $arr) : (array_push($arr, $url) - 1); $toKey = get_char($keyId); echo $host . $toKey;?>jinzhi.php:模拟进制转化

<?php/** * @desc im:十进制数转换成三十六机制数 * @param (int)$num 十进制数 * return 返回:三十六进制数*/function get_char($num) { $num = intval($num); if ($num <= 0) return false; $charArr = array("0","1","2","3","4","5","6","7","8","9","A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"); $char = ""; do { $key = ($num - 1) % 36; $char= $charArr[$key] . $char; $num = floor(($num - $key) / 36); } while ($num > 0); return $char;}/** * @desc im:三十六进制数转换成十机制数 * @param (string)$char 三十六进制数 * return 返回:十进制数 */function get_num($char){ $array=array("0","1","2","3","4","5","6","7","8","9","A", "B", "C", "D","E", "F", "G", "H", "I", "J", "K", "L","M", "N", "O","P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y","Z"); $len=strlen($char); for($i=0;$i<$len;$i++){ $index=array_search($char[$i],$array); $sum+=($index+1)*pow(36,$len-$i-1); } return $sum;}?>进行路由请求:http://localhost:4000/get.php/10000000

输出:http://url.cn/J

至于解析短链接跳转至原有链接,只是对上面思路进行取反.

摘要算法

实现思路:

    将长网址 md5 生成 32 位签名串,分为 4 段, 每段 8 个字节对这四段循环处理, 取 8 个字节, 将他看成 16 进制串与 0x3fffffff(30位1) 与操作, 即超过 30 位的忽略处理这 30 位分成 6 段, 每 5 位的数字作为字母表的索引取得特定字符, 依次进行获得 6 位字符串总的 md5 串可以获得 4 个 6 位串,取里面的任意一个就可作为这个长 url 的短 url 地址

这种算法虽然会生成四个短链接,但是存在重复几率.

算法对比

采用自增序列的好处就是简单好理解易操作.但是由于id随着增大长度不固定,但是这个问题可以通过让id从指定的数字开始递增即可以解决.还有一个问题就是我们使用的短码是有序的,可能会存在安全方面的问题.当然相关的防护手法也有很多,比如签名验证之类的安全策略;我们也可以自己实现安全手法,比如从一个随机中心值进行开端计数,然后选用一些校检位算法计算出固定位的校检码,将其连接起来,得到固定长不递增的短码.

第二种算法存在碰撞的问题,虽然产生重复(碰撞)的几率很小.但是也采用这种算法也有一个好处就是短码的位数是固定的,不会从一位到多位.

所以这两种算法各有千秋,如果事务所需要的短链接有效期较短,那么通过批处理定期清洗,那么用摘要算法也不错.而自增算法能够确保任何恳求量都不会呈现冲突也不失一种非常好的解决算法.

重定向的问题(301还是302)

短链接重定向的执行过程:

    用户访问短链接:https://dwz.cn/9WnR9Qcx短链接服务器dwz.cn收到请求,根据URL路径9WnR9Qcx获取到原始的长链接:http://www.lishanlei.cn/服务器返回状态码,将响应头中的Location设置为:http://www.lishanlei.cn/浏览器重新向http://www.lishanlei.cn/发送请求返回响应

Request URL: https://dwz.cn/9WnR9QcxRequest Method: GETStatus Code: 302 FoundRemote Address: 220.181.164.108:443Referrer Policy: no-referrer-when-downgradeAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Headers: Origin,Accept,Content-Type,X-Requested-WithAccess-Control-Allow-Methods: POST,GET,PUT,PATCH,DELETE,HEADAccess-Control-Allow-Origin: Content-Length: 47Content-Type: text/html; charset=utf-8Date: Wed, 03 Oct 2018 05:42:18 GMTLocation: http://www.lishanlei.cn/那么服务器在返回状态码时应该选取301还是302呢?

301是永久重定向,而302是临时重定向.

如果选取301,短链接生成以后就不会变化,所以用301符合http语义,这样对服务器的压力会有所减少.但是这样一来,我们就无法统计短地址被点击的次数了.

而选择302会增加服务器的压力,但是我们可以统计短链接被点击的次数,这些数据可能对于公司的发展规划非常重要.

综上所述,我认为更好的应该选择302

 

End