会话固定为什么偶尔会失效
一句话总结
集群筛选出“可以发送的地方”之后,从中选出一个,就是负载均衡。选择的方式取决于记住多少状态——什么都不记就是随机,记住顺序就是轮询(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
下一项实验要做什么
使用同样的三个上游,只更换方式并统计数字。依次确认轮询如何精确分配、权重如何改变份额、随机在小样本下如何偏斜;在哈希环上,亲自统计同一个用户是否固定在同一处,以及去掉一台服务器时有多少用户换位。最后把键从头改成查询参数,观察没有键的请求不再固定。