不用rand或uuid因不可预测、过长;MurmurHash3通过确定性哈希+62进制转换生成6位短码,但需标准化URL并依赖数据库唯一索引回退防冲突。

为什么不用 rand 或 uuid 做短码?
因为短链接的核心诉求是「可预测的低冲突 + 高吞吐 + 无状态」。rand 生成的字符串不可复现,存库前得先查重,QPS 上不去;uuid 太长(32 字符),且带连字符、大小写混杂,不利于手输和传播。MurmurHash 的确定性哈希行为刚好补上这个缺口:同一 URL 每次算出相同短码,无需查重,直接拼接后入库或返回。
但注意:MurmurHash3 原生输出是 uint32/uint64,不能直接当短码用——得转成 62 进制(0-9a-zA-Z)并截断到 6 位左右。否则哈希值太长,失去“短”意义。
murmur3.Sum64 输出怎么安全转成 6 位短码?
直接取 Sum64() 结果模 62^6 会引入偏差(低位分布不均),而且 Go 的 math/rand 不参与这里——我们靠哈希本身抗碰撞,不是靠随机性。正确做法是把哈希值当作大整数,逐位取余生成 62 进制字符:
func hashToCode(h uint64) string {
const chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
var buf [6]byte
for i := 5; i >= 0; i-- {
buf[i] = chars[h%62]
h /= 62
}
return string(buf[:])
}常见错误是用 fmt.Sprintf("%x", h)[:6] ——十六进制只含 0-9a-f,字符集太小,6 位仅约 16⁶ ≈ 1600 万种组合,而 62⁶ ≈ 560 亿,差三个数量级,冲突概率飙升。
立即学习“go语言免费学习笔记(深入)”;
URL 去重必须依赖数据库唯一索引,不能只靠哈希
MurmurHash 再快,也无法保证全球任意两个不同 URL 绝对不碰撞(生日悖论在 62⁶ 空间下仍有约 0.001% 概率撞)。所以实际流程必须是:
- 对原始 URL 计算
murmur3.Sum64,转成 6 位短码 - 尝试
INSERT INTO links (code, url) VALUES (?, ?) - 若数据库报
UNIQUE constraint failed: links.code,说明短码已存在 → 回退到加盐重哈希(比如拼上时间戳毫秒再算一次)或换用 7 位码
别省这一步。线上环境跑几天就会遇到碰撞,尤其当 URL 含大量参数(如 ?utm_source=...)时,不同参数顺序可能被业务层视为相同 URL,但哈希值不同,反而放大冲突面。
Go 标准库没有 MurmurHash,得用第三方包
官方 hash 包里只有 adler32、crc32 这类校验和,不适合做短码。可靠选择只有 github.com/spaolacci/murmur3(纯 Go 实现,无 CGO)或 golang.org/x/exp/murmur(实验包,API 可能变)。别用带 cgo 的版本——交叉编译部署到 Alpine 容器会失败。
初始化示例:
import "github.com/spaolacci/murmur3"
<p>h := murmur3.Sum64([]byte("<a href="https://www.php.cn/link/2fb2c0b63768c3c73ef007ed0f753663">https://www.php.cn/link/2fb2c0b63768c3c73ef007ed0f753663</a>"))
code := hashToCode(h.Sum64())注意:传入字节切片前,建议先做标准化处理(如去掉末尾 /、统一协议大小写、归一化查询参数顺序),否则 https://a.com/ 和 https://a.com 会被算出两个短码。
哈希只是第一步,真正卡住人的永远是 URL 标准化逻辑和数据库冲突回退策略——这两块没压测过,上线后第一波爬虫流量就能打穿短码池。










