核心概念

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

Core concepts, architecture and lifecycle 核心概念、架构和生命周期

原文:https://grpc.io/docs/what-is-grpc/core-concepts/

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

​ 对 gRPC 不熟悉吗?那么请首先阅读gRPC简介。对于特定语言的详细信息,请参阅您选择的编程语言的快速入门、教程和参考文档。

概述

服务定义

​ 与许多 RPC 系统类似,gRPC的基本思想是定义一个服务,指定可以远程调用的方法以及它们的参数和返回类型。默认情况下,gRPC 使用协议缓冲区作为接口定义语言 (IDL),用于描述服务接口和有效负载消息的结构。如果需要,也可以使用其他替代方案。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

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

  • 一元 RPCs(Unary RPCs):客户端向服务端发送单个请求并获取单个响应,就像普通函数调用一样。

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

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

    1
    
    rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
    
  • 双向流式 RPCs:双方都使用读写流发送一系列的消息。两个流独立运行,因此客户端和服务端可以按任何顺序进行读写操作:例如,服务端可以在写入响应之前等待接收所有客户端消息,或者它可以交替读取消息然后写入消息,或者进行其他读写组合。每个流中的消息顺序保持不变。

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

​ 您将在下面的RPC 生命周期章节中了解更多关于不同类型的 RPC。

使用 API

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

  • 在服务端,服务端实现了服务声明的方法,并运行 gRPC 服务端来处理客户端调用。gRPC 基础设施(The gRPC infrastructure)会解码传入的请求,执行服务方法,并对服务响应进行编码。
  • 在客户端,客户端有一个称为 存根(stub)(对于某些语言,首选术语是 客户端(client))的本地对象,实现了与服务相同的方法。然后,客户端只需在本地对象上调用这些方法,方法会将调用的参数封装为适当的协议缓冲区消息类型,将请求发送到服务端,并返回服务端的协议缓冲区响应。

同步 vs. 异步

​ 同步的 RPC (Synchronous RPC)调用会阻塞,直到从服务端接收到响应,这是RPC所追求的过程调用抽象的最接近逼真的表现。另一方面,网络本质上是异步的,在许多场景中,能够在不阻塞当前线程的情况下启动 RPC 是非常有用的。

​ 大多数语言中的 gRPC 编程 API 都提供同步(synchronous )和异步(asynchronous )两种风格(flavors)。您可以在各编程语言的教程和参考文档中了解更多信息(完整的参考文档即将推出)。

RPC 生命周期

​ 在本节中,您将更详细地了解当 gRPC 客户端调用 gRPC 服务端方法时发生的情况。有关完整的实现细节,请参阅特定编程语言的页面。

一元 RPC

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

  1. 一旦客户端调用存根(stub )方法后,服务端就会收到有关此调用的客户端元数据(metadata)、方法名称以及指定的截止时间(deadline)(如果适用)的通知。
  2. 然后,服务端可以立即发送自己的初始元数据(initial metadata)(必须在任何响应之前发送),或者等待客户端的请求消息。哪个先发生取决于应用程序的特定实现。
  3. 一旦服务端收到客户端的请求消息,它会执行必要的工作来创建和填充响应。然后,将响应(如果成功)与状态详细信息(状态码和可选状态消息)以及可选的尾部元数据(optional trailing metadata)一起返回给客户端。
  4. 如果响应状态为 OK,则客户端接收到响应,这样在客户端上就完成了调用。

服务端流式 RPC

​ 服务端流式 RPC 类似于一元 RPC,不同之处在于服务端以流式方式返回一系列消息作为对客户端请求的响应。在发送完所有消息后,服务端将其状态详细信息(状态码和可选状态消息)以及可选的尾部元数据(optional trailing metadata)发送给客户端。这样就完成了在服务端的处理。客户端在接收到所有服务端的消息后完成。

客户端流式 RPC

​ 客户端流式 RPC 类似于一元 RPC,不同之处在于客户端向服务端发送一系列消息,而不是单个消息。服务端以单个消息作为响应(连同其状态详细信息和可选的尾部元数据(optional trailing metadata))进行响应,通常在接收到所有客户端的消息后响应,但不一定是这样。

双向流式 RPC

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

​ 客户端和服务端流处理是特定于应用程序的。由于这两个流是独立的,因此客户端和服务端可以按任何顺序读取和写入消息。例如,服务端可以等待接收到所有客户端的消息后再写入其消息,或者服务端和客户端可以进行"乒乓(ping-pong)“式的交互 —— 服务端接收请求,然后发送响应,然后客户端根据这一次的响应发送另一个请求,依此类推。

截止时间/超时

​ gRPC 允许客户端指定在 RPC 完成之前愿意等待的时间长度,如果RPC在此时间内未完成,将以DEADLINE_EXCEEDED错误终止。在服务端,服务端可以查询特定 RPC 是否已超时,或者还剩多少时间来完成 该RPC。

​ 指定截止时间或超时是特定于编程语言的:某些编程语言的 API 使用超时 timeout(时间段(durations of time)),而某些编程语言的 API 使用截止时间 deadline (固定时间点(a fixed point in time)),并且可能有默认的截止时间,也可能没有。

RPC 终止

​ 在 gRPC 中,客户端和服务器对调用的成功进行独立且本地的判断,它们的结论可能不一致。这意味着,例如,你可能有一个在服务端成功完成的 RPC(“我已发送完所有响应!"),但在客户端失败(“响应在我的截止时间之后到达!")。服务端也可以在客户端发送完所有请求之前决定完成调用。

取消 RPC

​ 客户端或服务端可以随时取消 RPC。取消操作会立即终止 RPC,不再进行进一步的工作。

警告

​ 在取消操作之前所做的更改不会被回滚。

元数据

​ 元数据(metadata)是关于特定 RPC 调用的信息(例如 认证细节),以键值对的形式表示,其中键是字符串,值通常是字符串,但也可以是二进制数据。

​ 键是不区分大小写的,并由 ASCII 字母、数字和特殊字符 -_. 组成,且不能以 grpc- 开头(这是为 gRPC 保留的)。二进制值的键以 -bin 结尾,而 ASCII 值的键则不以-bin结尾。

​ gRPC 不使用用户定义的元数据(这些用户自定义的元数据允许客户端提供与调用相关的信息给服务端),反之亦然。

​ 访问元数据是与编程语言相关的。

通道

​ gRPC 通道提供与指定主机和端口上的 gRPC 服务端的连接。在创建客户端存根(stub)时使用它。客户端可以指定通道参数以修改 gRPC 的默认行为,例如打开或关闭消息压缩(message compression)。通道具有状态,包括 connectedidle

​ gRPC 如何处理关闭通道取决于编程语言。某些编程语言还允许查询通道状态。

最后修改 June 1, 2023: 第一次提交翻译文本 (8d42264)