gRPC
使用 HTTP/2
作为其传输协议,实现通过网络发送消息。这也是 gRPC
能够成为高性能 RPC
框架的原因之一。
在 HTTP/2
中,客户端和服务器端的所有通信都是通过一个 TCP
连接完成的,这个连接可以传送任意数量的双向字节流。
相关术语如下:
- 流(
stream
):在一个已建立的连接上的双向字节流。一个流可以携带一条或多条消息。 - 帧(
frame
):HTTP/2
中最小的通信单元。每一帧都包含一个帧头,它至少要标记该帧所属的流。 - 消息(
message
):完整的帧序列,映射为一条逻辑上的HTTP
消息,由一帧或多帧组成。这样的话,允许消息进行多路复 用,客户端和服务器端能够将消息分解成独立的帧,交叉发送 它们,然后在另一端进行重新组合。
如图所示,gRPC
通道代表一个到端点的连接,也就是一个 HTTP/2
连接。当客户端应用程序创建 gRPC
通道的时候,它会在幕后创建一个到服务器端的 HTTP/2
连接。在通道创建完成之后,就可以重用它来发送多个到服务器端的远程调用。这些远程调用会映射为 HTTP/2
中的 流。远程调用中的消息以 HTTP/2
帧的形式进行发送,帧可能会携带一 条 gRPC
长度前缀的消息,也可能在 gRPC
消息非常大的情况下,一条消息跨多帧。
1. 请求消息
请求消息用于初始化远程调用。在 gRPC
中,请求消息始终由客户端应用程序来触发,它包含 3 部分:
- 请求头信息
- 以长度作为前缀的消息
- 流结束标记(end of stream flag,以下简称 EOS 标记),
如图所示。远程调用在客户端发送请求头信息之后就会初始化,然后其中会发送以长度作为前缀的消息,最后发送 EOS
标记,通知收件方请求消息已发送。
当调用 getProduct
方法时,客户端会通过发送下面的请求头信息来初始化调用。
HEADERS (flags = END_HEADERS)
:method = POST ➊
:scheme = http ➋
:path = /ProductInfo/getProduct ➌
:authority = abc.com ➍
te = trailers ➎
grpc-timeout = 1S ➏
content-type = application/grpc ➐
grpc-encoding = gzip ➑
authorization = Bearer xxxxxx ➒
- ❶ 定义
HTTP
方法。对gRPC
来说,:method
头信息始终为POST
。 - ❷ 定义
HTTP
模式。如果启用传输层安全协议(Transport Level Security,TLS
),就将模式设置为https
,否则设置为http
。 - ❸ 定义端点路径。对
gRPC
来说,这个值的构造为/{ 服务名 }/{ 方 法名 }
。 - ❹ 定义目标
URI
的虚拟主机名。 - ❺ 定义对不兼容代理的检测。在
gRPC
中,这个值必须为trailers
。 - ❻ 定义调用的超时时间。如果没有指定,服务器端会假定超时时间无 穷大。
- ❼ 定义
content-type
。对gRPC
来说,content-type
应该以application/grpc
开头。否则,gRPC
会给出HTTP
状态为 415(不 支持的媒体类型)的响应。 - ❽ 定义消息的压缩类型。可选的值是
identity
、gzip
、deflate
、snappy
和{custom}
。 - ❾ 这是可选的元数据。
authorization
元数据用来访问安全的端点。
其它注意点:
- 名称以
:
开头的头信息叫作保留头信息,HTTP/2
要求保留头信息出现在其他头信息之前。 gRPC
通信中所传递的头信息分为两类:调用定义的头信息 (call-definition header)和自定义元数据。- 调用定义的头信息是
HTTP/2
预定义的头信息。这些头信息应该在自定义元数据之前发送。 - 自定义元数据是由应用程序层定义的任意一组键–值对。在声明自定义元数据时,需要确保不要使用以
grpc-
开头的名称。在gRPC
核心中,这被列为保留名字。
当完成对服务器端调用的初始化之后,客户端会以 HTTP/2
数据帧的形式发送以长度作为前缀的消息。如果这条消息不适合放到一个数据帧中,那么它可以跨多个数据帧。请求消息的结束通过在最后一个 DATA
帧上添加 END_STREAM
标记来实现。当因为没有要发送的数据而需要关闭请求流时,必须发送一个带有 END_STREAM
标记的空数据帧:
DATA (flags = END_STREAM)
<Length-Prefixed Message>
2. 响应消息
响应消息由服务器端生成,用来响应客户端的请求。与请求消息类似, 在大多数场景中,响应消息也包含 3 个主要部分:响应头信息、以长度作为前缀的消息以及 trailer。如果没有发送以长度作为前缀的消息来响应客户端,则响应消息只会包含头信息和 trailer,如图所示:
下面通过同一个示例来介绍响应消息的 HTTP/2
帧序列。当服务器端发送响应消息至客户端时,首先会发送如下所示的响应头信息。
HEADERS (flags = END_HEADERS)
:status = 200 ➊
grpc-encoding = gzip ➋
content-type = application/grpc ➌
- ❶ 表明
HTTP
请求的状态。 - ❷ 定义消息的压缩类型。可选的值是
identity
、gzip
、deflate
、snappy
和{custom}
。 - ❸ 定义
content-type
。对gRPC
来说,content-type
应该以application/grpc
开头。
与请求头信息类似,应用程序层所定义的自定义元数据也可以按照任意键–值对集的形式在响应头信息中进行发送。
服务器端发送完响应头之后,以长度作为前缀的消息就会以 HTTP/2
数据帧的形式在调用中进行发送。与请求消息类似,如果该消息不适合放到一个数据帧中,那么它可以跨多个数据帧。
DATA
<Length-Prefixed Message>
如下所示,END_STREAM
标记并不会随数据帧一起发送,而会作为单独的头信息来发送,名为 trailer
,最后,通过发送 trailer
来提醒客户端响应消息已发送。trailer
还会携带状态码以及请求的状态信息。
HEADERS (flags = END_STREAM, END_HEADERS)
grpc-status = 0 # OK ➊
grpc-message = xxxxxx ➋
- ❶ 定义
gRPC
状态码。gRPC
会使用一组定义良好的状态码。这些状态码的定义可以在gRPC
官方文档中找到。 - ❷ 定义对错误的描述。这是可选的,只有在处理请求出现错误时,才会进行设置。
trailer
会以 HTTP/2
头信息帧的形式进行投递,但会在响应消息结束时发送。响应 EOS
标记就是在 trailer
头信息中设置的 END_STREAM
标记。另外,它还会包含 grpc-status
头信息和 grpc-message
头信息。
在特定的场景中,请求调用可能会立即失败。在这些情况下,服务器端需要发回一个不包含数据帧的响应。因为服务器端只发送 trailer
作为响应,所以这些 trailer
也会以 HTTP/2
头信息帧的形式进行投递,同时会包含 END_STREAM
标记。另外,trailer
会包含下面的头信息。
HTTP
状态:status
- 内容类型:
content-type
- 状态:
grpc-status
- 状态信息:
grpc-message
3. gRPC 通信模式中的消息流
3.1 简单 RPC 模式
在一元 RPC
模式中,gRPC
服务器端和 gRPC
客户端的通信始终只涉及一个请求和一个响应。如图 所示,请求消息包含头信息, 随后是以长度作为前缀的消息,该消息可以跨一个或多个数据帧。 消息最后会添加一个 EOS
标记,方便客户端半关(half-close
)连 接,并标记请求消息的结束。在这里,“半关”指的是客户端在自己的一侧关闭连接,这样一来,客户端无法再向服务器端发送消息, 但仍能够监听来自服务器端的消息。只有在接收到完整的消息之后,服务器端才生成响应。响应消息包含一个头信息帧,随后是以长度作为前缀的消息。当服务器端发送带有状态详情的 trailer
头信息之后,通信就会关闭。
3.2 服务器端 RPC 模式
从客户端的角度来说,一元 RPC
模式和服务器端流 RPC
模式具有相同的请求信息流。这两种情况都是发送一条请求消息,主要差异在于服务器端。
在服务器端流 RPC
模式中,服务器端不再向客户端发送一条响应消息,而会发送多条响应消息。服务器端会持续等待,直到接收到完整的请求消息,随后它会发送响应头消息和多条以长度作为前缀的消息,如图所示。在服务器端发送带有状态详情的 trailer
头信息之后,通信就会关闭。
3.3 客户端 RPC 模式
在客户端流 RPC
模式中,客户端向服务器端发送多条消息,服务器端在响应时发送一条消息。
客户端首先通过发送头信息帧来与服务器端建立连接,然后以数据帧的形式,向服务器端发送多条以长度作为前缀的消息,如图所示。最后,通过在末尾的数据帧中发送 EOS
标记,客户端将连接设置为半关的状态。与此同时, 服务器端读取所接收到的来自客户端的消息。在接收到所有的消息之后,客户端发送一条响应消息和 trailer
头信息,并关闭连接。
3.4 双向流 RPC 模式
在这种模式中,客户端和服务器端都会给对方发送多条消息,直到它们关闭连接为止。
在双向流 RPC
模式中,客户端通过发送头信息帧与服务器端建立连接。然后,它们会互发以长度作为前缀的消息,无须等待对方结 束。如图所示,客户端和服务器端会同时发送消息。两者都可以在自己的一侧关闭连接,这意味着它们不能再发送消息了。
参考:《gRPC与云原生应用开发》