不用uuid或自增id因前者过长(36位)、后者暴露总量且不安全;murmurhash64+标准化url+base62编码(剔除0o1l)可生成短码,但必须加db唯一索引或redis setnx防碰撞,高并发下需限重试、异步落库、降级兜底。

为什么不用UUID或自增ID做短链接
短链接本质是把长URL映射成极短字符串,但直接用 UUID(36位)或数据库自增ID(暴露总量、可预测)都不合适。前者太长,后者有安全和并发压力问题。MurmurHash 是个折中选择:固定长度、分布均匀、计算快,适合做哈希中间层。
注意:MurmurHash 本身不加密,也不防碰撞,只负责把任意长度的URL转成一个 long 值,后续靠Base62进一步压缩可读性。
- 别对原始URL直接用
MurmurHash3.hash128()—— 它返回128位,Java里不好处理;用MurmurHash3.hash64()得到单个long - 务必对URL做标准化预处理:统一协议头(
https://)、去掉末尾斜杠、解码后再哈希,否则https://a.com/和https://a.com/%2F会算出不同hash - 如果用Spring Boot,推荐用
net.openhft.hashing.LongHashFunction替代手写MurmurHash实现,它更轻量且已优化边界情况
Base62编码怎么避开0OIl这些易混淆字符
Base62比Base64少两个字符(去掉 + 和 /),又比Base36多数字+大小写字母,但默认字符集常含 0(零)和 O(大写o)、1 和 l(小写L),用户手动输入时极易出错。
真正可用的Base62字符集得显式排除这四类: 0、O、1、l。剩下62个干净字符才叫“人眼友好”。
- 别直接抄网上“标准Base62表”,先检查是否含
0O1l;常见错误是用了"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"这种原始顺序表 - 推荐用确定性字符表:
"23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"(注意跳过了0O1lI中的0、O、1、l,I也建议跳过) - 编码时用
long做输入,逐次取模62,反向拼接;解码时从左到右,每轮result = result * 62 + indexOf(char)
MurmurHash + Base62 组合后还要不要查重
要。MurmurHash 64位理论碰撞概率极低(约2^(-64)),但短链接系统面对的是海量URL,尤其存在大量爬虫试探、重复提交场景。一旦碰撞,两个不同URL生成相同短码,用户点击就错跳——这是线上事故。
不能靠“概率低”赌运气,必须加一层数据库唯一约束或Redis SETNX 防重。
- 生成Base62码后,先查DB或Redis看
short_code是否已存在;存在则对原hash加盐(比如追加时间戳毫秒数或随机int),重新哈希再试 - 别用纯随机生成短码(如
ThreadLocalRandom.current().nextInt(100000, 999999)),它无法保证全局唯一,且无法反向还原原始URL - 如果用MySQL,
short_code字段必须建唯一索引;用Redis,用SET short:abc123 "https://..." NX EX 3600,失败就重试
高并发下如何避免短码生成变慢或雪崩
短链接生成看似简单,但压测时容易卡在三处:MurmurHash线程安全(其实它是无状态的,没问题)、Base62编码(纯CPU,很快)、最慢的是查重+写库。尤其当DB写入延迟升高,整个链路就会阻塞。
关键不是优化单次计算,而是控制重试频次和降级策略。
- 查重失败(如Redis超时或DB唯一冲突)最多重试3次,第4次直接抛异常或走备用方案(比如返回带参数的中间页,异步补短码)
- 不要在HTTP请求线程里同步写DB,可先写入Kafka或本地队列,由后台消费者落库;短码生成接口只负责快速返回
- 缓存热点短码的反查结果(
short_code → long_url),但注意:生成阶段的“查重缓存”不能只依赖本地ConcurrentHashMap,得用分布式锁或Redis原子操作
真实系统里,99%的性能瓶颈不在MurmurHash或Base62,而在你没想清楚“查重失败后要不要等、等多久、等不来怎么办”。这个逻辑漏掉,再快的算法也扛不住流量打穿。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











