同一组上游,只换算法来数
目标
逐一更换负载均衡方式并统计实际的分配,亲自确认基于哈希的方式的两个性质(相同的键去往同一处;去掉一台时只有那一台的份额发生移动)。
为什么重要
负载均衡只是一行配置,但这一行配置决定了缓存命中率、会话保持,以及去掉服务器时的冲击大小。只读文档,一切都似乎合情合理;亲手统计数字之后,“随机在小样本下会偏斜到这种程度”和“去掉一台也只移动这么多”才会留在脑海里。尤其是提取不到键的请求会随机流向各处,这是会话保持故障的常见原因,只看配置是看不出来的。
步骤
- 以
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=四行。 - 把
/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=四行。 - 创建
/root/envd-lb/lb-random.yaml——不设置权重,只把lb_policy改为RANDOM的配置。用该配置启动并请求 40 次,在/root/envd-lb/03-random.txt中写入p8088=、p8089=、p8090=、total=、max_gap=五行(max_gap是收到次数最多的值减去收到次数最少的值)。 - 创建
/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中写入九行사용자 포트(占位符依次为用户与端口)。 - 用用户
u1请求四次,用u2请求四次,在/root/envd-lb/05-sticky.txt中写入u1_ports=(收到的四个端口,用空格分隔)、u1_distinct=(不同端口的数量)、u2_distinct=三行。 - 创建
/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三行。 - 创建
/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、不带查询,请求三次时不同端口的数量)三行。 - 在
/root/envd-lb/08-report.md中写入even_spread=(第 1 步的三个值,用逗号分隔)、heavy_share=(第 2 步中设置权重 2 的端点收到的比例,百分比整数)、sticky=(第 5 步中同一个用户固定在同一处则为 yes)、moved_users=(第 6 步的值)四行,并在下面至少写四行学到的内容。
参考
- 所有统计分配情况的步骤都必须用
--concurrency 1启动。工作线程有多个时,轮次会在各线程之间各自独立,数字每次都会不同。 - 上游用
python3 /opt/lab/envoy/upstream.py <포트> ok(占位符为端口)启动。响应正文是ok:<포트> <경로>(占位符依次为端口与路径),所以可以统计是哪一个收到的。 - 重新启动 Envoy 之前,请用
pkill -x envoy清理,并用循环等待启动,直到/ready返回 LIVE。 - 不同值的个数用
sort -u | wc -l统计。 - 常见错误——把
hash_policy写在集群而不是路由里。键是从请求中提取的,所以要写在路由的route之下。
均匀分配是默认行为
以 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 步的值)四行,并在下面至少写四行学到的内容。
制作这张表的目的,是让你下次能自己选择“在什么时候使用哪种方式”。值取自前面步骤的文件,说明行中则写下每种方式放弃了什么、得到了什么。