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

Envoy 内部结构

同一组上游,只换算法来数

在 TT Lab 中继续学习

目标

逐一更换负载均衡方式并统计实际的分配,亲自确认基于哈希的方式的两个性质(相同的键去往同一处;去掉一台时只有那一台的份额发生移动)。

为什么重要

负载均衡只是一行配置,但这一行配置决定了缓存命中率、会话保持,以及去掉服务器时的冲击大小。只读文档,一切都似乎合情合理;亲手统计数字之后,“随机在小样本下会偏斜到这种程度”和“去掉一台也只移动这么多”才会留在脑海里。尤其是提取不到键的请求会随机流向各处,这是会话保持故障的常见原因,只看配置是看不出来的。

步骤

  1. 以 ok 启动三个上游(8088、8089、8090)。在 /root/envd-lb/lb-rr.yaml 中放置 lb_policy: ROUND_ROBIN 的 STATIC 集群 pool 和 / 路由,并用 --concurrency 1 启动(管理端口 9951,监听器 127.0.0.1:10051)。请求 9 次,在 /root/envd-lb/01-rr.txt 中写入 p8088=、p8089=、p8090=、total= 四行。
  2. 把 /root/envd-lb/lb-rr.yaml 复制为 /root/envd-lb/lb-weight.yaml,并为每个端点设置 load_balancing_weight——8088 为 2,其余两个为 1。用该配置重新启动并请求 12 次,在 /root/envd-lb/02-weight.txt 中写入 p8088=、p8089=、p8090=、total= 四行。
  3. 创建 /root/envd-lb/lb-random.yaml——不设置权重,只把 lb_policy 改为 RANDOM 的配置。用该配置启动并请求 40 次,在 /root/envd-lb/03-random.txt 中写入 p8088=、p8089=、p8090=、total=、max_gap= 五行(max_gap 是收到次数最多的值减去收到次数最少的值)。
  4. 创建 /root/envd-lb/lb-ring.yaml——lb_policy 为 RING_HASH,ring_hash_lb_config.minimum_ring_size 为 1024,路由的 hash_policy 为头 x-user。用该配置启动后,分别用 u1 到 u9 九个用户各请求一次,在 /root/envd-lb/04-map.txt 中写入九行 사용자 포트(占位符依次为用户与端口)。
  5. 用用户 u1 请求四次,用 u2 请求四次,在 /root/envd-lb/05-sticky.txt 中写入 u1_ports=(收到的四个端口,用空格分隔)、u1_distinct=(不同端口的数量)、u2_distinct= 三行。
  6. 创建 /root/envd-lb/lb-ring2.yaml——在第 4 步的配置中只去掉端点 8090。用该配置重新启动后,用同样的九个用户再次请求,在 /root/envd-lb/06-remap.txt 中写入九行 사용자 포트(占位符依次为用户与端口)。然后与第 4 步的表对照,在 /root/envd-lb/06-moved.txt 中写入 moved=(位置发生变化的用户数)、stayed=、total=9 三行。
  7. 创建 /root/envd-lb/lb-query.yaml——端点恢复为三个,hash_policy 不使用头,而使用查询参数 uid。用该配置启动后,用 ?uid=u1 请求三次,用 ?uid=u2 请求三次,在 /root/envd-lb/07-key.txt 中写入 u1_distinct=、u2_distinct=、header_ignored=(只附加头 x-user: u1、不带查询,请求三次时不同端口的数量)三行。
  8. 在 /root/envd-lb/08-report.md 中写入 even_spread=(第 1 步的三个值,用逗号分隔)、heavy_share=(第 2 步中设置权重 2 的端点收到的比例,百分比整数)、sticky=(第 5 步中同一个用户固定在同一处则为 yes)、moved_users=(第 6 步的值)四行,并在下面至少写四行学到的内容。

参考

均匀分配是默认行为

以 ok 启动三个上游(8088、8089、8090)。在 /root/envd-lb/lb-rr.yaml 中放置 lb_policy: ROUND_ROBIN 的 STATIC 集群 pool 和 / 路由,并用 --concurrency 1 启动(管理端口 9951,监听器 127.0.0.1:10051)。请求 9 次,在 /root/envd-lb/01-rr.txt 中写入 p8088=、p8089=、p8090=、total= 四行。

轮询按列表轮流逐个选择。这是默认行为,在服务器规格相同、请求开销相近时最容易预测。每个工作线程各自记住自己的轮次,所以用默认的 concurrency(核心数)统计时,9 次请求不会得到 3 比 3 比 3。本实验把工作线程固定为一个,原因就在这里。

把规格不同的服务器放进同一组

