写点什么

一种长链换短链的实现思路

作者:golf
  • 2023-10-10
    陕西
  • 本文字数:4308 字

    阅读完需:约 14 分钟

一种长链换短链的实现思路

1. 引言

我们以一个具体的需求案例来引出问题,假设现在有一个长链接:


https://rtest-bj06-private01.qsh1.cn/resources/20231007/597393408/downloadzip/21317ec2ed154d93817390675718584e.mp4?dname=保密会议-1007_20231007164031.mp4
复制代码


产品要求分享出去的是类似下面的短链接:


https://s.qsh1.cn/a/XNiZAx7FY
复制代码


我们如何实现?

2. 方案选型

这里需要用到长链接换短链接的功能,有两种可选方案。


  • 方案一:集成其它厂商的短网址 API

  • 方案二:研发自己的短链接服务


先说方案一,业界很多厂商有已经提供好的短网址服务,像百度提供的https://dwz.cn/, 可以直接集成,具体流程为:


  • 在网站上申请一个 token,作为访问 API 的凭证;

  • 调用百度提供的 API 接口,传入原始长链接,得到一个短链接,类似https://dwz.cn/jaFUSvF7


之前有项目用过一段百度的短网址服务,但用下来存在以下几个问题:


  1. 访问百度 API 需要走公网,单次访问延时基本在 200ms 以上,会拖慢业务接口速度;

  2. 生成的短链域名与企业主域名不一致,例如:dwz.cn 与 quanshi.com 没有相关性,不易记忆;

  3. 不支持批量生成多个短链接;


延时问题基本是硬伤,只要调用外部厂商的 API 基本都存在,如果你的项目中也关心延时,更推荐自研。


其实,开发自己的短链接服务并不复杂,总结起来就只做两件事情:


  • 分享时将长链接换成短链接;

  • 访问短链接时再映射成长链接;


接下来,我们具体展开来描述如何实现长链接和短链接的映射。

3. 长链换短链

长链换短链一般是在用户要将链接分享到外部时使用,短链接主要有两部分组成:


  • ukey:用于标识链接,例如链接https://dwz.cn/jaFUSvF7中的 jaFUSvF7

  • 短域名:当企业域名比较长时,可能有必要申请一个短域名,来配合 ukey 将链接缩短;


这两部分我们分别来说明。

3.1 生成 UKey

之前专门写过一篇文章一种短链ukey生成算法来介绍如何生成 ukey,该文章最后封装了一个 API 方法,定义如下:


func BuildUkey(data1, data2, data3, randomLen int) string


  • 参数 data1, data2, data3: 分别为 3 个整数形式的业务标识,可根据需要使用 1~3 个;

  • 参数 randomLen: 支持指定几位随机字符来增强安全性,0 表示不使用随机数;


只要提供几个整数形式的业务标识,就能很方便的生成 ukey,所以这里我们主要讨论如何得到整数形式的业务标识。

方法一:开放参数由使用方指定

假设有一个 http 接口/urlserver/long2short来提供长链换短链的功能,我们可以在必要入参 longUrl 之外,多开放两个入参 bizType 和 uid 来让调用方指定业务标识,接口参数如下:


  • longUrl:原始长链接;

  • bizType:业务类型,用于扩展给不同的业务场景使用;

  • uid: 业务标识,在 bizType 参数指定的类型下需要唯一;


将 bizType 和 uid 作为 data1 和 data2 参数传给 BuildUkey 方法,即可以生成一个唯一的 ukey。


这种方法的好处是同一个业务标识生成的短链能保持不变,能避免短链的重复生成。

方法二:内置发号器

如果业务方没有整数形式的业务标识,上面的方法就不适用了。这种情况下,可能需要在短链服务中内置一个发号器来生成唯一标识。


其实,发号器和 ID 生成器是一类业务,前面有一篇文章一款简单实用的ID生成器,有介绍如何高效的生成唯一标识,我们可以使用这个库来作为发号器。


从发号器拿到唯一标识后,传给 BuildUKey 方法即可得到一个 ukey, 这种方法只需要传 data1。


