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) 可以在单个连接上实现多个并发对话;通道通过允许在多个并发连接上进行多个流来扩展此概念。从表面上看,通道为用户发送消息提供了一个简单的接口;但在底层,为了保持这些连接的活跃、健康和高效利用,投入了大量的工程努力。

通道代表到端点的虚拟连接,实际上可能由许多 HTTP/2 连接支撑。RPC 与某个连接相关联(这种关联将在后文详细描述)。实际上,RPC 就是普通的 HTTP/2 流。消息与 RPC 相关联,并作为 HTTP/2 数据帧 (Data Frames) 发送。更具体地说,消息是分层在数据帧之上的。一个数据帧可能包含多条 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,因为“通道”一词在 Go 语言中有其特定的含义。
  2. gRPC 使用 HTTP/2 默认的 16kb 数据帧最大限制。超过 16kb 的消息可能会跨越多个数据帧,而小于此大小的消息可能会与其他多条消息共享一个数据帧。
  3. 这是 RoundRobin 负载均衡器的行为,但并非每个负载均衡器都必须或应当以这种方式运行。