百亿级短链接系统设计:如何用 Redis + MurmurHash 实现高性能的“短网址生成器”?
2026/8/21 11:55:32 网站建设 项目流程

🔗 前言:面试官最爱问的“简单题”

“请设计一个短链接系统,像t.cn/AbCdEf这种。”
这道题在字节、阿里、腾讯的面试中出现的概率高达 80%。

很多初学者会不假思索地回答:“用 Redis 的INCR自增 ID,然后转成 62 进制不就行了?”
错!大错特错!

  1. 安全隐患:自增 ID 是连续的,黑客写个脚本t.cn/1,t.cn/2… 就能遍历你的所有数据,窃取商业机密。
  2. 性能瓶颈:严重依赖 Redis 单点计数器,难以水平扩展。

今天,我们来聊聊大厂主流的**“摘要算法生成法”**,利用MurmurHash + Base62,打造一个安全、无碰撞、可扩展的百亿级短链接系统。


🧠 核心原理:为什么是 MurmurHash?

要将一个长长的 URL 压缩成短字符串,本质上是一个Hash(摘要)过程。

1. 为什么不用 MD5?

MD5 生成的结果是 128 位(32 个字符),太长了!如果我们截取前 6 位,哈希冲突(Collision)的概率会爆炸式增长。

2. MurmurHash 的优势

Google 出品的MurmurHash算法,是目前非加密型 Hash 算法中的王者。

  • 速度快:比 MD5 快几十倍。
  • 雪崩效应好:输入微小的变化,输出巨大的差异。
  • 32 位输出:MurmurHash32 返回的是一个整数,非常适合后续处理。
3. Base62 编码 (压缩的核心)

我们需要把 MurmurHash 算出的整数(比如394820123)转换成更短的字符串。
使用Base62(a-z, A-Z, 0-9 共 62 个字符):

  • 626≈56862^6 \approx 568626568亿
  • 627≈3.562^7 \approx 3.56273.5万亿

结论:只需 6 到 7 位字符,就足够容纳全世界的网页!


⚔️ 核心难点:如何解决“哈希冲突”?

这是这道题的致命考点
虽然 MurmurHash 优秀,但在百亿量级下,冲突是必然的(抽屉原理)。即:
Hash(URL_A) == Hash(URL_B)

工业级解决方案:加盐双重探测

  1. 计算h = MurmurHash(LongURL)
  2. h转为 Base62 字符串,即ShortURL
  3. 查库校验
    • 去 Redis/DB 查这个ShortURL是否已存在。
    • 情况 A (不存在):完美,直接存入。
    • 情况 B (存在,且长链接一致):幂等,直接返回原短链。
    • 情况 C (存在,但长链接不一致)冲突爆发!
  4. 解决冲突
    • 给 LongURL 后面加一个特殊的“盐”(比如DUPLICATE)。
    • 重新执行第 1 步:Hash(LongURL + "DUPLICATE")
    • 直到找到一个不冲突的位置。

🏗️ 架构设计:读写分离与多级缓存

在海量数据下,数据库存不下,Redis 存太贵。我们需要合理的架构。

架构流程图:

访问短链_读链路
生成短链_写链路
1. 提交长链
2. MurmurHash计算
3. 布隆过滤器查重
未命中
命中
加盐重算
1. GET请求
2. 查本地缓存
3. 查分布式缓存
4. 查数据库
5. 回填缓存
6. 302 跳转
重定向服务
用户访问短链
JVM 缓存
Redis Cluster
读取 MySQL
短链生成服务
API 网关
Base62 编码
Bloom Filter
写入 MySQL 分片库
触发冲突解决策略
用户生成请求

关键设计点:

  1. 布隆过滤器 (Bloom Filter):在写入 DB 前,先用布隆过滤器判断短链是否存在。这能拦截 99% 的数据库查询,极大提升写入性能。
  2. 301 vs 302
    • 301 (永久重定向):浏览器会缓存,减轻服务器压力,但无法统计点击量
    • 302 (临时重定向):每次都请求服务器,适合做数据分析(大家一般都选这个)。

🛠️ 代码实战:Java 实现核心逻辑

引入 Google Guava 库来使用 MurmurHash。

<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>31.1-jre</version></dependency>

核心工具类:

importcom.google.common.hash.Hashing;importjava.nio.charset.StandardCharsets;publicclassShortLinkGenerator{// Base62 字符集privatestaticfinalStringBASE62="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";/** * 将 10 进制转 62 进制 */publicstaticStringtoBase62(longnum){StringBuildersb=newStringBuilder();while(num>0){inti=(int)(num%62);sb.append(BASE62.charAt(i));num/=62;}returnsb.reverse().toString();}/** * 生成短链接 */publicStringgenerate(StringlongUrl){// 1. 使用 MurmurHash32 生成 32 位整数// 注意:需处理负数,转为 32 位无符号长整型longhash32=Hashing.murmur3_32_fixed().hashString(longUrl,StandardCharsets.UTF_8).padToLong();// 2. 转 Base62StringshortCode=toBase62(hash32);// 3. 解决冲突逻辑 (模拟)while(isConflict(shortCode,longUrl)){// 加盐重算longUrl+="[SALT]";hash32=Hashing.murmur3_32_fixed().hashString(longUrl,StandardCharsets.UTF_8).padToLong();shortCode=toBase62(hash32);}returnshortCode;}// 模拟数据库查询privatebooleanisConflict(StringshortCode,StringlongUrl){// 实际逻辑:// String existUrl = redis.get(shortCode);// return existUrl != null && !existUrl.equals(longUrl);returnfalse;}}

📊 百亿级优化:分库分表

当数据量达到 100 亿时,单表肯定存不下。
如何分片?

  • 方案:直接用ShortURL做分片键 (Sharding Key)。
  • 逻辑Hash(ShortURL) % 1024,将数据分散到 1024 张表中。
  • 优势:读取时,直接根据短链算出在哪张表,不需要遍历所有库。

📝 总结

设计一个短链接系统,不仅仅是写代码,更是对算法选择、冲突处理、存储架构的综合考量。

  • 拒绝自增 ID:为了安全。
  • 拥抱 MurmurHash:为了速度和随机性。
  • 布隆过滤器:为了极致的写入性能。

掌握了这套方案,面试官问你 TinyURL 设计时,你就可以自信地“降维打击”了。


博主留言:
想获取包含布隆过滤器分库分表配置的完整 Spring Boot 工程源码吗?
在评论区回复“短链”,我发给你一份《百亿级短链系统企业级落地代码》,直接 CV 就能用!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询