短链顾名思义是一种很短的地址,应用广泛,例如页面中有一张二维码图片,包含的是一个原始地址(如下所示),如果二维码中的链接需要修改,那么就得发代码替换掉。

  但如果二维码图包含的是一条短链,那么只要修改短链中的映射关系,就能不发代码了。当然了,前提是有一套短链系统维护着他们之间的关系,下图是列表和新增的界面。

  

  

  前端界面的代码省略了,直接看短链用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(么么哈希)算法,由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 缓存中,采用哈希的数据结构,也就是将计算出的 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));

  网上的一些文章在判断短链是否存在时,会采用布隆过滤器

  它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,长度是 10 亿的布隆过滤器,也只需要 125MB左右的内存空间。

  布隆过滤器的缺点是有一定的误识别率和删除困难,例如下图中的 A 和 E 是存在于布隆过滤器中的,它们的映射位置都设成了 1,而 B 并不存在,但它的映射指向了两个是 1 的位置,从而就造成了误识别。

  

Node.js躬行记(6)——自制短链系统的更多相关文章

  1. Node.js躬行记(1)——Buffer、流和EventEmitter

    一.Buffer Buffer是一种Node的内置类型,不需要通过require()函数额外引入.它能读取和写入二进制数据,常用于解析网络数据流.文件等. 1)创建 通过new关键字初始化Buffer ...

  2. Node.js躬行记(2)——文件系统和网络

    一.文件系统 fs模块可与文件系统进行交互,封装了常规的POSIX函数.POSIX(Portable Operating System Interface,可移植操作系统接口)是UNIX系统的一个设计 ...

  3. Node.js躬行记(4)——自建前端监控系统

    这套前端监控系统用到的技术栈是:React+MongoDB+Node.js+Koa2.将性能和错误量化.因为自己平时喜欢吃菠萝,所以就取名叫菠萝系统.其实在很早以前就有这个想法,当时已经实现了前端的参 ...

  4. Node.js躬行记(15)——活动规则引擎

    在日常的业务开发中,会包含许多的业务规则,一般就是用if-else硬编码的方式实现,这样就会增加逻辑的维护成本,若无注释,可能都无法理解规则意图. 因为一旦规则有所改变,那么就需要修改代码再发布代码, ...

  5. Node.js躬行记(19)——KOA源码分析(上)

    本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...

  6. Node.js躬行记(21)——花10分钟入门Node.js

    Node.js 不是一门语言,而是一个基于 V8 引擎的运行时环境,下图是一张架构图. 由图可知,Node.js 底层除了 JavaScript 代码之外,还有大量的 C/C++ 代码. 常说 Nod ...

  7. Node.js躬行记(23)——Worker threads

    Node.js 官方提供了 Cluster 和 Child process 创建子进程,通过 Worker threads 模块创建子线程.但前者无法共享内存,通信必须使用 JSON 格式,有一定的局 ...

  8. Node.js躬行记(3)——命令行工具

    一.自定义 创建一个空目录,然后通过npm init命令初始化package.json文件,并按提示输入相关信息或直接回车使用默认信息,生成的内容如下所示. { "name": & ...

  9. Node.js躬行记(24)——低代码

    低代码开发平台(LCDP)是无需编码(0代码)或通过少量代码就可以快速生成应用程序的开发平台.让具有不同经验水平的开发人员可以通过图形化的用户界面,通过拖拽组件和模型驱动的逻辑来创建网页和移动应用程序 ...

随机推荐

  1. ArrayList的删除姿势你都知道了吗

    引言 前几天有个读者由于看了<ArrayList哪种遍历效率最好,你真的弄明白了吗?>问了个问题普通for循环ArrayList为什么不能删除连续重复的两个元素?其实这个描述是不正确的.正 ...

  2. E-BOOK-TINY6410-LCD的使用

    电子书需要通过屏幕显示出来,首先写了LCD模块.代码上传到了 github https://github.com/qq2216691777/E-book 本次完善了lcd模块的程序.可以适用在其他地方 ...

  3. python之scrapy篇(二)

    一.创建工程 scarpy startproject xxx 二.编写iteam文件 # -*- coding: utf-8 -*- # Define here the models for your ...

  4. SpringMVC的@Validated校验注解使用方法

    validate会对参数进行校验,校验标准为validate后的类中的标准.本例中对User进行校验,User类中设置了校验标准. 在后台开发过程中,对参数的校验成为开发环境不可缺少的一个环节.比如参 ...

  5. 2018年第九届蓝桥杯B组(201806-----递增三元组)

    给定三个整数数组 A = [A1, A2, - AN], B = [B1, B2, - BN], C = [C1, C2, - CN], 请你统计有多少个三元组(i, j, k) 满足: 1 < ...

  6. 发现一个怪象windows 7系统上老是丢包windows 10网络正常

    不知何故障,同一个局域网,windows 10系统上不丢包,windows 7系统老是丢包,不知是不是这二个系统的差区别,还是大家都有这样的情况. 相互PC之间ping也又不丢包,只有windos 7 ...

  7. maven 的安装与配置详细步骤

    1. 直接搜索maven 2. 进入后点击download界面,这时出现的是当前最新版本, 当然还有以前的版本可供下载 3. 下载解压到你指定的目录后,需要配置一下环境变量. a. 右键此电脑点击属性 ...

  8. Dubbo 配置的加载流程

    配置加载流程 在SpringBoot应用启动阶段,Dubbo的读取配置遵循以下原则 Dubbo支持了多层级的配置,按照预先定义的优先级自动实现配置之间的覆盖,最终所有的配置汇总到数据总线URL后,驱动 ...

  9. 谈谈你不知道的gist

    1.Gist是什么关于Gist的详细介绍,请阅读官方文档About gists,下面只简略介绍部分功能: Gist是一种与其他人共享代码片段和粘贴的简单方法. 当您需要与同事或朋友共享示例代码或技术时 ...

  10. JavaScript入门-函数function(二)

    JavaScript入门-函数function(二) 递归函数 什么是递归函数? 递归简单理解就是,在函数体里,调用自己. //我们在求一个10的阶乘的时候,可能会这么做 //写一个循环 var to ...