核心概念、架构和生命周期

介绍 gRPC 的关键概念,概述 gRPC 的架构和 RPC 生命周期。

不熟悉 gRPC?首先阅读 【gRPC 简介】。对于特定语言的详细信息,请查看您所选语言的快速入门、教程和参考文档。

概述

服务定义

像许多 RPC 系统一样,gRPC 基于定义服务的理念,指定可以远程调用的方法及其参数和返回类型。默认情况下,gRPC 使用 协议缓冲(Protocol Buffers)作为接口定义语言(IDL),用于描述服务接口和负载消息的结构。如果需要,也可以使用其他替代方法。

service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

gRPC 允许定义四种类型的服务方法:

  • 单向 RPC(Unary RPC):客户端发送单个请求到服务器并收到单个响应,就像正常的函数调用。

    rpc SayHello(HelloRequest) returns (HelloResponse);
  • 服务器流式 RPC(Server streaming RPC):客户端发送请求到服务器并获得一个流,读取一系列的消息。客户端从返回的流中读取,直到没有更多消息为止。gRPC 保证在单个 RPC 调用中的消息顺序。

    rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
  • 客户端流式 RPC(Client streaming RPC):客户端发送一系列消息到服务器,使用提供的流。在客户端完成消息发送后,等待服务器读取并返回响应。同样,gRPC 保证在单个 RPC 调用中的消息顺序。

    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
  • 双向流式 RPC(Bidirectional streaming RPC):双方都使用读写流发送一系列消息。两个流是独立的,客户端和服务器可以按任意顺序进行读取和写入。例如,服务器可以等待接收到所有客户端的消息后再写响应,也可以交替地读一个消息然后写一个响应,或以其他组合的方式进行读写。每个流中的消息顺序被保留。

    rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);

您将在下面的 RPC 生命周期 部分学习更多不同类型的 RPC。

使用 API

.proto 文件中的服务定义开始,gRPC 提供了协议缓冲编译器插件,这些插件生成客户端和服务器端代码。gRPC 用户通常在客户端调用这些 API,在服务器端实现相应的 API。

  • 在服务器端,服务器实现服务声明的方法并运行 gRPC 服务器来处理客户端请求。gRPC 基础设施解码传入的请求,执行服务方法并编码服务响应。

  • 在客户端,客户端有一个本地对象,称为存根(stub,在某些语言中,首选术语是客户端),它实现与服务相同的方法。然后,客户端只需在本地对象上调用这些方法,这些方法将调用的参数包装在适当的协议缓冲消息类型中,将请求发送到服务器,并返回服务器的协议缓冲响应。

同步与异步

同步 RPC 调用会阻塞,直到服务器返回响应,这是 RPC 致力于模拟过程调用的最接近的抽象。另一方面,网络本质上是异步的,在许多场景中,能够在不阻塞当前线程的情况下启动 RPC 是很有用的。

大多数语言中的 gRPC 编程 API 都有同步和异步两种版本。您可以在每个语言的教程和参考文档中了解更多信息(完整的参考文档即将发布)。

RPC 生命周期

在本节中,您将深入了解当 gRPC 客户端调用 gRPC 服务器方法时发生的过程。有关完整的实现细节,请参阅特定语言的页面。

单向 RPC

首先考虑最简单的 RPC 类型,即客户端发送单个请求并获得单个响应。

  1. 客户端调用存根方法后,服务器被通知调用了 RPC,并收到客户端的 元数据(包括方法名和指定的 截止时间(如果适用))。

  2. 服务器可以选择立即发送自己的初始元数据(必须在任何响应之前发送),或者等待客户端的请求消息。先发生哪种情况,取决于应用程序。

  3. 一旦服务器收到客户端的请求消息,它会执行必要的工作以创建并填充响应。响应随后被返回(如果成功),并与状态详情(状态码和可选的状态消息)及可选的尾部元数据一起返回给客户端。

  4. 如果响应状态为 OK,则客户端收到响应,完成客户端的调用。

服务器流式 RPC

服务器流式 RPC 类似于单向 RPC,只不过服务器返回一个消息流来响应客户端的请求。在发送完所有消息后,服务器会发送状态详情(状态码和可选的状态消息)以及可选的尾部元数据给客户端。客户端在接收到所有服务器消息后完成。

客户端流式 RPC

客户端流式 RPC 类似于单向 RPC,只不过客户端发送一系列消息到服务器,而不是单个消息。服务器通常在接收到所有客户端的消息后,发送一个响应(包括状态详情和可选的尾部元数据),但不一定是这样。

双向流式 RPC

在双向流式 RPC 中,调用由客户端发起,服务器接收到客户端的元数据、方法名和截止时间。服务器可以选择立即返回初始元数据,或者等待客户端开始流式消息。

客户端和服务器的流处理是应用程序特定的。由于 两个流是独立的,客户端和服务器可以按照任意顺序读取和写入消息。例如,服务器可以等待接收到所有客户端的消息后再写入响应,或者客户端和服务器可以进行 “乒乓” 式的交互 —— 服务器接收到请求后,返回响应,客户端根据响应发送另一个请求,以此类推。

截止时间/超时

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 的默认行为,例如打开或关闭消息压缩。通道具有状态,包括连接和空闲状态。

如何处理关闭通道依赖于具体语言。有些语言还允许查询通道状态。