gRPC 客户端负载均衡

gRPC client side load balancing

我在 kubernetes pods 中将 gRPC 与 Python 用作 client/server... 我希望能够启动多个 pods 相同类型(gRPC 服务器)并让客户端(随机)连接到它们。

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

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

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

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

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

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

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

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

客户端 LB 保持简单,"dumb" 是有意为之。我们选择实施复杂 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],该策略将尝试与所有这些建立连接。第一个成功连接的人将成为被选中的人直到它断开连接。因此名称 "pick-first"。更长的名称可以是 "pick first and stick with it for as long as possible",但这样会产生很长的文件名 :)。 If/when 被选中的地址断开连接,优先选择策略将转移到返回下一个成功连接的地址(内部称为 "connected subchannel"),如果有的话。再一次,只要它保持连接,它就会继续选择这个连接的子通道。如果全部失败,调用将失败。

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

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

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

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

This is because gRPC is built on HTTP/2, and HTTP/2 is designed to have a single long-lived TCP connection, across which all requests are multiplexed—meaning multiple requests can be active on the same connection at any point in time. Normally, this is great, as it reduces the overhead of connection management. However, it also means that (as you might imagine) connection-level balancing isn’t very useful. Once the connection is established, there’s no more balancing to be done. All requests will get pinned to a single destination pod.

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