RSS

基于 HTTP/2 的 gRPC:设计一种强大、高性能的协议

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

gRPC 语义

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

Channel mapping

让我们看看 gRPC 语义与 HTTP/2 的关系

gRPC on HTTP/2

通道是 gRPC 中的一个关键概念。HTTP/2 中的流(Streams)允许在单个连接上进行多个并发对话;通道(channels)通过允许多个并发连接上的多个流来扩展这个概念。表面上,通道为用户提供了一个易于发送消息的接口;然而,在底层,大量的工程工作被投入到保持这些连接的活跃、健康和被充分利用。

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

解析器和负载均衡器

为了保持连接的活跃、健康和被充分利用,gRPC 利用了许多组件,其中最重要的是名称解析器(name resolvers)负载均衡器(load balancers)。解析器将名称转换为地址,然后将这些地址交给负载均衡器。负载均衡器负责根据这些地址创建连接,并在连接之间对 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 ELBs) 在60 秒后断开。

由于 gRPC 定期在连接上发送 HTTP/2 PING 帧,从而产生了非空闲连接的感知。使用上述空闲关闭规则的端点将跳过关闭这些连接。

一种强大、高性能的协议

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

选择协议的开发者必须选择既能满足当前需求又能满足未来需求的协议。他们通过选择 gRPC 可以获得良好的服务,无论是出于其恢复能力、性能、长连接还是短连接通信、可定制性,或者仅仅是因为知道他们的协议能够在高效的同时扩展到极其庞大的流量。要立即开始使用 gRPC 和 HTTP/2,请查看 gRPC 的入门指南

脚注

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