上面两种方法无所谓优劣,其实是相互补充的,有各自的适用场景:


  • 方法 1:能避免短链接的重复生成,也能支持对外短链不变的情况下修改长链接,但对使用方有整数业务标识的要求,更适合遵循一定规范的系统内部使用;

  • 方法 2:更通用,使用方也更简单,缺点是不好作去重,当业务方多次调用时链接数量可能会成倍增长,有不小的冗余,更适合系统外部使用。


所以,可以将两种方法融合,预留一个 bizType 给内置发号器使用,其它的 bizType 根据接入的业务需求来自定义。当业务方传了 bizType 和 uid 时,使用业务方传的标识,当没有传时就走内置发号器生成一个 uid,最终都是将 bizType 和 uid 传给 BuildUkey 方法来得到 ukey,并且两种方式生成的 ukey 不会重复。

3.2 申请短域名

短域名与长域名的申请并无不同,每个域名服务商处都可以申请,拿腾讯云示例,只要查询域名没被注册就可以申请购买:



不过短域名最好和企业的完整域名有一定的相关性,这样能增加识别度,也更容易被记住,例如:


  • quanshi.com -> qsh1.cn


有了短域名后,就可以和 ukey 拼成一个完整的短链, 示例(路径/a 只是为了后续扩展):


https://s.qsh1.cn/a/XNiZAx7FY
复制代码

3.3 链接信息存储

