HTTP 状态码 301/302 与短链接的实现

之前某厂面试的时候问到过状态码 301/302 的问题,问我有没有相应的实践。当时我只是把概念简单说了下,状态码 3xx 表示重定向,301 是永久重定向,302 是临时重定向,但是实在没有这方面的实践。

今天看了一篇文章,讲短链接的实现,涉及到了 301/302 跳转,感觉面试答这个就非常完美了。分享这篇文章之前,先来简单回顾下这两个状态码。

301 Moved Permanently 永久移动
表示被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个 URI 之一。

后台会在响应头中返回 Location 字段,其中包含了需要重定向的新地址,浏览器接收响应之后会重定向到这个地址。如果没有额外指定,这个响应默认会被浏览器缓存。当下次再请求的时候,浏览器不会向服务器发送请求,而是直接从缓存中获取需要重定向的新地址。

除非是 HEAD 请求,否则响应实体应该包含指向新的 URI 的链接和简短说明。如果既不是 HEAD 也不是 GET 请求,浏览器会禁止自动重定向,除非得到用户确认,因为请求的条件可能发生变化。

客户端发出请求:
GET /bolg HTTP/1.1
Host: www.example.com

服务器响应(不带Cache-Control头部):
HTTP/1.1 301 Moved Permanently
Location: http://www.example.org/index

服务器响应(带Cache-Control头部):
HTTP/1.1 301 Moved Permanently
Location: http://www.example.org/index
Cache-control: private; max-age=600;

302 Moved Temporarily 临时重定向
要求客户端执行临时重定向。由于这样的重定向是临时的,客户端应该继续向原有地址发送请求。

这个响应默认不会被浏览器缓存,只有在 Cache-ControlExpires 中进行指定的情况下,这个响应才会缓存。新的临时性的 URI 应该在响应头的 Location 字段返回。没有指定缓存的情况下,当下次再请求的时候,浏览器会继续发送请求给服务器。

除非是 HEAD 请求,否则响应实体应该包含指向新的 URI 的链接和简短说明。如果既不是 HEAD 也不是 GET 请求,浏览器会禁止自动重定向,除非得到用户确认,因为请求的条件可能发生变化。

客户端发出请求:
GET /bolg HTTP/1.1
Host: www.example.com

服务器响应(不带Cache-Control头部):
HTTP/1.1 302 Moved Temporarily
Location: http://www.example.org/index

服务器响应(带Cache-Control头部):
HTTP/1.1 302 Found
Location: http://www.example.org/index
Cache-control: private; max-age=600;

短链跳转的基本原理

通过浏览器请求抓包之后可以看到,短链接的请求返回状态码 302 与 Location 字段,这个 Location 的值就是对应的长链接。
在这里插入图片描述
浏览器收到这个请求之后,会再请求长链接得到最终的响应,整个过程流程图如下:
在这里插入图片描述
整个步骤就是先访问短链接 A ,然后重定向到长链接 B 。那么问题来了,既然 301 和 302 都可以实现重定向,到底该用哪一个呢?

上面已经提到,301 代表永久重定向,也就是说第一次请求拿到长链接后,下次浏览器再去请求短链接,浏览器不会再去请求短链接的服务器了,而是直接从浏览器缓存里取。这样有一个问题,如果服务器需要获取用户的点击数,如果浏览器缓存了长链接,服务器就没办法获取到短链接的点击数了,所以一般不采用 301 。

302 代表临时重定向,也就是说每次访问短链接都会去请求短链接的服务器(除非响应头中用 Cache-Control 或 Expires 暗示浏览器缓存)。这样虽然给服务器增加了压力,但是便于服务器统计点击数,所以推荐使用 302 。

短链接生成的几种方法

怎样才能生成短链接?通过观察短链接的地址可以发现,短链接是由一个固定短链域名 + 长链接映射成的一串字母组成。那么长链接怎么才能映射成一串字母呢,可以使用哈希函数来实现。
在这里插入图片描述
有的同学可能会说用 MD5 ,SHA 等算法,其实这样有点杀鸡用牛刀了。既然是加密就意味着性能上会有损失,我们其实不关心反向解密的难度,反而更关心的是哈希的运算速度和冲突概率

