gRPC 核心概念和生命周期
RPC
- [1] gRPC简介2024年5月5日
- [2] gRPC 核心概念和生命周期2024年5月5日
- [3] ProtoBuf(Protocol Buffers)简介2024年5月5日
- [4] ProtoBuf(Protocol Buffers)历史2024年5月5日
概述
本篇介绍 gRPC 的核心概念,以及其架构和生命周期。
服务定义
与许多 RPC 系统一样,gRPC 的基础是定义一个服务,指定可以远程调用的方法及其参数和返回类型。默认情况下,gRPC 使用 protocol buffers 作为接口定义语言 (IDL),用于描述服务接口和负载消息的结构。当然,如果想要使用其他替代 IDL 也是可以的。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
gRPC 允许定义四种类型的服务方法:
- 一元 RPC:客户端向服务端发送单个请求,并返回单个响应,就像普通函数调用一样。
rpc SayHello(HelloRequest) returns (HelloResponse);
- 服务端流式 RPC:客户端向服务端发送请求,并获取一个流以读取一系列消息。客户端从返回的流中读取,直到没有更多的消息为止。gRPC 保证在单个 RPC 调用中的消息有序。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
- 客户端流式 RPC:客户端使用提供的流写入一系列消息并将它们发送到服务器。一旦客户端完成消息的写入,它就会等待服务器读取并返回其响应。同样,gRPC 保证在单个 RPC 调用中的消息有序。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
- 双向流式 RPC:双方使用读写流发送消息序列。这两个流的操作是独立的,因此客户端和服务端可以按照任意顺序读取和写入:例如,服务端可以等待接收所有客户端消息,然后再写入其响应,或者可以交替地读取、写入消息,或者其他可能的读写组合。各自流中的消息保持有序。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
使用 API
从 .proto
文件中的服务定义开始,gRPC 提供 protocol buffer 编译器插件来生成客户端和服务端代码。通常,gRPC 用户在客户端调用这些 API,并在服务器端实现相应的 API。
- 在服务器端,服务器实现服务声明的方法,并运行 gRPC 服务器以处理客户端调用。gRPC 基础工具解码传入的请求,执行服务方法,并对服务响应进行编码。
- 在客户端,有一个称为 stub(存根)的本地对象,该对象实现与服务相同的方法。然后,客户端只需在本地对象上调用这些方法,方法将调用参数包装到适当的 protocol buffer 消息类型中,将请求发送到服务器,并返回服务器的 protocol buffer 响应。
同步 vs. 异步
同步 RPC 调用会阻塞,直到从服务器收到响应。它与普通的本地方法调用最相似。但是,网络本质上是异步的。因此,在许多场景中,能够不阻塞当前线程的情况下启动 RPC 是非常有用的。
大多数语言中的 gRPC 编程 API 都有同步和异步两种风格。你可以在每种语言的教程和参考文档中了解到更多信息。
RPC 生命周期
本节中,你将更详细地了解当 gRPC 客户端调用 gRPC 服务端方法时会发生什么。
一元 RPC
首先考虑最简单类型的 RPC:客户端发送单个请求并返回单个响应。
- 客户端调用存根方法后,服务器会知道该 RPC 被调用,并带有客户端元数据、方法名称和指定的截止时间(如果适用的话)。
- 然后,服务器可以立即发送自己的初始元数据(必须在任何响应之前发送),或者等待客户端的请求消息。哪个先发生取决于应用程序。
- 一旦服务器收到客户端的请求消息,它会执行创建和填充响应所需的所有工作。然后,如果成功,将响应返回给客户端,并附带状态详细信息(状态代码和可选状态消息)和可选的尾部元数据。
- 如果响应状态是 OK,客户端将收到响应,即调用完成。
服务端流式 RPC
服务端流式 RPC 类似于一元 RPC,区别在于服务器在响应客户端请求时会返回一系列消息。发送完所有消息后,服务器的状态详细信息(状态代码和可选状态消息)和可选的尾部元数据将发送给客户端。 这表示服务端的处理已完成。客户端在获取到所有的服务器消息后表示处理完成。
客户端流式 RPC
客户端流式 RPC 类似于一元 RPC,区别在于客户端向服务器发送一系列消息,而不是单个消息。服务器响应单个消息(以及其状态详细信息和可选的尾部元数据)。服务器通常是在(但非必须)收到所有客户端的消息后响应。
双向流式 RPC
在双向流式 RPC 中,call(调用)由客户端 invoke(调用)方法发起;服务器接收客户端元数据、方法名称和截止时间。服务器可以选择发送其初始元数据,或者等待客户端开始流式传输消息。
客户端和服务器端的流处理方式是由应用程序自己决定的。由于这两个流是独立的,因此客户端和服务器可以以任何顺序读取和写入消息。例如,服务器可以等待接收所有客户端的消息,然后再写入其消息,或者服务器和客户端可以进行“乒乓球”式的交互 - 服务器收到请求,然后发送响应,然后客户端根据响应发送另一个请求,以此类推。
截止时间/超时
gRPC 允许客户端指定他们愿意等待 RPC 完成的时间,否则 RPC 将以 DEADLINE_EXCEEDED
错误终止。在服务器端,服务器可以查询特定 RPC 是否已超时,或者还剩多少时间来完成 RPC。
指定截止时间或超时是语言特定的:某些语言 API 使用超时(持续时间),而某些语言 API 使用截止时间(固定时间点),并且不一定有设置默认截止时间。
RPC 终止
在 gRPC 中,客户端和服务端都独立地、本地地判定调用是否成功。二者的结论有可能不匹配。例如,你可以在服务端成功完成 RPC(“我已发送了所有响应!”),但在客户端端失败(“响应在我的截止时间之后到达!”)。服务端也可以在客户端发送完所有请求之前决定结束调用。
取消 RPC
客户端或服务器都可以随时取消 RPC。取消将立即终止 RPC,不会再进行进一步的操作。
元数据
元数据是关于特定 RPC 调用的信息(例如身份验证详细信息),以键值对的形式表示,其中键是字符串,值通常也是字符串,但也可以是二进制数据。
键不区分大小写,由 ASCII 字母、数字和特殊字符 -
、_
、.
组成,且不能以 grpc-
开头(这是保留给 gRPC 本身的)。二进制值的键必须以 -bin
结尾; ASCII 值键则不需要以此结尾。
用户定义的元数据不会被 gRPC 使用,这允许客户端提供与调用相关的信息给服务器,反之亦然。
对元数据的访问方式取决于各个语言。
通道
gRPC 通道提供与指定主机和端口上的 gRPC 服务器的连接。它在创建客户端存根时使用。客户端可以指定通道参数以修改 gRPC 的默认行为,例如启用或禁用消息压缩。通道具有状态,包括 connected
(连接)和 idle
(空闲)。
gRPC 关闭通道的方式是语言特定的。某些语言还允许查询通道状态。