短链接生成器架构解密:62 进制编码 + 分布式 ID,如何让 6 位字符支撑 568 亿个网址?
短链接生成器架构解密:62 进制编码 + 分布式 ID,如何让 6 位字符支撑 568 亿个网址?
摘要:每天数十亿次的短链接请求背后,隐藏着怎样的技术架构?本文将从数学原理、编码算法、分布式 ID 生成、高并发设计等维度,深度解析短链接系统的核心设计,并附带完整的 Spring Boot 3 实现代码。
一、从一条推文说起:短链接的万亿级市场
2023 年,Twitter 每天处理的短链接数量超过 50 亿次。Bitly 平台累计生成的短链接已突破 800 亿个。这些数字背后,是一个看似简单却蕴含深厚技术功力的系统——短链接生成器。
原始 URL: https://www.example.com/products/category/electronics/smartphones/brand-x/model-y/specifications?color=black&storage=256gb&utm_source=wechat&utm_medium=social&utm_campaign=2024_spring 短链接: https://short.link/aB3xK96 位字符,从 200+ 字符压缩而来,压缩比高达 97%。但问题来了:
6 位字符真的能表示 568 亿个不同的 URL 吗?这个系统该如何设计才能支撑高并发场景?
二、数学基石:6 位字符的排列组合魔法
2.1 字符集的选择
短链接系统的核心在于字符集(Character Set)的选择。常见的选择有:
| 字符集 | 包含内容 | 字符数量 | 6 位组合数 |
|---|---|---|---|
| 10 进制 | 0-9 | 10 | 1,000,000 |
| 16 进制 | 0-9, a-f | 16 | 16,777,216 |
| 36 进制 | 0-9, a-z | 36 | 2,176,782,336 |
| 62 进制 | 0-9, a-z, A-Z | 62 | 56,800,235,584 |
| 64 进制 | 0-9, a-z, A-Z, +, / | 64 | 68,719,476,736 |
/** * 62 进制字符集定义 * 为什么选择 62 进制? * - 足够大的组合空间(568 亿) * - 兼容 URL 安全字符(无需 URL 编码) * - 大小写敏感,增加熵值 */ public class Base62 { private static final String BASE62_CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; private static final int BASE = 62; /** * 将十进制数字转换为 62 进制字符串 */ public static String encode(long num) { if (num == 0) return "0"; StringBuilder sb = new StringBuilder(); while (num > 0) { sb.append(BASE62_CHARS.charAt((int)(num % BASE))); num /= BASE; } return sb.reverse().toString(); } /** * 将 62 进制字符串转换回十进制数字 */ public static long decode(String str) { long num = 0; for (char c : str.toCharArray()) { num = num * BASE + BASE62_CHARS.indexOf(c); } return num; } }2.2 组合数计算验证
public class CombinationCalculator { public static void main(String[] args) { long base = 62; int length = 6; // 计算 62^6 long combinations = (long) Math.pow(base, length); System.out.println("=== 62 进制 6 位字符组合数 ==="); System.out.println("字符集大小:" + base); System.out.println("字符长度:" + length); System.out.println("总组合数:" + combinations); System.out.println("约等于:" + combinations / 1_0000_0000.0 + " 亿"); // 输出:568 亿 } }结论:62 进制的 6 位字符可以表示 568 亿 个不同的短链接,足以支撑绝大多数业务场景。
三、核心架构设计:三种主流方案对比
3.1 方案一:自增 ID + Base62 编码(推荐⭐)
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 原始 URL │───▶│ 分布式 ID │───▶│ Base62 编码 │───▶│ 短链接 key │ │ 哈希校验 │ │ 生成器 │ │ (62 进制) │ │ (aB3xK9) │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘优点: - 简单高效,O(1) 时间复杂度 - ID 连续,便于分页和统计 - 无碰撞风险
缺点: - 需要分布式 ID 生成器 - ID 可预测(可通过混淆解决)
3.2 方案二:哈希算法(MD5/SHA1)
/** * 基于 MD5 的短链接生成方案 * 注意:存在哈希碰撞风险,需要额外处理 */ public class HashBasedShortener { public String generate(String url) { // 计算 MD5 String md5 = DigestUtils.md5Hex(url); // 取前 6 位(或更多位减少碰撞) String shortKey = md5.substring(0, 6); // ⚠️ 需要检查碰撞 if (urlRepository.existsByShortKey(shortKey)) { // 碰撞处理:取后 6 位或使用其他策略 shortKey = md5.substring(md5.length() - 6); } return shortKey; } }优点: - 无需存储 ID 映射 - 相同 URL 生成相同短链
缺点: - 存在碰撞风险(生日悖论) - 需要额外的碰撞处理逻辑
3.3 方案三:雪花算法(Snowflake)+ Base62
/** * 基于 Twitter Snowflake 的分布式 ID 生成 * 适合超大规模分布式系统 */ public class SnowflakeIdGenerator { private final long workerId; private final long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; // 位掩码配置 private static final long TIMESTAMP_BITS = 41; // 41 位时间戳 private static final long WORKER_BITS = 5; // 5 位工作节点 ID private static final long SEQUENCE_BITS = 12; // 12 位序列号 private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); private static final long WORKER_SHIFT = SEQUENCE_BITS; private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_BITS; public synchronized long nextId() { long timestamp = System.currentTimeMillis(); // 时钟回拨处理 if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨,拒绝生成 ID"); } if (timestamp == lastTimestamp) {