01
概念
在 gRPC 中,客户端应用程序可以直接在另一台计算机上的服务器应用程序上调用方法,就好像它是本地对象一样,从而使您更轻松地创建分布式应用程序和服务。与许多 RPC 系统一样,gRPC 围绕定义服务的思想,指定可通过其参数和返回类型远程调用的方法。
gRPC 可以将 protocol buffers 用作其接口定义语言(IDL)和其基础消息交换格式。
代码语言:javascript复制service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
02
gRPC 架构
在服务器端,服务器实现此接口并运行 gRPC 服务器以处理客户端调用。在客户端,客户端具有一个存根(在某些语言中仅称为客户端),提供与服务器相同的方法。
从 Google 内部的服务器到您自己的计算机,gRPC 客户端和服务器都可以在各种环境中运行并相互通信,并且可以使用 gRPC 支持的任何语言编写。因此,例如,您可以使用 Go,Python 或 Ruby 的客户端轻松地用 Java 创建gRPC 服务器。此外,最新的 Google API 的接口将具有 gRPC 版本,可让您轻松地在应用程序中内置 Google 功能。
关于 protocol buffers,可以阅读⎡Protobuf - 更小、更快、更简单的交互式数据语言⎦,此处不再赘述。
gRPC 可以定义四种服务方法:
1. 一元 RPC,客户端向服务器发送单个请求并获得单个响应,就像普通函数调用一样。
代码语言:javascript复制rpc SayHello(HelloRequest) returns (HelloResponse);
2. 服务器流式 RPC,客户端在其中向服务器发送请求,并获取流以读取回一系列消息。客户端从返回的流中读取,直到没有更多消息为止。gRPC 保证单个 RPC 调用中的消息顺序。
代码语言:javascript复制rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
3. 客户端流式RPC,客户端在其中编写一系列消息,然后再次使用提供的流将它们发送到服务器。客户端写完消息后,它将等待服务器读取消息并返回响应。gRPC再次保证了在单个RPC调用中的消息顺序。
代码语言:javascript复制rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
4. 双向流式RPC,双方都使用读写流发送一系列消息。这两个流是独立运行的,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以在写响应之前等待接收所有客户端消息,或者可以先读取消息再写入消息,或其他一些读写组合。每个流中的消息顺序都会保留。
代码语言:javascript复制rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
使用API
从 .proto 文件中的服务定义开始,gRPC 提供了protocol buffers 编译器插件,这些插件可生成客户端和服务器端代码。gRPC 用户通常在客户端调用这些 API,并在服务器端实现相应的 API。
- 在服务器端,服务器实现服务声明的方法,并运行 gRPC 服务器来处理客户端调用。gRPC 基础结构解码传入的请求,执行服务方法,并对服务响应进行编码。
- 在客户端,客户端具有一个称为 stub 的本地对象(对于某些语言,首选术语是 client),该对象实现与服务相同的方法。然后,客户端可以只在本地对象上调用这些方法,将调用的参数包装在适当的 protocol buffers消息类型中- gRPC 再将请求发送到服务器并返回服务器的 protocol buffers 响应之后进行查找。
同步与异步
阻塞的同步 RPC 调用直到从服务器收到响应为止是最接近 RPC 所追求的过程调用抽象的近似方法。另一方面,网络本质上是异步的,因此在许多情况下能够启动 RPC 而不阻塞当前线程很有用。
03
RPC 生命周期
一元 RPC - 最简单的 RPC 类型,其中客户端发送单个请求并返回单个响应。
- 客户端调用存根方法后,会通知服务器已使用该调用的客户端元数据,方法名称和指定的期限(如果适用)来调用 RPC。
- 然后,服务器可以立即发送自己的初始元数据(必须在发送任何响应之前发送),或者等待客户端的请求消息。首先发生的是特定于应用程序的。
- 服务器收到客户的请求消息后,它将完成创建和填充响应所必需的一切工作。然后将响应(如果成功)连同状态详细信息(状态代码和可选状态消息)以及可选尾随元数据一起返回(如果成功)。
- 如果响应状态为 OK,则客户端将获得响应,从而在客户端完成呼叫。
服务器流式 RPC
服务器流式 RPC 与一元 RPC 相似,不同之处在于服务器响应客户端的请求返回消息流。发送所有消息后,服务器的状态详细信息(状态代码和可选状态消息)和可选尾随元数据将发送到客户端。这样就完成了服务器端的处理。客户端收到所有服务器的消息后即完成。
客户端流式 RPC
客户端流式 RPC 与一元 RPC 相似,不同之处在于客户端将消息流发送到服务器而不是单个消息。服务器以一条消息(以及其状态详细信息和可选的尾随元数据)作为响应,通常(但不一定)是在它收到所有客户端的消息之后。
双向流式RPC
在双向流式 RPC 中,调用由客户端调用方法启动,服务器接收客户端元数据,方法名称和期限。服务器可以选择发回其初始元数据,也可以等待客户端开始流式传输消息。
客户端和服务器端流处理是特定于应用程序的。由于两个流是独立的,因此客户端和服务器可以按任何顺序读取和写入消息。例如,服务器可以等到收到客户端的所有消息后再写消息,或者服务器和客户端可以打“ping-pong” – 服务器收到请求,然后发回响应,然后客户端发送基于响应的另一个请求,依此类推。
截止时间 / 超时
gRPC 允许客户端指定在 RPC 因 DEADLINE_EXCEEDED 错误终止之前,他们愿意等待 RPC 完成多长时间。在服务器端,服务器可以查询以查看特定的RPC 是否超时,或者还剩下多少时间来完成 RPC。
指定期限或超时是特定于语言的:某些语言 API 按照超时(时间长度)工作,而某些语言 API 按照期限(固定时间点)工作,并且可能有也可能没有默认期限。
RPC 终止
在 gRPC 中,客户端和服务器均对呼叫成功进行独立和本地确定,其结论可能不匹配。这意味着,例如,您可能拥有一个在服务器端成功完成 RPC 的RPC(“我已经发送了所有响应!”),但是在客户端却失败了(“响应在我的截止日期之后到达!”)。服务器也有可能在客户端发送所有请求之前决定完成。
取消 RPC
客户端或服务器都可以随时取消 RPC。取消操作会立即终止 RPC,因此不再进行任何工作。并且取消之前所做的更改不会回滚。
元数据
元数据是以键值对列表的形式提供的有关特定 RPC 调用的信息(例如身份验证详细信息),其中键是字符串,值通常是字符串,但可以是二进制数据。元数据对于 gRPC 本身是不透明的-它允许客户端向服务器提供与调用相关的信息,反之亦然。
对元数据的访问取决于语言。
通道
gRPC 通道提供到指定主机和端口上的 gRPC 服务器的连接。创建客户端存根时使用。客户可以指定通道参数来修改 gRPC 的默认行为,例如打开或关闭消息压缩。通道具有状态,包括已连接和空闲。
gRPC 如何处理关闭通道取决于语言。某些语言还允许查询通道状态。
04
Golang 语言中的 gPRC
准备工作
- 安装最新版本的 Go 语言。
- 安装 protocol buffers 编译器 protoc。
安装用于 protocol buffers 编译器 protoc 的 Go 插件。
代码语言:javascript复制使用以下命令为 Go 安装 protocol buffers 编译器插件: export GO111MODULE=on # Enable module mode go get google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc
简单示例
RPC 应用的开发流程:
- 编写.proto文件。
- 使用 protoc 编译.proto文件,生成.go文件。
- 编写服务器端代码。
- 编写客户端代码。
参考资料:
https://grpc.io/docs/
推荐阅读:
Protobuf - 更小、更快、更简单的交互式数据语言