6、自制短链系统
最后更新于:2022-04-02 06:10:13
短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉。
* 原始地址:[https://github.com/pwstrick/daily](https://github.com/pwstrick/daily)
* 短链:http://t.cn/4fYKXF
但如果二维码图包含的是一条短链,那么只要修改短链中的映射关系,就能不发代码了。当然了,前提是有一套短链系统维护着他们之间的关系,下图15和图16分别是列表和新增的界面。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/96/a7/96a79a41ccc3201527d1ea7d286cbde4_1894x1030.png =800x)
图 15
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/a3/fc/a3fc68054b01dd9fef8723b0cdb5b505_1608x526.png =600x)
图 16
前端界面的代码省略了,直接看短链用Node.js实现的后端代码。
## 一、MySQL
在 web\_short\_chain 表中,主键 id 是一个自增的整数,short 字段存储着短链中的 key,也就是 http://t.cn/4fYKXF 中的 4fYKXF 之类的数据,并且是全表唯一的,目前还未对其建索引。
~~~
CREATE TABLE `web_short_chain` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`short` varchar(10) COLLATE utf8mb4_bin NOT NULL COMMENT '短链地址中的key',
`url` varchar(200) COLLATE utf8mb4_bin NOT NULL COMMENT '原始地址',
`ctime` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`mtime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态',
PRIMARY KEY (`id`),
UNIQUE KEY `short_UNIQUE` (`short`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='短链存储'
~~~
## 二、计算 short 的值
需要两步才能将原始地址映射成短链地址,第一步是使用[MurmurHash](https://baike.baidu.com/item/Murmur%E5%93%88%E5%B8%8C/22689658?fr=aladdin)(么么哈希)算法,由Austin Appleby在2008年发明,可将原始地址转换成一个哈希值,算法如下(最新版本 MurmurHash3)。
~~~
function MurmurHashV3(key, seed) {
if (typeof key === "string") key = createBuffer(key);
var remainder, bytes, h1, h1b, c1, c1b, c2, c2b, k1, i;
remainder = key.length & 3; // key.length % 4
bytes = key.length - remainder;
h1 = seed;
c1 = 0xcc9e2d51;
c2 = 0x1b873593;
i = 0;
while (i < bytes) {
k1 =
(key[i] & 0xff) |
((key[++i] & 0xff) << 8) |
((key[++i] & 0xff) << 16) |
((key[++i] & 0xff) << 24);
++i;
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
h1 ^= k1;
h1 = (h1 << 13) | (h1 >>> 19);
h1b = ((h1 & 0xffff) * 5 + ((((h1 >>> 16) * 5) & 0xffff) << 16)) & 0xffffffff;
h1 = (h1b & 0xffff) + 0x6b64 + ((((h1b >>> 16) + 0xe654) & 0xffff) << 16);
}
k1 = 0;
switch (remainder) {
case 3:
k1 ^= (key[i + 2] & 0xff) << 16;
case 2:
k1 ^= (key[i + 1] & 0xff) << 8;
case 1:
k1 ^= key[i] & 0xff;
k1 = ((k1 & 0xffff) * c1 + ((((k1 >>> 16) * c1) & 0xffff) << 16)) & 0xffffffff;
k1 = (k1 << 15) | (k1 >>> 17);
k1 = ((k1 & 0xffff) * c2 + ((((k1 >>> 16) * c2) & 0xffff) << 16)) & 0xffffffff;
h1 ^= k1;
}
h1 ^= key.length;
h1 ^= h1 >>> 16;
h1 = ((h1 & 0xffff) * 0x85ebca6b + ((((h1 >>> 16) * 0x85ebca6b) & 0xffff) << 16)) & 0xffffffff;
h1 ^= h1 >>> 13;
h1 = ((h1 & 0xffff) * 0xc2b2ae35 + ((((h1 >>> 16) * 0xc2b2ae35) & 0xffff) << 16)) & 0xffffffff;
h1 ^= h1 >>> 16;
return h1 >>> 0;
}
~~~
在得到一个整型的哈希值后,就得转换成字符,像上面短链中的字符是 6 个,也就是将10进制转换成62进制,如下所示。
~~~
function string10to62(n) {
if (n === 0) {
return "0";
}
var digits = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
var result = "";
while (n > 0) {
result = digits[n % digits.length] + result;
n = parseInt(n / digits.length, 10);
}
return result;
}
~~~
## 三、缓存
在将映射关系存入数据库时,可将其直接存入[redis](http://www.redis.cn/)缓存中,采用哈希的数据结构,也就是将计算出的 short 作为 key,原始地址作为 value。
假设每条关系所占空间是50字节,那么2000W条记录大概占用 1G左右,为了节省空间,缓存的超时时间会设为 7 天。
每次在访问短链时,首先从缓存中读取,若有,就直接跳转;若无,则查询数据库,再将映射关系存入缓存中。
~~~
//读取redis
let url = await services.common.redisShortChainGet(short);
ctx.status = 302; //临时跳转
if(url) {
ctx.redirect(getCompleteUrl(url, querystring));
return;
}
//缓存中不存在,则读取数据库
const data = await services.common.getOneShortChain({ short });
if(!data) {
ctx.body = "短链不存在";
return;
}
//将数据库中读取的短链缓存起来
await services.common.redisShortChainSet(short, data.url);
ctx.redirect(getCompleteUrl(data.url, querystring));
~~~
网上的一些文章在判断短链是否存在时,会采用[布隆过滤器](https://baike.baidu.com/item/%E5%B8%83%E9%9A%86%E8%BF%87%E6%BB%A4%E5%99%A8/5384697)。
它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,长度是 10 亿的布隆过滤器,也只需要 125MB左右的内存空间。
布隆过滤器的缺点是有一定的误识别率和删除困难,例如下图中的 A 和 E 是存在于布隆过滤器中的,它们的映射位置都设成了 1,而 B 并不存在,但它的映射指向了两个是 1 的位置,从而就造成了误识别。
:-: ![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/b6/26/b626d44780e570f6a074c70eaba0c267_620x618.png =300x)
图 17
*****
> 原文出处:
[博客园-Node.js躬行记](https://www.cnblogs.com/strick/category/1688575.html)
[知乎专栏-Node.js躬行记](https://zhuanlan.zhihu.com/pwnode)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎阅读。
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
';