gRPC 初识
官方文档
概念文档 https://grpc.io/docs/guides/
java helloword demo https://grpc.io/docs/quickstart/
引导文档 https://grpc.io/docs/guides/
以上是建议学习顺序
以下是概念文档的翻译,以后会出具体实现代码
简介
gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java 和 Go 语言版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.
gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。
gRPC 概念
本文档通过对于 gRPC 的架构和 RPC 生命周期的概览来介绍 gRPC 的主要概念。本文是在假设你已经读过文档部分的前提下展开的。针对具体语言细节请查看对应语言的快速开始、教程和参考文档(很快就会有完整的文档)。
概览
服务定义
正如其他 RPC 系统,gRPC 基于如下思想:定义一个服务, 指定其可以被远程调用的方法及其参数和返回类型。gRPC 默认使用 protocol buffers 作为接口定义语言,来描述服务接口和有效载荷消息结构。如果有需要的话,可以使用其他替代方案。
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
required string greeting = 1;
}
message HelloResponse {
required string reply = 1;
}
gRPC 允许你定义四类服务方法:
- 单项 RPC,即客户端发送一个请求给服务端,从服务端获取一个应答,就像一次普通的函数调用。
rpc SayHello(HelloRequest) returns (HelloResponse){
}
- 服务端流式 RPC,即客户端发送一个请求给服务端,可获取一个数据流用来读取一系列消息。客户端从返回的数据流里一直读取直到没有更多消息为止。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
- 客户端流式 RPC,即客户端用提供的一个数据流写入并发送一系列消息给服务端。一旦客户端完成消息写入,就等待服务端读取这些消息并返回应答。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
- 双向流式 RPC,即两边都可以分别通过一个读写数据流来发送一系列消息。这两个数据流操作是相互独立的,所以客户端和服务端能按其希望的任意顺序读写,例如:服务端可以在写应答前等待所有的客户端消息,或者它可以先读一个消息再写一个消息,或者是读写相结合的其他方式。每个数据流里消息的顺序会被保持。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
我们将在下面 RPC 生命周期章节里看到各类 RPC 的技术细节。
使用 API 接口
gRPC 提供 protocol buffer 编译插件,能够从一个服务定义的 .proto 文件生成客户端和服务端代码。通常 gRPC 用户可以在服务端实现这些API,并从客户端调用它们。
- 在服务侧,服务端实现服务接口,运行一个 gRPC 服务器来处理客户端调用。gRPC 底层架构会解码传入的请求,执行服务方法,编码服务应答。
- 在客户侧,客户端有一个存根实现了服务端同样的方法。客户端可以在本地存根调用这些方法,用合适的 protocol buffer 消息类型封装这些参数— gRPC 来负责发送请求给服务端并返回服务端 protocol buffer 响应。
同步 vs 异步
同步 RPC 调用一直会阻塞直到从服务端获得一个应答,这与 RPC 希望的抽象最为接近。另一方面网络内部是异步的,并且在许多场景下能够在不阻塞当前线程的情况下启动 RPC 是非常有用的。
在多数语言里,gRPC 编程接口同时支持同步和异步的特点。你可以从每个语言教程和参考文档里找到更多内容(很快就会有完整文档)。
RPC 生命周期
现在让我们来仔细了解一下当 gRPC 客户端调用 gRPC 服务端的方法时到底发生了什么。我们不究其实现细节,关于实现细节的部分,你可以在我们的特定语言页面里找到更为详尽的内容。
单项 RPC
首先我们来了解一下最简单的 RPC 形式:客户端发出单个请求,获得单个响应。
- 一旦客户端通过桩调用一个方法,服务端会得到相关通知 ,通知包括客户端的元数据,方法名,允许的响应期限(如果可以的话)
- 服务端既可以在任何响应之前直接发送回初始的元数据,也可以等待客户端的请求信息,到底哪个先发生,取决于具体的应用。
- 一旦服务端获得客户端的请求信息,就会做所需的任何工作来创建或组装对应的响应。如果成功的话,这个响应会和包含状态码以及可选的状态信息等状态明细及可选的追踪信息返回给客户端 。
- 假如状态是 OK 的话,客户端会得到应答,这将结束客户端的调用。
服务端流式 RPC
服务端流式 RPC 除了在得到客户端请求信息后发送回一个应答流之外,与我们的简单例子一样。在发送完所有应答后,服务端的状态详情(状态码和可选的状态信息)和可选的跟踪元数据被发送回客户端,以此来完成服务端的工作。客户端在接收到所有服务端的应答后也完成了工作。
客户端流式 RPC
客户端流式 RPC 也基本与我们的简单例子一样,区别在于客户端通过发送一个请求流给服务端,取代了原先发送的单个请求。服务端通常(但并不必须)会在接收到客户端所有的请求后发送回一个应答,其中附带有它的状态详情和可选的跟踪数据。
双向流式 RPC
双向流式 RPC ,调用由客户端调用方法来初始化,而服务端则接收到客户端的元数据,方法名和截止时间。服务端可以选择发送回它的初始元数据或等待客户端发送请求。 下一步怎样发展取决于应用,因为客户端和服务端能在任意顺序上读写 - 这些流的操作是完全独立的。例如服务端可以一直等直到它接收到所有客户端的消息才写应答,或者服务端和客户端可以像"乒乓球"一样:服务端后得到一个请求就回送一个应答,接着客户端根据应答来发送另一个请求,以此类推。
截止时间
gRPC 允许客户端在调用一个远程方法前指定一个最后期限值。这个值指定了在客户端可以等待服务端多长时间来应答,超过这个时间值 RPC 将结束并返回DEADLINE_EXCEEDED
错误。在服务端可以查询这个期限值来看是否一个特定的方法已经过期,或者还剩多长时间来完成这个方法。 各语言来指定一个截止时间的方式是不同的 - 比如在 Python 里一个截止时间值总是必须的,但并不是所有语言都有一个默认的截止时间。
RPC 终止
在 gRPC 里,客户端和服务端对调用成功的判断是独立的、本地的,他们的结论可能不一致。这意味着,比如你有一个 RPC 在服务端成功结束("我已经返回了所有应答!"),到那时在客户端可能是失败的("应答在最后期限后才来到!")。也可能在客户端把所有请求发送完前,服务端却判断调用已经完成了。
取消 RPC
无论客户端还是服务端均可以再任何时间取消一个 RPC 。一个取消会立即终止 RPC 这样可以避免更多操作被执行。它不是一个"撤销", 在取消前已经完成的不会被回滚。当然,通过同步调用的 RPC 不能被取消,因为直到 RPC 结束前,程序控制权还没有交还给应用。
元数据集
元数据是一个特殊 RPC 调用对应的信息(授权详情]) ,这些信息以键值对的形式存在,一般键的类型是字符串,值的类型一般也是字符串(当然也可以是二进制数据)。元数据对 gRPC 本事来说是不透明的 - 它让客户端提供调用相关的信息给服务端,反之亦然。 对于元数据的访问是语言相关的。
通讯协议
HTTP2 协议上的 gRPC
本文档作为 gRPC 在 HTTP2 草案17框架上的实现的详细描述,假设你已经熟悉 HTTP2 的规范。产品规则采用的是ABNF 语法
大纲
以下是 gRPC 请求和应答消息流中一般的消息顺序:
- 请求 → 请求报头 *有定界符的消息 EOS
- 应答 → 应答报头 *有定界符的消息 EOS
- 应答 → (应答报头 *有定界符的消息 跟踪信息) / 仅仅跟踪时
请求
-
请求 → 请求报头 *界定的消息 EOS 请求报头是通过报头+联系帧方式以 HTTP2 报头来发送的。
-
请求报头 → 调用定义 *自定义元数据
-
调用定义 → 方法模式路径TE [授权] [超时] [内容类型] [消息类型] [消息编码] [接受消息类型] [用户代理]
-
方法 → “:method POST”
-
模式 → “:scheme ” (“http” / “https”)
-
路径 → “:path” {开放的 API 对应的方法路径}
-
Authority → “:authority” {授权的对应的虚拟主机域名}
-
TE → “te” “trailers” # 用来检测不兼容的代理
-
超时 → “grpc-timeout” 超时时间值 超时时间单位
-
超时时间值 → {至少8位数字正整数的 ASCII 码字符串}
-
超时时间单位 → 时 / 分 / 秒 / 毫秒 / 微秒 / 纳秒
-
时 → “H”
-
分 → “M”
-
秒 → “S”
-
毫秒 → “m”
-
微秒 → “u”
-
纳秒 → “n”
-
内容类型 → “content-type” “application/grpc” [(“+proto” / “+json” / {自定义})]
-
内容编码 → “gzip” / “deflate” / “snappy” / {自定义}
-
消息编码 → “grpc-encoding” Content-Coding
-
接受消息编码 → “grpc-accept-encoding” Content-Coding *("," Content-Coding)
-
用户代理 → “user-agent” {结构化的用户代理字符串}
-
消息类型 → “grpc-message-type” {消息模式的类型名}
-
自定义数据 → 二进制报头 / ASCII 码报头
-
二进制报头 → {以“-bin”结尾小写的报头名称的 ASCII 码 } {以 base64 进行编码的值}
-
ASCII 码报头 → {小写报头名称的 ASCII 码} {值}
HTTP2 需要一个在其他报头之前以“:”开始的保留报头。额外的实现应该在保留报头后面马上传送超时信息,并且应该在发送自定义元数据前发送调用定义报头。 如果超时信息被遗漏,服务端会认为是无限时长的超时。客户端实现可以根据发布需要*地发送一个默认最小超时时间。 自定义元数据是应用层定义的任意的键值对集合。除了 HTTP2 报头部总长度的传输限制外,唯一的约束就是以“grpc-”开始的报头名称是为将来使用保留的。
注意 HTTP2 并不允许随意使用字节序列来作为报头值,所以二进制的报头值必须使用 Base64 来编码,参见https://tools.ietf.org/html/rfc4648#section-4。 实现必须接受填充的和非填充的值,并且发出非填充的值。应用以“-bin”结尾的名称来定义二进制报头。运行时库在报头被发送和接收时,用这个后缀来检测二进制报头并且正确地在报头被发送和接收时进行 Base64 编码和解码。
界定的消息的重复序列通过数据帧来进行传输。
-
界定的消息 → 压缩标志 消息长度 消息
-
压缩标志 → 0 / 1 # 编码为 1 byte 的无符号整数
-
消息长度 → {消息长度} # 编码为 4 byte 的无符号整数
-
消息 → *{二进制字节}
压缩标志 值为1 表示消息的二进制序列通过消息编码报头声明的机制进行压缩,为0表示消息的字节码没有进行编码。压缩上下文不在消息编辑间维护,声明必须为流中的每个消息创建一个新的上下文。假如 压缩标志 被遗漏了,那么压缩标志 必须为0。
对请求来讲,EOS (end-of-stream)以最后接收到的数据帧出现 END_STREAM 标志为准。 在请求流需要关闭但是没有数据继续发送的情况下,代码必须发送包含这个标志的空数据帧。
应答
-
应答 → (应答报头 界定的消息 跟踪信息) / 仅仅跟踪
-
应答报头 → HTTP 状态 [消息编码] [消息接受编码] 内容类型 *自定义元数据
-
仅仅跟踪 → HTTP 状态 内容类型 跟踪消息
-
跟踪消息 → 状态 [状态消息] *自定义元数据
-
HTTP状态 → “:status 200”
-
状态 → “grpc-status” <状态码的 ASCII 字符串>
-
状态消息 → “grpc-message” <状态描述文本对应的 ASCII 字符串>
应答报头 和 仅仅跟踪 分别在一个HTTP2报头帧块里发送。大多数应答期望既有报头又有跟踪消息,但是调用允许仅仅跟踪生成一个立即的错误。假如状态码是 OK 的话,则必须在跟踪消息里发送状态。 对于应答来讲,通过在最后一个接收的包含跟踪信息的报头帧里提供一个 END_STREAM 标志来表明流结束。
实现应当会让中断的部署在应答里发送一个非200的HTTP状态码和一系列非GRPC内容类型并且省略状态和状态消息。 当发生这种情况时实现应当合成状态和状态消息来扩散到应用层。
安全
HTTP2 规范当使用 TLS 时强制使用 TLS 1.2 及以上的版本,并且在部署上对允许的密码施加一些额外的限制以避免已知的比如需要 SNI 支持的问题。并且期待 HTTP2 与专有的传输安全机制相结合,这些传输机制的规格说明不能提供有意义的建议。
连接管理
GOAWAY 帧
服务端发出这种帧给客户端表示服务端在相关的连接上不再接受任何新流。这种帧包含服务端最后成功接受的流的ID。客户端应该认为任何在最后成功的流后面初始化的任意流为 UNAVAILABLE,并且在别处重试这些调用。客户端可以*地在已经接受的流上继续工作直到它们完成或者连接中断。 服务端应该在终止连接前发送 GOAWAY 帧,以可靠地通知客户端哪些工作已经被服务端接受并执行。
PING 帧
客户端和服务端均可以发送一个 PING 帧,对方必须精确回显它们所接收到的信息。这可以被用来确认连接仍然是活动的,并且能够提供估计端对端延迟估计的方法。假如服务端初始的 PING 在最后期限仍然没有收到运行时所期待的应答的话,所有未完成的调用将会被以取消状态关闭。一个客户端期满的初始的PING则会导致所有的调用被以用不可用状态关闭。注意PING的频率高度依赖于网络环境,实现可以根据网络和应用需要,*地调整PING频率。
连接失败
假如客户端检测到连接失败,所有的调用都会被以不可用状态关闭。而服务端侧则所有已经打开的调用都会被以取消状态关闭。
Protobuf 上的 GRPC
用 protobuf 定义的服务接口可以通过 protoc 的代码生成扩展简单地映射成 GRPC ,以下定义了所用的映射:
-
路径 → / 服务名 / {方法名}
-
服务名 → ?( {proto 包名} "." ) {服务名}
-
消息类型 → {全路径 proto 消息名}
-
内容类型 → "application/grpc+proto"
上一篇: 对称加密之基于口令的加密解密
下一篇: 04_RSA非对称加密