加权流量切分 / canary · 同一入口按比例发往不同版本
前面所有页里,「目标」都是一个确定值:这条 path 命中这个 handler、这条短链跳那个地址。流量切分把目标变成概率分布——网关选中一个 upstream「组」后,组里有 stable、canary
等多个版本同时在线,路由层按权重把请求分给它们:90% 走稳定版、10% 走金丝雀(canary)。这是「稳定入口 ↔ 可变目标」的概率版,也是灰度发布、蓝绿、A/B 的共同底座。
拖动权重滑块改变分流比例,发一批请求看实际落点怎样逼近配置的比例(大数定律);再看两个关键机制:请求头定向(带 x-canary 的内部请求绕过权重、强制进新版,用于上线前自测),以及会话黏滞(sticky)——按
user-id 哈希分流,保证同一个用户每次都落在同一版本,不会在新旧版本之间来回切换。
金丝雀(canary)的意义。 新版本上线不是「一刀切全量替换」,而是先放一点点流量(如 5%)进去,盯着错误率、延迟、业务指标——没问题就逐步把权重调大(5% → 25% → 100%),出问题就把权重调回 0 瞬间回滚,已经发出去的请求一行代码都不用改。整个过程只是路由层那几个权重数字在变——这就是把「目标」从确定值换成可随时调整的概率分布带来的能力。蓝绿发布是它的特例(两个版本、权重在 100/0 与 0/100 间整体切换)。
1 · 会话黏滞:为什么不能每个请求都重新掷骰子
如果每个请求都独立随机分流,同一个用户连续点几下可能时而落新版、时而落旧版——界面 / 数据结构在两版间反复横跳,体验破碎,A/B 实验的数据也被污染。解法是按用户哈希:把 user-id 算成一个 [0,1) 的稳定数,落在累积权重区间的哪一段就去哪个版本。同一个
user-id 哈希值恒定,于是每次都落同一版本(sticky);而大量不同用户的哈希值近似均匀分布,整体比例仍然符合权重。输入一个 user-id 看它被钉在哪一版。
下面把三种分流并排写出来:无状态随机、按 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 系统。无论哪层,模型都一样:一张「版本 → 权重」表 + 一个把请求映射到 [0,1) 的函数。把映射函数从「随机」换成「user-id 哈希」就得到 sticky,换成「请求头判断」就得到定向——三者经常叠在一起用。
2 · 相关页
- 本系列 · 反向代理 / API 网关——网关先按 location 选中一个 upstream「组」,这页接着把组内多个版本按权重分流——两页是 dispatch 的上下半场。
- 本系列 · 为什么要有路由这一层——「灰度与下线」在那页是动机之一,这页给出它的具体机制:目标不是一个地址,而是一组带权重、可随时调整的版本。
- Istio · Traffic Management · istio.io——service mesh 怎样用 VirtualService / DestinationRule 按 weight 把流量切到不同 subset,并支持按 header 定向——本页模型的生产实现。