能够满足这样的哈希算法有很多,这里推荐 Google 出品的 MurmurHash 算法,是一种非加密行哈希函数,适用于一般的哈希检索操作。与其他流行的哈希函数相比,对于规律性较强的 key ,MurmurHash 的随机分布特征表现更好。非加密意味着相比 MD5 ,SHA 这些函数它的性能肯定更高(实际性能是 MD5 等加密算法的十倍以上)。也正是由于这些优点,目前已经广泛应用到 Redis ,MenCache,Cassandra,HBase,Lucene 等众多著名的项目中。

MurmurHash 提供了两种长度的哈希值,32bit 和 128bit 。为了让网址尽可能短,我们选择 32bit ,32bit 能表示的最大值近 43 亿,对于中小型公司的业务绰绰有余。由上面这个长链接的例子,计算得到的哈希值为 3002604296 。于是可以得到短链接为固定短链域名 + 哈希值:

http://gk.link/a/3002604296

有人说这个链接还是有点长,还有一招,3002604296 得到的哈希值是十进制的,可以把它转为 62 进制缩短长度,十进制转为 62 进制如下:
在这里插入图片描述
于是我们得到了一个 62 进制数 3hcCxy ,一下从 10 位缩短到了 6 位!于是得到的短链接为:

http://gk.link/a/3hcCxy

6 位 62 进制数可表示 568 亿的数,应付长链接转换绰绰有余。

如何实现62进制

我们知道 JS 中的 Number.prototype.toString 方法最高只支持转换到 36 进制,想要实现 62 进制,一种方案是使用第三方库,另一种方案就是自己实现:

function indexToChar(i) {
    if (i >=0 && i < 10) {
        return i.toString();
    }
    if (i >= 10 && i < 36) {
        return String.fromCharCode(87+i)
    }
    if (i >= 36 && i < 62) {
        return String.fromCharCode(29+i)
    }
    throw new RangeError("Index out of bounds!"); // 下标越界
}
function convert(num, radix=62) {
    let res = [];
    while (num > radix) {
        let remainder = num % radix;
        res.push(indexToChar(remainder));
        num = Math.floor(num / radix);
    }
    res.push(indexToChar(num));
    return res.reverse().join("");
}
convert(30023323232); // wLQNSU

Number.prototype.toString(number, radix) 可以将十进制数转为其他进制
parseInt(string, radix) 可以将其他进制转为十进制数

如何解决哈希冲突

既然是哈希函数,不可避免地会产生哈希冲突(尽管概率很低),该怎么解决呢?我们知道既然访问短链能跳转到长链,那么两者之间的映射关系一定是要保存起来的,可以使用 Redis 或 MySQL 等,于是就有了一种设计思路:

1. 将长链接经过 MurmurHash 之后得到短链接
2. 根据短链接去数据表中查找是否存在相关记录,如果不存在,将长链接与短链接对应关系插入数据表中存储
3. 如果存在,说明已经有相关记录了,此时在长链接上拼接一个自定义的字段,然后再对这个字符串做第一部操作(如果还重复就继续拼接字符串),到时根据短链取出长链的时候把自定义的字符串移除就是原来的长链

以上的步骤显然是需要优化的,插入一条记录居然要经过两次 sql 查询,如果在高并发下,显然会成为性能瓶颈。

我们可以维护一个 ID 自增生成器,比如 1,2,3 这样的整数 ID 。先将长链转为短链,然后 ID 生成器为其分配一个 ID ,拼接到短链接后面得到最终的短网址。在高并发下,ID 自增生成器的 ID 生成可能会成为系统瓶颈,所以它的设计就显得尤为重要。

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

  • uuid
  • Redis
  • Snowflake
  • MySQL 自增主键
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值