加权流量切分与金丝雀发布
前面所有页里,「目标」都是一个确定值:这条 path 命中这个 handler、这条短链跳那个地址。流量切分把目标变成概率分布——网关选中一个 upstream「组」后,组里有 stable、canary 等多个版本同时在线,路由层按权重把请求分给它们:90% 走稳定版、10%
走金丝雀(canary)。这是「稳定入口 ↔ 可变目标」的概率版,也是灰度发布、蓝绿与 A/B 的共同底座。
除了权重本身,还有两个关键机制:请求头定向让带 x-canary 的内部请求绕过权重、强制进新版,用于上线前自测;会话黏滞(sticky)按 user-id 哈希分流,保证同一个用户每次都落在同一版本,不在新旧版之间来回切换。
金丝雀(canary)的意义在于新版本上线不是一刀切全量替换,而是先放一点点流量进去,盯着错误率、延迟与业务指标,没问题就逐步调大权重,出问题就把权重调回 0 瞬间回滚,已经发出去的请求一行代码都不用改。整个过程只是路由层那几个权重数字在变。蓝绿发布是它的特例,两个版本、权重在 100/0 与 0/100 之间整体切换。
本页引擎的分流质量可以量出来。穷举 100000 个 key,权重 90/10/0 时实测落点为 89.61% 与 10.39%,50/50 时为 50.14% 与 49.86%,34/33/33 时为 33.89%、32.64%、33.46%,误差都在 0.4 个百分点以内。
1 · 会话黏滞
如果每个请求都独立随机分流,同一个用户连续点几下可能时而落新版、时而落旧版,界面与数据结构在两版之间反复横跳,体验破碎,A/B 实验的数据也被污染。
解法是按用户哈希:把 user-id 算成一个
区间上的稳定数,落在累积权重区间的哪一段就去哪个版本。同一个 user-id 哈希值恒定,于是每次都落同一版本;而大量不同用户的哈希值近似均匀分布,整体比例仍然符合权重。
user-id 哈希的黏滞分流。可输入不同的 user-id,看它被钉在哪一版;同一个 id 反复输入结果不变。这种哈希分流还有一个对灰度放量至关重要的性质:调整权重时,只有必要的那部分 key 会换桶。实测把权重从 90/10 调到 80/20,恰有 10.03% 的 key 跳桶——正是新增给 canary 的那 10%,原本落在 canary 的用户一个都没被换走。而 90/10 调到 50/50 时有 39.48% 跳桶,同样接近理论上必须搬动的 40%。累积权重区间是从左往右排的,调大后面的权重不会打乱前面已经落定的那段。
下面把三种分流并排写出来:无状态随机、按 user 哈希的 sticky、以及请求头定向,都落在同一个「累积权重加落点」模型上。
const weights = { stable: 90, canary: 10, beta: 0 };
const total = 90 + 10 + 0;
// 把一个 [0,1) 的点映射到累积权重区间 → 命中的版本
function pick(r) {
let acc = 0;
for (const [name, w] of Object.entries(weights)) {
acc += w / total;
if (r < acc) return name; // 落在这一段 → 这个版本
}
}
// 无状态分流: 每个请求重新掷骰子 (整体比例对, 但同一用户会跳版本)
const target = pick(Math.random());
// sticky 分流: 用 user-id 哈希代替随机 —— 同一 id 哈希恒定 → 每次同一版本
function hash01(s) { // 字符串 → [0,1) 稳定哈希
let h = 2166136261;
for (const ch of s) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619); }
return (h >>> 0) / 2 ** 32;
}
const stickyTarget = pick(hash01(userId));
// 请求头定向: 内部自测请求绕过权重, 强制进新版
if (req.headers['x-canary'] === 'true') return 'canary';
建议 · 权重路由可以住在网关(nginx 的 split_clients 或 upstream 权重)、service mesh 的 sidecar(Istio 用 VirtualService 按 weight 切 subset)、也可以在边缘或应用内的 feature flag 系统。无论哪层模型都一样:一张「版本到权重」的表,加一个把请求映射到
的函数。把映射函数从随机换成 user-id 哈希就得到 sticky,换成请求头判断就得到定向,三者经常叠在一起用。
网关先按 location 选中一个 upstream 组,本页接着把组内多个版本按权重分流,两页是 dispatch 的上下半场。灰度与下线在为什么要有路由这一层是动机之一,本页给出它的具体机制。