bigywater.tech
gRPC · 14

负载均衡:pick_first 与 round_robin 的区别

grpc-go 默认的负载均衡策略是什么?round_robin 和它差在哪里?后端被停掉、地址列表更新、有一个后端很慢,分别会怎样?这篇用三个本机后端逐项实测。

[300 0 0] vs [100 99 101]
pick_first 与 round_robin 把 300 次调用分给 3 个后端的结果
[60 0 0]
pick_first 在服务发现加入新后端之后,60 次调用仍全部打在原后端
3042.4 ms vs 3.4 ms
同样 30 次调用,pick_first 取决于第一个地址是不是那个慢后端

pick_first

实测 · 本机执行输出
  pick_first  三个后端收到的次数 [300 0 0]
  round_robin 三个后端收到的次数 [100 99 101]

默认策略:按地址顺序试,连上第一个能用的就只用它,其余是备份。连接没断就不换,即使地址列表里加了新后端。

round_robin

实测 · 本机执行输出
  客户端 1:1 2 0 1 2 0 1 2 0
  客户端 2:0 2 0 1 2 0 1 2 0
  客户端 3:0 2 0 1 2 0 1 2 0
  客户端 4:0 2 0 1 2 0 1 2 0
  客户端 5:2 0 1 2 0 1 2 0 1
  客户端 6:2 2 0 1 2 0 1 2 0

给每个地址建一个子连接,在就绪的子连接中轮流选。顺序的起点随机,只在已就绪的后端里选。

后端停掉

实测 · 本机执行输出
  pick_first  前 8 次之后停掉后端 0、等 50 ms:0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
  pick_first  停掉后端 0 之后马上继续调用,重复 20 次、共 320 次调用:有失败的有 15 次,失败的调用共 15 个;第一个失败 code=Unavailable message="error reading from server: read tcp 127.0.0.1:58184->127.0.0.1:9806: read: connection reset by peer"
  round_robin 前 8 次之后停掉后端 0、等 50 ms:1 1 1 2 0 1 2 0 1 2 1 2 1 2 1 2 1 2 1 2 1 2 1 2
  round_robin 停掉后端 0 之后马上继续调用,重复 20 次、共 320 次调用:有失败的有 5 次,失败的调用共 5 个;第一个失败 code=Unavailable message="error reading from server: EOF"

两种策略都会自动切到别的后端。切换完成之前正好发出去的调用会失败(Unavailable),需要靠重试(第 18 篇)兜底。

地址更新

实测 · 本机执行输出
  pick_first  地址 [0 1]           三个后端收到的次数 [60 0 0]
  pick_first  加入 2 → [0 1 2]     三个后端收到的次数 [60 0 0]
  pick_first  去掉 0 → [1 2]       三个后端收到的次数 [0 60 0]
  round_robin 地址 [0 1]           三个后端收到的次数 [30 30 0]
  round_robin 加入 2 → [0 1 2]     三个后端收到的次数 [20 20 20]
  round_robin 去掉 0 → [1 2]       三个后端收到的次数 [0 30 30]

round_robin 会立即把新地址纳入轮询,pick_first 不会。想让新加的后端分到流量,不能用 pick_first。

慢后端

实测 · 本机执行输出
  慢的是后端 0:pick_first  30 次共 3042.4 ms;各后端次数 [30 0 0]
  慢的是后端 0:round_robin 30 次共 1014.7 ms;各后端次数 [10 10 10]
  慢的是后端 2:pick_first  30 次共 3.4 ms;各后端次数 [30 0 0]
  慢的是后端 2:round_robin 30 次共 1016.4 ms;各后端次数 [10 10 10]

round_robin 不看后端响应快慢,慢后端分到的比例和快的一样。需要感知负载要用 least_request 或带权重的策略(本篇没有测)。

shuffle

实测 · 本机执行输出
  默认                       三个后端收到的次数 [30 0 0]
  shuffleAddressList=true  三个后端收到的次数 [12 9 9]

pick_first 可以配置 shuffleAddressList,让不同客户端挑选不同的第一个地址,减轻“所有客户端都连同一个后端”的问题。

分步演示

负载均衡
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1 / grpc-go v1.84.0 / protobuf-go v1.36.12 / golang.org/x/net v0.57.0 / protoc 36.2(Homebrew);服务端和客户端都是本机起的 Go 进程(127.0.0.1,端口 9800 以上,不访问任何外部服务);需要看线上字节时用我自己写的 TCP 抓包代理加 golang.org/x/net/http2 的 Framer 解析 HTTP/2 帧;计时用 time.Now 或 perf_counter,数字每次运行会有小幅抖动。后端是本机 3 个 grpc 服务,用 resolver/manual 提供地址;“谁回的”由 handler 返回的编号得出;故障切换的“马上继续调用”重复了 20 次,因为是否有失败的调用取决于时序;选择器起点随机、只在就绪的子连接里选,以及 shuffleAddressList 的含义,读自 grpc-go 源码(endpointsharding.go、pickfirst.go)
    • 没有验证:least_request、weighted_round_robin、ring_hash 等其它策略;DNS 解析器下的行为(第 15 篇);多个后端同时挂掉的情形;后端是“半死不活”(接受连接不回应)时的切换;每个后端地址对应多个 IP 的情况
    京ICP备2026067502号-1京公网安备11010502063487号