把 /root/envd-lb/lb-rr.yaml 复制为 /root/envd-lb/lb-weight.yaml,并为每个端点设置 load_balancing_weight——8088 为 2,其余两个为 1。用该配置重新启动并请求 12 次,在 /root/envd-lb/02-weight.txt 中写入 p8088=、p8089=、p8090=、total= 四行。

增加服务器时,并不总是以相同的规格增加。新买的设备如果快一倍,就让它承担两倍的请求更合适,这时用的就是端点权重。轮询会按权重来轮转,权重 2 不是“每两次多一次”,而是“在总份额中占两倍”。总和为 4,所以 12 次请求会分成 6 比 3 比 3。

看上去均匀,其实并不均匀

创建 /root/envd-lb/lb-random.yaml——不设置权重,只把 lb_policy 改为 RANDOM 的配置。用该配置启动并请求 40 次,在 /root/envd-lb/03-random.txt 中写入 p8088=、p8089=、p8090=、total=、max_gap= 五行(max_gap 是收到次数最多的值减去收到次数最少的值)。

随机是完全不记住任何状态的方式。所以不论有多少个工作线程,结果都一样,端点加入或离开时也没有需要重新计算的东西。代价是样本较小时会明显偏斜——请与轮询在 9 次请求中精确得到 3 比 3 比 3 的情况对比。在每秒数千个请求的地方,这个差异会消失,所以规模较大的环境有时会默认使用它。

同一个用户始终去往同一台服务器

创建 /root/envd-lb/lb-ring.yaml——lb_policy 为 RING_HASH,ring_hash_lb_config.minimum_ring_size 为 1024,路由的 hash_policy 为头 x-user。用该配置启动后,分别用 u1 到 u9 九个用户各请求一次,在 /root/envd-lb/04-map.txt 中写入九行 사용자 포트(占位符依次为用户与端口)。

基于哈希的方式会对从请求中提取的键做哈希,找到环上的位置,再从该位置顺时针选择最近的端点。相同的键总是落在同一个位置,所以同一个用户会固定在同一台服务器上——本地缓存命中率会提高,服务器也可以持有会话。键由 hash_policy 选择(头、Cookie、查询参数、来源 IP)。minimum_ring_size 较小时环会稀疏,分布会偏斜。

同一个键发送四次,位置也不会变

用用户 u1 请求四次,用 u2 请求四次,在 /root/envd-lb/05-sticky.txt 中写入 u1_ports=(收到的四个端口,用空格分隔)、u1_distinct=(不同端口的数量)、u2_distinct= 三行。

如果没有这个性质,在有本地缓存的服务中,同一个用户的请求每次都会去往不同的服务器,缓存几乎无法命中。反过来,依赖这个性质时,也会带来一种风险:某一个用户可能独自让一台服务器变得很重——所以选择什么作为键才如此重要。不同值的个数用 sort -u | wc -l 统计。

去掉一台服务器,有多少人会换位

创建 /root/envd-lb/lb-ring2.yaml——在第 4 步的配置中只去掉端点 8090。用该配置重新启动后,用同样的九个用户再次请求,在 /root/envd-lb/06-remap.txt 中写入九行 사용자 포트(占位符依次为用户与端口)。然后与第 4 步的表对照,在 /root/envd-lb/06-moved.txt 中写入 moved=(位置发生变化的用户数)、stayed=、total=9 三行。

这才是使用哈希环的真正原因。如果只是用“哈希值除以服务器数量的余数”,服务器数量一变,几乎所有人都会换位。环方式只会移动原本挂在消失的服务器上的键,其余保持不变。请亲自统计移动了多少人——用 join 或 paste 把两张表并排对照会比较方便。

把键从头改成查询字符串

创建 /root/envd-lb/lb-query.yaml——端点恢复为三个,hash_policy 不使用头,而使用查询参数 uid。用该配置启动后,用 ?uid=u1 请求三次,用 ?uid=u2 请求三次,在 /root/envd-lb/07-key.txt 中写入 u1_distinct=、u2_distinct=、header_ignored=(只附加头 x-user: u1、不带查询,请求三次时不同端口的数量)三行。

从哪里提取键,就等于“把什么视为相同”。用户会话适合 Cookie,租户适合头,缓存键适合查询参数。提取不到键,就没有哈希,Envoy 会把该请求随机发送——所以缺少键的请求不会被固定。最后一行就是确认这一点。

整理成各方式的性质表

在 /root/envd-lb/08-report.md 中写入 even_spread=(第 1 步的三个值,用逗号分隔)、heavy_share=(第 2 步中设置权重 2 的端点收到的比例,百分比整数)、sticky=(第 5 步中同一个用户固定在同一处则为 yes)、moved_users=(第 6 步的值)四行,并在下面至少写四行学到的内容。

制作这张表的目的,是让你下次能自己选择“在什么时候使用哪种方式”。值取自前面步骤的文件,说明行中则写下每种方式放弃了什么、得到了什么。