这个坑值得记一笔。灰度分流写成 hash(user_id) 对 100 取模小于 10 进新版本,Java 侧对 null 做哈希返回 0,所有未登录请求都进了这 10%。上线半小时新版本错误率飙到 12%,把比例降到 1% 仍然是同一批请求,日志里 uid 字段全是 null。

刚开始盯着「分流函数没有对空值做单独分支」,折腾了半个多小时,排掉了。

排查方向其实有限,先把可能性排一排。null 的哈希值固定为 0,取模结果必然落在灰度区间;未登录请求没有分配 visitor_id,字段一直是空;灰度规则只按用户维度设计,漏掉了匿名流量。

上来先统计 null 在总流量里的占比

python -c "import json;rows=[json.loads(l) for l in open('req.jsonl')];print(sum(1 for r in rows if r.get('uid') is None), len(rows))"

给匿名请求补一个稳定的 visitor_id

python -c "import uuid;print(uuid.uuid4())"

接着分流函数里对空值走独立分桶

python -c "h=lambda u: 99 if u is None else abs(hash(u))%100;print([h(x) for x in [None,'a','b']])"

末了按用户类型分别看灰度占比

python -c "print('split ratio by uid_type')"

怎么算真的好了,看的是同一批现象。匿名流量的灰度占比与配置一致,新版本错误率回落到 0.8%,登录用户的分流结果没有变化。

取模分流的前提是输入有值,null 会稳定落在 0 号桶,上线前按用户类型拆开看流量分布,这类问题一眼就能发现。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。

作者 9823189