应使用 murmur3.Sum32 哈希后取模实现用户分流,而非 rand.Intn;因后者不可复现、不一致、无法回溯,而 murmur3 在千万级用户下分布均匀且结果稳定。

用 murmur3 做用户分流,别用 rand.Intn
Go 里最常被误用的 A/B 分流方式,就是直接对 userID 调用 rand.Intn(100) 算百分比——它完全不可复现、不一致、没法回溯。A/B 测试要求同一用户每次请求都进同一组,否则数据就废了。
正确做法是哈希后取模,推荐 murmur3.Sum32:
import "github.com/spaolacci/murmur3"<br><br>func getGroup(userID string, totalGroups int) int {<br> h := murmur3.Sum32([]byte(userID))<br> return int(h) % totalGroups<br>}-
murmur3在 1000 万用户 ID 下分布偏差 fnv 或原生hash/fnv - 别用
time.Now().UnixNano()或任何带时间/随机因子的逻辑,会破坏确定性 - 如果 userID 是数字类型(如
int64),先转成字符串再哈希,避免字节序歧义
BenchmarkMurmurHash32 这个名字不是随便写的
你看到的 BenchmarkMurmurHash32-45000000025.7ns/op 不是性能广告,而是真实压测结果:在 4.5 亿次调用下,单次耗时仅 25.7 纳秒。这意味着哪怕 QPS 上万,哈希本身也不构成瓶颈。
但要注意两个实际限制:
立即学习“go语言免费学习笔记(深入)”;
- 别在 HTTP handler 里反复初始化
murmur3实例——它无状态,直接调用函数即可 - 如果做多层嵌套实验(比如「首页改版」+「下单按钮颜色」),哈希输入得带实验名前缀,例如
murmur3.Sum32([]byte("homepage_v2:" + userID)),否则不同实验会相互干扰 - 线上跑着跑着发现某组流量突然偏高?大概率是哈希输入里混进了可变字段(比如带时间戳的 sessionID)
配置实验别写死在代码里,用结构体 + JSON
硬编码 if userID%100 看似简单,但一上线就不能动态调比例、不能关实验、不能查历史配置。真正可用的方案必须支持热加载。
建议定义一个轻量配置结构:
type Experiment struct {<br> Name string `json:"name"`<br> Enabled bool `json:"enabled"`<br> Traffic int `json:"traffic"` // 0-100<br> Groups []Group `json:"groups"`<br>}<br><br>type Group struct {<br> ID string `json:"id"`<br> Weight int `json:"weight"` // relative weight, sum = 100<br> Variant string `json:"variant"`<br>}-
Traffic控制整体实验开启比例(比如先开 1%),Weight控制组内分配(比如 A 组 70%,B 组 30%) - JSON 配置从文件或 etcd 加载,用
fsnotify监听变更,避免重启服务 - 千万别把
Weight设成浮点数——Go 的float64在大数取模时有精度误差,导致分流不准
testing.B 跑基准测试时,别漏掉 ResetTimer
写完分流函数,肯定要压测性能。但很多人只写 BenchmarkXXX 函数,忘了初始化开销会污染结果。
典型错误写法:
func BenchmarkBad(t *testing.B) {<br> for i := 0; i < t.N; i++ {<br> userID := fmt.Sprintf("user_%d", i)<br> _ = getGroup(userID, 100)<br> }<br>}这里 fmt.Sprintf 的内存分配和字符串构造全算进耗时了,根本测不出哈希本身性能。
正确写法:
func BenchmarkGood(t *testing.B) {<br> userID := "user_123456789"<br> t.ResetTimer() // 关键:重置计时器,跳过 setup 阶段<br> for i := 0; i < t.N; i++ {<br> _ = getGroup(userID, 100)<br> }<br>}- 所有 setup(生成测试数据、初始化变量)必须放在
t.ResetTimer()之前 - 如果要测不同长度 userID 的影响,得拆成多个 benchmark 函数,不能在一个里
switch len - 线上观察到 CPU 升高?先跑一遍
go test -bench=.,确认不是哈希函数成了热点
分流逻辑本身不复杂,难的是让每一步都可验证、可回滚、可审计。比如哈希输入拼错一个字符,整组数据就失效;配置没加校验,权重填成 150 就直接溢出。这些细节不写日志、不加单元测试,上线后只能靠业务指标反推,代价太大。


















