TT Lab
开始
学习 学习路径 课程

Envoy 内部结构

会话固定为什么偶尔会失效

在 TT Lab 中继续学习

一句话总结

集群筛选出“可以发送的地方”之后,从中选出一个,就是负载均衡。选择的方式取决于记住多少状态——什么都不记就是随机,记住顺序就是轮询(round robin),记住请求的键就是哈希环。无论选哪一种,都要付出代价。

为什么需要它

有三台服务器时,似乎把请求各分三分之一就行了。实际上大多数情况确实如此。问题出在两个需求出现的时候。

一个是“请让这个用户始终发往同一台服务器”。如果服务器持有本地缓存,或者把会话放在内存中,这个需求同时决定了性能和正确性。另一个是“去掉一台服务器时,不能让全部重新洗牌”。如果持有缓存的服务器们一下子全部换位,去掉的那一刻整体缓存命中率就会跌到谷底,后端要承受这份负载。

同时解决这两个需求的是一致性哈希,在 Envoy 中就是 RING_HASH 和 MAGLEV。

工作原理

lb_policy 选择方式 代价
ROUND_ROBIN 按列表轮流逐个选择 每个工作线程各自记住自己的轮次
LEAST_REQUEST 随机抽取两个,选择进行中请求较少的那个 请求开销参差不齐时更有利
RANDOM 每次随机 无需记住任何状态,开销低。样本较小时会偏斜
RING_HASH 对键做哈希,选择环上最近的端点 构建环的开销。环稀疏时分布会偏斜
MAGLEV 用固定大小的查找表,以更低的开销得到同样的性质 表的大小固定,端点非常多时会受到限制

可以为每个端点设置 load_balancing_weight。权重 2 并不是“每两次多一次”,而是指在总份额中占两倍。三个端点的权重为 2、1、1 时,总和为 4,所以 12 次请求会分成 6、3、3。

基于哈希的方式需要键。选择键的是路由的 hash_policy,可以从头、Cookie、查询参数、来源 IP 中提取。这里有一点经常被忽略——提取不到键的请求没有哈希,所以会直接随机发送。没有 Cookie 的第一个请求、漏掉头的内部调用就是这样。“大部分是固定的,偶尔会去别处”的真相,多半就是它。

环的性质也值得了解。如果只是用“哈希值除以服务器数量的余数”,服务器数量一变,几乎所有的键都会换位。环方式则只有原本挂在消失的服务器上的键会移走,其余保持不变。代价是,环如果稀疏(minimum_ring_size 较小),端点在环上就分布不均,分布本身会偏斜。

在现场相遇的样子

“发了 9 次,却没有得到 3 比 3 比 3。” Envoy 的每个工作线程各自记住轮询的轮次。默认的 concurrency 是核心数,所以用较少的请求来统计,会分散到各个工作线程,数字每次都不同。统计分配情况的实验要用 --concurrency 1。在生产环境中不要去数,而要看统计数据。

“开启了会话保持,却偶尔失效。”先看是否混入了没有键的请求。另外,在端点加入或离开的瞬间,有一部分会移走是正常的——一致性哈希不是“谁都不移动”,而是“把移动的数量降到最低”。

只有特定服务器变热的情况。这是哈希键的分布偏斜造成的。如果用租户 ID 作为键,而某一个租户占了一半流量,那么该租户所在的服务器就会承担一半。此时要把键拆得更细(按用户),或者把这个租户单独拿出来。

选择方式的标准。归纳起来就是两个问题。第一,同一个请求是否必须去往同一个地方。如果涉及本地缓存或会话,就必须如此,那就选基于哈希的方式。第二,请求开销是否参差不齐。如果有的请求 5 毫秒,有的请求 2 秒,按顺序分配的方式会不断给收到慢请求的服务器增加工作。这时看进行中请求数量的 LEAST_REQUEST 更好。两者都不是的话,使用轮询最容易预测;规模非常大时,不记状态的随机反而更便宜。

官方文档:Load balancing overview · Cluster configuration · HTTP route components

下一项实验要做什么

使用同样的三个上游,只更换方式并统计数字。依次确认轮询如何精确分配、权重如何改变份额、随机在小样本下如何偏斜;在哈希环上,亲自统计同一个用户是否固定在同一处,以及去掉一台服务器时有多少用户换位。最后把键从头改成查询参数,观察没有键的请求不再固定。