短链构造好后,需要给短链和长链保存一个对应关系,以支持两者的映射。设计一张数据表来保存短链 ukey 和长链的映射关系:


 CREATE TABLE `short_url` (  `ukey` varchar(50) DEFAULT NULL COMMENT '短链ukey',  `long_url` varchar(255) DEFAULT NULL COMMENT '原始长链接',  `timestamp` timestamp NULL DEFAULT NULL COMMENT '创建更新时间戳',  UNIQUE KEY `idx_ukey` (`ukey`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;
复制代码


一条 SQL 语句完成对短链和长链映射关系的保存:


INSERT INTO `short_url` (`ukey`, `long_url`, `timestamp`) VALUES ('XNiZAx7F2', 'https://rtest-bj06-private01.qsh1.cn/resources/20231007/597393408/downloadzip/21317ec2ed154d93817390675718584e.mp4?dname=保密会议-1007_20231007164031.mp4', '2023-10-08 11:38:22');
复制代码


保存完后,将短链返回给业务调用方,这样业务方就完成了长链换短链。

4. 短链映射长链

当用户访问短链接时,系统需要自动将短链映射为长链,大致上可以分为以下三步:


  • 解析短链提取 ukey 信息

  • 查询长链接

  • 跳转至长链接

4.1 短链接解析

短链映射长链需要走一次接口查询,上面短链接https://s.qsh1.cn/a/XNiZAx7FY中的路径部分/a/XNiZAx7FY只是为了让链接短,这种一个字母的路径从语义上讲没有什么识别度,服务器内部的接口一般不会这样来开。


假如内部接口定义为:/urlserver/short2long/[ukey],我们需要在网关上做一层 URL 转换,将外部 URL 映射成内部 URL。以 Nginx 为例,可以用一个 rewrite 指令来完成服务器内部的 URL 重定向:


rewrite ^/a/(.*) /urlserver/short2long/$1;
复制代码


这样,就将来自外部的 URL/a/XNiZAx7FY 转换成了内部 URL/urlserver/short2long/XNiZAx7FY


之后,增加一个内部 URL 的匹配指令即可将请求路由到负责处理短链的服务器上:


location ~* ^/urlserver(/.*) {    proxy_pass http://urlserver_upstream;}
复制代码


需要说明的是,这里的内部 URL 与之前的原始长链接并没有关系。

4.2 查询长链接

短链服务器收到请求后,只需要解析出 URL 上携带的ukey参数,并用 ukey 查询出长链接,一条 SQL 即可实现,如下:


4.3 跳转至长链接

跳转长链接比较简单,只需要一个 302 重定向即可,用 beego 代码示例:


c.Redirect(longUrl, 302)
复制代码


不过这里有一个细节,有时候由于业务需要会在分享链接时增加一些参数,例如:https://s.qsh1.cn/a/XNiZAx7FY?token=xxx


这些扩展的参数短链服务是无法处理的,只能由提供原始长链接的服务来处理,所以短链服务在跳转长链接之前还需要做一件事情,将链接上的参数原封不动的透传到长链接上,代码示例如下:


    //获取追加的GET参数    requestURI := c.Ctx.Input.Request.RequestURI    params := strings.SplitN(requestURI, "?", 2)    if len(params) < 2 {             c.Redirect(longUrl, 302)   // 没有参数,直接跳转        return    }
// 透传参数,分为两种情况: // 1. 若原始的长连接中包含?,则需要&拼接 // 2. 如果不包含?,则用?拼接 if strings.Contains(langUrl, "?") { longUrl += "&" + params[1] } else { longUrl += "?" + params[1] } c.Redirect(longUrl, 302)
复制代码

4.4 提升性能

短链与长链的映射基本是固定的,每次都把请求转发到后端服务就显得有些多余,而且每次请求都要查询一次 DB,当量大时有可能对后台 DB 造成不小的压力。


为此,我们可以做一个优化,在网关上缓存接口的响应结果,还是以 Nginx 为例,只需要两步配置就能支持接口响应的缓存。


首先,在 http 块增加如下配置,来指明缓存路径和一些缓存参数:


proxy_cache_path /var/cache levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
复制代码


参数释义:


  • /var/cache:Nginx 缓存资源的本地存放路径;

  • levels=1:2 :指定 2 级目录来存储缓存文件,其中第一级目录用 1 位 16 进制命名,如 a;第二级目录用 2 位 16 进制命名,如 3a。所以此例中一级目录有 16 个,二级目录有 1616=256 个,总目录数为 16256=4096 个。

  • key_zone:一块命名的内存区域,用来存放缓存的 key 和 metadata;

  • max_size :最大缓存空间, 当达到上限后会删除最少使用的 cache;

  • inactive:某个缓存在 inactive 指定的时间内如果不访问,将会从缓存中删除;


其次,在相应 URL 的 Location 块中添加如下配置,来启用缓存:


server {    location ~* ^/urlserver(/.*) {        proxy_cache my_cache;                                 proxy_cache_valid 200 302 10m;        proxy_cache_key $host$uri$is_args$args;        add_header Nginx-Cache "$upstream_cache_status";        ……    }}
复制代码


配置项释义如下:


  • proxy_cache:用于启用 cache,并指明要使用的 key_zone;

  • proxy_cache_valid:缓存 HTTP 响应状态码为 200 和 302 的请求,并将它们缓存 10 分钟;

  • add_header Nging-Cache "$upstream_cache_status":用于在响应头中判断是否命中缓存,HIT 表示命中缓存,MISS 表示未命中缓存;

  • proxy_cache_key: 表示将主机名、URI、问号及查询参数连接起来作为缓存 key;


设置完成后,重新请求看效果:


小结

本文主要介绍了长链接换短链接这类业务的一种实现思路。


先通过 ukey 设计和短域名申请介绍了长链接如何换成短链接,又通过 nginx 配置和 302 跳转描述了如何将短链接映射为长链接,最后根据业务特点做了一步提性能的优化配置。


本文中介绍的方法,如果做一些延伸,还可以支持相关联的需求,例如:分享带密码保护的链接,分享带有效期的链接等。这些功能可以通过在短链接中添加参数或使用特定的链接生成算法来实现。


除此之外,还可以在链接上添加来源参数,以追踪链接的渠道来源,以及统计不同来源的链接点击次数,给渠道作贡献度排名。


总之,通过长链接换短链接的实现思路,可以为用户提供更便捷、安全和可控的链接分享方式。同时,根据业务需求,还可以扩展更多的功能来满足不同的需求。


本文中提到的一些相关文章贴在了下面,有兴趣可以参考阅读。

参考阅读


发布于: 刚刚阅读数: 4
用户头像

golf

关注

IT行业也需要能沉下心来把软件做好的工匠。 2018-09-21 加入

10年以上行业经验和技术积累,擅长将复杂的业务转换为清晰简化的方案设计,并致力于打造高性能、可扩展、结构优良的软件。

评论

发布
暂无评论
一种长链换短链的实现思路_golang_golf_InfoQ写作社区