返回文章

链接太长?短链来救场!

从 HTTP 302 重定向、MurmurHash 与 62 进制编码讲起,梳理短链生成、哈希冲突处理,以及缓存、分库分表和布隆过滤器等架构设计。

今天来简单聊一聊短链服务。

最近小年的项目中需要使用到短链,需求背景大致是这样:用户在页面上生成分享链接,并且可以把链接和内容分享给其他用户。

分享的方式有短信、其他三方平台,比如 WeChat、Facebook、Twitter 等。

而生成短链的目的也很简单:第一是为了看起来更简洁,分享的链接可能会携带各种参数;第二是省钱,因为短信的费用跟内容长短有关,内容越长费用可能越高;第三是适用多平台,一些第三方平台的分享内容会有长度限制。

短链应用场景

跳转原理

短链跳转的实现原理其实很简单,简单概括就是:重定向!

短链重定向流程

HTTP 状态码:3XX

这类状态码代表需要客户端采取进一步的操作才能完成请求。通常,这些状态码用来重定向,后续的请求地址,也就是重定向目标,会在本次响应的 Location 字段中指明。

一般来说,短链服务都是使用 302 实现重定向,因为可以利用短链来统计访问数量之类的数据。

小年直接贴一个 ChatGPT 的回答:

HTTP 301 与 302 重定向对比

生成短链

短链的生成方式常见的有两种:

  • 哈希算法:核心是对长链进行哈希运算,生成唯一的 Hash Code
  • 发号器:可以理解为分布式 ID 自增生成器,为每一个长链分配一个唯一的 ID。

小年项目选用的是哈希算法,也比较推荐哈希算法。一个是实现起来比较简单,另外一个是哈希算法生成的短链比较无规则,跟我们平时收到的营销短信比较类似。

哈希算法生成短链示例

当然具体场景还是要具体分析,发号器的方案也有它的优点。

发号器方案

不过小年这里主要讲一下哈希算法。

网上推荐最多的是 MurmurHash 算法,对比我们常用的 MD5、SHA 这类哈希算法来说,最大的优势就是

MurmurHash 是一种非加密的高效哈希算法,由 Austin Appleby 于 2008 年开发。该算法因其高性能、均匀分布和较低的碰撞率,成为了很多高效数据处理场景的选择,特别是在哈希表、缓存、分布式系统和大数据处理中。

MurmurHash 的特点

  1. 高性能:MurmurHash 的计算速度非常快,特别适合大规模数据处理的场景。其高效性得益于简单的位运算和乘法操作。
  2. 非加密:MurmurHash 不是为加密设计的,因此不能保证数据的安全性。它的目标是快速生成散列值,而不需要复杂的安全计算。
  3. 良好的分布性:MurmurHash 生成的散列值分布均匀,碰撞率低,这对于需要分散存储数据的场景非常关键,比如哈希表或分布式存储系统。
  4. 多版本:MurmurHash2 被广泛使用,适合 32 位系统;MurmurHash3 是当前的主流版本,改进了前两个版本中的一些弱点,适用于 64 位和 128 位的需求,并提供更强的哈希质量。

MurmurHash 与其他哈希算法的对比

  1. 与 MD5、SHA-1 比较:MurmurHash 比这些加密哈希函数更快,因为它不需要执行复杂的安全计算。MD5 和 SHA 系列哈希算法主要用于数据完整性验证和安全需求,而 MurmurHash 侧重于速度和分布性。
  2. 与 CityHash 和 xxHash 比较:CityHash 和 xxHash 是两个速度和性能相当接近 MurmurHash 的哈希函数。CityHash 由 Google 开发,专注于更大的输入集。xxHash 在某些场景下可能比 MurmurHash 更快,但 MurmurHash 的兼容性和稳定性让它依然被广泛使用。

MurmurHash 算法介绍

MurmurHash 提供了两种长度的哈希值:32 bit 和 128 bit。32 bit 可以表示 42.9 亿的数据,基本能够应付我们常见的业务场景了。

当然,为了能够得到极致简短的短链,还可以将哈希值进行 62 进制的转换。

public static void main(String[] args) {
    String rawUrl = "http://edisonz.cn/archives/groovy";
    HashFunction hashFunction = Hashing.murmur3_32();
    HashCode hashCode = hashFunction.hashString(rawUrl, StandardCharsets.UTF_8);
    System.out.println(hashCode.asInt());// 1936285200
    System.out.println(hashCode);// 105e6973
    System.out.println(intToBase64(hashCode.asInt()));// 1PqlUg
}

哈希冲突这个问题也比较好解。不同长链哈希值一致的情况,可以在长链中加入**“盐值”(salt)**后再做哈希,访问的时候再移除掉 salt,就是正常的长链。

高性能架构

当然,上面仅仅是短链服务的理论和实现原理。要设计一个能够真正应用于实际业务场景的企业级短链服务,还有很多细节点需要我们去思考和挖掘。

而且这也是一道高频的面试问题,相信有同学可能碰到过:如何设计一个高性能的短链服务。

小年从两个维度来简单分享自己的看法,有建议或者不足的地方欢迎大家改正和补充。

数据层

  • 分库分表:确定未来周期内的数据增长量,是否需要采取分库分表。
  • 读写分离:评估读写的并发流量。一般来说读流量远大于写流量,可以采用读写分离。
  • 过期时间:短链是否存在过期时间,对于过期的数据需要定时清理还是归档。

应用层

  • 缓存:对热点数据开启缓存,减少数据库的访问,这应该是性能提效比较显著的一招。二级缓存和三级缓存都可以结合场景使用起来,比如 Redis 和本地缓存。
  • 布隆过滤器:每个长链生成的短链在数据库中是唯一的,所以在生成短链后需要先去数据库查询。引入 Redis 布隆过滤器可以减轻判断短链是否重复时的数据库压力。当然,对于创建短链,也就是写流量并发高的场景效果最好,但需要自己维护 Redis 布隆过滤器。

十分钟,部署一个短链服务

看过小年上面的分享后,相信大家对短链也有一定程度的了解。

短链的原理和整体实现其实并不复杂,小年使用 Next.js 实现了一个简单易用的短链服务。

有兴趣的同学可以了解一下:十分钟!免费部署一个自己的短链服务!

Demo 体验:slink-code.vercel.app

短链服务 Demo

参考资料