RSS

HTTP/2 上的 gRPC:打造一个健壮、高性能的协议

上一篇文章中,我们探讨了 HTTP/2 如何通过为长连接提供框架来显著提高网络效率并实现实时通信。在本文中,我们将研究 gRPC 如何基于 HTTP/2 的长连接来创建一个用于服务间通信的高性能、健壮的平台。我们将探讨 gRPC 和 HTTP/2 之间的关系、gRPC 如何管理 HTTP/2 连接,以及 gRPC 如何使用 HTTP/2 来保持连接的活跃、健康和利用率。

gRPC 语义

首先,让我们深入了解 gRPC 的概念如何与 HTTP/2 的概念相关联。gRPC 引入了三个新概念:通道1远程过程调用 (RPC) 和消息。这三者之间的关系很简单:每个通道可以有多个 RPC,而每个 RPC 可以有多个消息。

Channel mapping

让我们看看 gRPC 语义如何与 HTTP/2 相关

gRPC on HTTP/2

通道是 gRPC 中的一个关键概念。HTTP/2 中的流可以在单个连接上实现多个并发对话;通道通过在多个并发连接上启用多个流来扩展这个概念。表面上,通道为用户提供了一个发送消息的简单接口;然而,在底层,大量的工程投入用于保持这些连接的活跃、健康和利用率。

通道表示到端点的虚拟连接,实际上可能由多个 HTTP/2 连接支持。RPC 与一个连接相关联(这种关联将在稍后描述)。RPC 在实践中是普通的 HTTP/2 流。消息与 RPC 相关联,并作为 HTTP/2 数据帧发送。更具体地说,消息分层在数据帧之上。一个数据帧可能包含多个 gRPC 消息,或者如果一个 gRPC 消息非常大2,它可能会跨越多个数据帧。

解析器和负载均衡器

为了保持连接的活跃、健康和利用率,gRPC 使用了许多组件,其中最重要的是名称解析器负载均衡器。解析器将名称转换为地址,然后将这些地址传递给负载均衡器。负载均衡器负责从这些地址创建连接并在连接之间进行 RPC 的负载均衡。

Resolvers and Load Balancers

Round Robin Load Balancer

例如,DNS 解析器可能会将某个主机名解析为 13 个 IP 地址,然后 RoundRobin 负载均衡器可能会创建 13 个连接 - 每个地址一个 - 并在每个连接上循环 RPC。一个更简单的负载均衡器可能只会创建到第一个地址的连接。或者,一个想要多个连接但知道主机名只会解析为一个地址的用户,可能会让他们的负载均衡器对每个地址创建 10 次连接,以确保使用多个连接。

解析器和负载均衡器解决了 gRPC 系统中小的但至关重要的问题。这种设计是故意的:将问题空间缩小到几个小的、离散的问题,有助于用户构建自定义组件。这些组件可用于微调 gRPC 以适应每个系统的个性化需求。

连接管理

配置完成后,gRPC 将保持连接池的健康、活跃和利用率 - 如解析器和负载均衡器所定义的那样。

当连接失败时,负载均衡器将开始使用最后已知的地址列表重新连接3。同时,解析器将开始尝试重新解析主机名列表。这在许多场景中很有用。例如,如果代理不再可达,我们希望解析器更新地址列表,以排除该代理的地址。再举一个例子:DNS 条目可能会随着时间的推移而更改,因此可能需要定期更新地址列表。通过这种方式和其他方式,gRPC 被设计为具有长期的弹性。

一旦解析完成,负载均衡器会被告知新的地址。如果地址已更改,负载均衡器可能会关闭与新列表中不存在的地址的连接,或者创建与之前不存在的地址的连接。

识别失败的连接

gRPC 连接管理的有效性取决于其识别失败连接的能力。连接失败通常有两种类型:干净的失败,其中失败被通知;以及不太干净的失败,其中失败未被通知。

让我们考虑一个干净、容易观察到的失败。当端点有意终止连接时,可能会发生干净的失败。例如,端点可能已正常关闭,或者可能超过了计时器,从而提示端点关闭连接。当连接干净地关闭时,TCP 语义就足够了:关闭连接会导致 FIN 握手发生。这会结束 HTTP/2 连接,从而结束 gRPC 连接。gRPC 将立即开始重新连接(如上所述)。这非常干净,不需要额外的 HTTP/2 或 gRPC 语义。

不太干净的版本是端点在没有通知客户端的情况下死亡或挂起。在这种情况下,TCP 可能会重试长达 10 分钟,然后才认为连接失败。当然,在 10 分钟内未能识别到连接已死是不可接受的。gRPC 使用 HTTP/2 语义解决了这个问题:当使用 KeepAlive 配置时,gRPC 会定期发送 HTTP/2 PING 帧。这些帧会绕过流量控制,用于确定连接是否仍然存活。如果在及时的时间内没有返回 PING 响应,gRPC 将认为连接失败,关闭连接并开始重新连接(如上所述)。

通过这种方式,gRPC 保持连接池的健康,并使用 HTTP/2 定期确定连接的健康状况。所有这些行为对用户都是不透明的,并且消息重定向会自动且即时发生。用户只是在看似始终健康的连接池上发送消息。

保持连接活跃

如上所述,KeepAlive 提供了一个有价值的好处:通过发送 HTTP/2 PING 定期检查连接的健康状况,以确定连接是否仍然存活。但是,它还有另一个同样有用的好处:向代理发出存活信号。

考虑一个客户端通过代理向服务器发送数据。客户端和服务器可能很乐意无限期地保持连接,并在必要时发送数据。另一方面,代理通常资源受限,可能会终止空闲连接以节省资源。Google Cloud Platform (GCP) 负载均衡器会在 10 分钟后断开看似空闲的连接,而 Amazon Web Services Elastic Load Balancers (AWS ELB) 会在 60 秒后断开它们。

由于 gRPC 定期在连接上发送 HTTP/2 PING 帧,因此会创建非空闲连接的感知。使用上述空闲终止规则的端点会忽略终止这些连接。

一个健壮、高性能的协议

HTTP/2 为长期、实时的通信流提供了基础。gRPC 在此基础上构建了连接池、健康状况语义、数据帧和多路复用的有效使用以及 KeepAlive。

开发人员在选择协议时必须选择既能满足当今需求又能满足未来需求的协议。选择 gRPC 对他们来说是明智之举,无论是为了弹性、性能、长期或短期通信、可定制性,还是仅仅知道他们的协议将扩展到极其巨大的流量,同时始终保持高效。要立即开始使用 gRPC 和 HTTP/2,请查看 gRPC 的入门指南

脚注

  1. 在 Go 中,gRPC 通道被称为 ClientConn,因为“通道”一词具有特定于语言的含义。
  2. gRPC 使用 HTTP/2 的数据帧默认最大大小 16kb。超过 16kb 的消息可能会跨越多个数据帧,而小于该大小的消息可能会与一些其他消息共享一个数据帧。
  3. 这是 RoundRobin 均衡器的行为,但并非每个负载均衡器都这样做或必须这样做。