gRPC 客户端负载均衡

Posted

技术标签:

【中文标题】gRPC 客户端负载均衡【英文标题】:gRPC client side load balancing 【发布时间】:2017-01-31 07:33:17 【问题描述】:

我在 kubernetes pod 中使用 gRPC 和 Python 作为客户端/服务器... 我希望能够启动多个相同类型的 Pod(gRPC 服务器)并让客户端(随机)连接到它们。

我分派了 10 个服务器 pod 并设置了一个“服务”来定位它们。然后,在客户端,我连接到服务的 DNS 名称——这意味着 kubernetes 应该进行负载平衡并将我引导到一个随机的服务器 pod。 实际上,客户端调用 gRPC 函数(效果很好),但是当我查看日志时,我发现所有调用都转到同一个服务器 pod。

我认为客户端正在执行某种 DNS 缓存,这会导致所有调用都发送到同一台服务器。是这样吗?无论如何禁用它并设置相同的存根客户端进行“新”调用并在每次调用时通过 DNS 获取新 ip?

我知道如果它每次都查询 DNS 服务器可能会导致开销,但目前分配负载对我来说更重要。

【问题讨论】:

【参考方案1】:

如果您创建了普通 Kubernetes 服务,该服务应该有自己的负载平衡虚拟 IP(检查 kubectl get svc your-service 是否为您的服务显示 CLUSTER-IP)。如果是这种情况,DNS 缓存应该不是问题,因为单个虚拟 IP 应该在实际后端之间分割流量。

尝试kubectl get endpoints your-service 以确认您的服务确实了解您的所有后端。

如果您有 headless service,DNS 查找将返回一个包含 10 个 IP 的 A 记录(每个 Pod 一个)。如果您的客户总是选择 A 记录中的第一个 IP,这也可以解释您所看到的行为。

【讨论】:

服务器有一个 CLUSTER_IP,当我执行“获取端点”时,我可以看到我拥有所有端点。仍然去同一台服务器。我认为这可能是 gRPC 的工作方式以及它的 HTTP/2 的连接重用......【参考方案2】:

让我借此机会通过描述事情应该如何工作来回答。

客户端 LB 在 gRPC C 核心(除 Java 和 Go 风格或 gRPC 之外的所有基础)中的工作方式如下(可以找到权威文档 here):

客户端 LB 故意保持简单和“愚蠢”。我们选择实施复杂 LB 策略的方式是通过外部 LB 服务器(如上述文档中所述)。您不关心这种情况。相反,您只是创建一个通道,它将使用(默认)pick-first LB 策略。

LB 策略的输入是已解析地址的列表。使用 DNS 时,如果 foo.com 解析为 [10.0.0.1, 10.0.0.2, 10.0.0.3, 10.0.0.4],该策略将尝试与所有这些服务器建立连接。第一个成功连接的将成为被选中的直到它断开。因此得名“先选”。更长的名称可能是“首先选择并尽可能长时间地使用它”,但这使得文件名很长:)。如果/当拣选的拾取器断开连接时,选择策略将转移到返回下一个成功连接的地址(内部称为“连接子信道”),如果有的话。再一次,只要它保持连接,它就会继续选择这个连接的子通道。如果全部失败,调用就会失败。

这里的问题是,DNS 解析本质上是基于拉的,仅在 1) 通道创建时和 2) 所选连接的子通道断开连接时触发。

截至目前,一个 hacky 解决方案是为每个请求创建一个新通道(效率非常低,但根据您的设置它可以解决问题)。

鉴于 2017 年第一季度即将发生的变化(请参阅 https://github.com/grpc/grpc/issues/7818)将允许客户选择不同的 LB 策略,即循环。此外,我们可能会考虑在该客户端配置中引入“随机化”位,这将在对地址进行循环之前对地址进行洗牌,从而有效地实现您的意图。

【讨论】:

感谢您的详细解答。实际上,我已经按照您的建议进行了操作,并为每个请求创建了一个新频道(效率不高,我知道)。从您的回答中,我了解到只有 dns 中的第一个 ip 会收到请求,直到它停止(没有可用的连接/被杀死/崩溃),然后客户端才会到达第二个 ip,依此类推……对吗? 是的。如前所述,在更改之前可以选择循环代替挑选优先作为 LB 策略。 在扩展多个 gRPC 服务器方面是否有任何典型的解决方案?还是客户端负载均衡?【参考方案3】:

通常的 K8S 负载平衡不适用于 gRPC。以下链接解释了原因。 https://kubernetes.io/blog/2018/11/07/grpc-load-balancing-on-kubernetes-without-tears/

这是因为 gRPC 建立在 HTTP/2 之上,而 HTTP/2 旨在 有一个长期存在的 TCP 连接,所有请求都通过该连接 多路复用——意味着多个请求可以在同一个上处于活动状态 随时连接。通常,这很棒,因为它 减少连接管理的开销。然而,这也意味着 那个(正如你想象的那样)连接级别的平衡不是很好 有用。一旦建立连接,就没有更多的平衡 要完成。所有请求都将固定到单个目标 pod。

大多数现代入口控制器都可以处理这个问题,但是它们要么是热的烤箱 (nginx),要么是 alpha 版本 (traefik),或者需要最新版本的 K8S (Linkerd)。可以做客户端负载均衡,可以找到Java解决方案here。

【讨论】:

以上是关于gRPC 客户端负载均衡的主要内容,如果未能解决你的问题,请参考以下文章

gRPC 客户端负载均衡

GRPC负载均衡

Go gRPC 客户端服务保障如何做?负载均衡重试健康检查

为什么对gRPC做负载均衡会很棘手?

gRPC负载均衡(自定义负载均衡策略--etcd 实现)

gRPC Load Balancing