这类问题我又碰上了一次。灰度按 uid 哈希对副本数取模,分桶 0 到 1 进新版,副本从 3 扩到 12 后同一个用户在两版之间反复跳,会话状态丢了两次。
第一反应是「分桶结果只存在内存里」,验证下来不成立。
真正麻烦的地方在于,几个原因的现象几乎一样。取模基数是副本数,扩容就变分组;哈希里带了进程随机盐;两个语言实现的哈希算法不一致。
上来先把分桶固定成 100 份再映射
python -c "import hashlib;u='user_123';print(int(hashlib.md5(u.encode()).hexdigest(),16)%100)"
然后用稳定哈希替换对副本数取模
bucket = int(hashlib.md5(uid.encode()).hexdigest(), 16) % 100
再往下把分桶结果落到配置表便于核对
python bucket_dump.py --users users.csv --buckets 100 --out buckets.csv
同一用户连续请求确认落到同一版本
for i in 1 2 3 4 5; do curl -s -H 'X-User-Id: user_123' https://api.example.com/version; done
恢复的判定其实很直接。同一用户五次请求都返回同一个版本号,扩容前后分桶不变,会话不再丢失。
容易忽略的是,取模的基数必须是固定常量,副本数、线程数这类会变的值不能进哈希,否则一扩容就重新洗牌。改动前把原配置备份一份,出问题能马上回滚,这比任何技巧都实用。
