gRPC
gRPC是现代开源的基于 HTTP2.0 高性能 RPC 框架,它能运行在任何环境。它能高效地连接数据中心以及内部的服务,支持可插件化负载均衡,跟踪,健康检查以及授权。它也适用于分布式计算的连接设备,移动应用程序和浏览器后端服务。
主要包含如下特性
- 简单的服务定义,按照定义好的 Protobuf 格式协议(protobuf 是一个强大的二进制序列化工具集和语言)
- 快速启动,伸缩性好
- 跨语言和平台,根据定义的服务自动生成各个语言的客户端,服务端代码
- 双向流与授权
gRPC可以使用协议缓冲区作为它的接口定义语言(IDL)和它的底层消息交换格式。
在 gRPC 中,客户端应用程序可以直接调用不同机器上的服务方法,就像本地对象调用方法一样,这样能够让你更加容易的创建分布式应用程序于服务。在众多 RPC 系统里,gRPC 是围绕定义好的服务运行的,它指定一个远程调用的方法,这个方法能传参数也能返回对象类型。在服务端,运行一个 gRPC 服务来实现合格接口并处理客户端的请求。在客户端会有一个相同方法的存根作为服务提供。
gRPC 客户端和服务端能在各种不同的环境彼此交互通信。比如你可以使用 Java 语言作为一个 gRPC 服务器,而客户端你可以用 Go,Python 或者是 Ruby。此外还有谷歌等对 gRPC 有些功能提供支持,你可以直接使用。
协议缓冲区(Protocol Buffers)
gRPC 默认使用 Protocol Buffers,是谷歌开源的序列化结构数据(也可以使用其它数据结构,比如 JSON)。下面讲一下是如何工作的
第一步就是要定义你想要序列化的数据结构,放置在以 .proto
结尾的格式文件(称之为 proto 文件)。Protocol buffer 数据被结构化为消息,每条消息都是一个小的逻辑条目,它包含了一系列 name-value 对被调用的字段信息。如
message Person {
string name = 1;
int32 id = 2;
bool has_ponycopter = 3;
}
一旦你定义好了这种数据结构,那么 protocol buffer 编译器就会根据你定义的内容编译这个文件自动生成在你所引用的语言(如 C#)的数据访问类。以上面的例子来说就会生成一些字段访问器,像 GetName()
、SetName()
,以及从原始字节序列化/解析整个结构的方法。
你可以直接 .proto
文件中定义 gRPC 服务方法,可以指定请求参数与返回类型
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
gRPC 使用特殊的插件根据的文件内容来生成代码:会生成客户端以及服务端代码,以及用于填充、序列化和检索消息类型的常规协议缓冲区代码。
使用方式
服务定义
gRPC 可以定义四种服务方式:
-
一元 RPC,客户端向服务发送单个请求,并获得单个响应,就像调用普通方法一样。
rpc SayHello(HelloRequest) returns (HelloResponse);
-
流服务 RPC,客户端向服务发送请求并获取服务的响应流。如果不是服务没有消息数据,客户端会一直读取服务响应返回的流。gRPC 保证单独的 RPC 调用消息都是有序的。
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
-
客户端流 RPC,客户端会向服务发送流请求。一旦客户端完成流输入消息,它就会一直等待服务端响应并返回。再次重生,gRPC 能保证了单个消息的排序
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
-
双向绑定流 RPC,双端使用读写流来发送响应请求。两个端流都是独立的,所以客户端和服务端都能正确读写流:例如,服务端在响应客户端请求之前不会向客户端回写返回体。每个流的消息顺序都有保留的。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
生命周期
一元 RPC
一元 RPC 是最简单的 RPC 类型,客户端单个请求,服务端响应单个请求返回给客户端。
- 一旦客户端调用方法,那服务端就接收到通知,RPC已经被调用了, 使用客户端的元数据、方法名称和截至日期。
- 服务器可以直接发送回它自己的初始元数据(必须在任何响应之前发送),或者等待客户机的请求消息
- 服务一旦接收到了客户端请求消息,那么它无论如何都会创建并填充一个消息返回。这个响应体包括状态详细(状态码以及可选状态消息)和可选的元数据返回(如果成功)给客户端。
- 如果返回提的状态码是 OK,那么客户端就会成功接收到服务端回送的消息体,这在客户端就是一个完整的 RPC 调用。
服务流RPC
服务流 RPC 过程很像一元 RPC,除了服务端这边是以流消息来响应客户端的请求的。在发送完所有的消息之后,服务器的状态详细(状态码以及状态消息)和元数据都会回送给客户端。整个过程是以在服务端处理完成的。客户端一接收完服务端的全部消息就会结束。
客户端流RPC
客户流 RPC 过程很像一元 RPC。除了客户端这边发送的是流请求消息而不是单个消息。而服务端这边还是一样返回的单消息体(同样包括状态详细和可选元数据),通常情况下,但不一定是在它收到所有客户端的消息之后。
双向流
在双向流RPC中,调用由调用方法的客户端和接收客户端元数据、方法名称和截止日期的服务器发起。服务端能发送一个初始元数据回去或者等待客户端开始一个流消息。
客户端-服务端流处理在应用程序中比较特殊。因为这两个流都是独立的,客户端和服务端能在任何顺序中读写消息。举个例子,服务端能在回写消息之前一直等待客户端接收完所有的消息,或者服务端和客户端之间像打乒乓球一样——服务端获取一个请求,然后回写一个响应回去,然后客户端又能基于这个响应发送其他消息,然后这样往返。
DeadLine 与超时
gRPC 允许客户端设置等待的时间,也就是 RPC 开始到完成的时间等待的时间。超出会报 DEADLINE_EXCEEDED
错误。在服务端能够查询 RPC 的超时时间,或是要完成 RPC 还剩余多少时间。
指定一个 deadline 和超时时间是由语言决定的:一些语言的 API 可以与超时很好的工作,一些语言的 API 也能按照 deadline 工作并且也有可能会有默认的 deadline。
RPC 结束
在 gRPC 中,客户端和服务端调用的成功与否都是独立的,跟本地执行有关,两端之间的成功与否并不是一致的。可能服务端回写了所有消息给客户端,但是在客户端可能由于 deadline 或 timeout 导致调用失败。这就导致了服务端成功,但是客户端是失败的。反过来也是如此。
取消 RPC
客户端和服务端能在任何时间取消 RPC。取消操作会立即终止 RPC,因此不会再做任何工作。所以跟业务紧密相关的工作要确保是事务的,因为双端中出现任何问题都要保证业务能回滚,而这点本身 gRPC 是无法保证的。
元数据
元数据是关于 RPC 调用的信息(比如授权详细),是由键值对组成的集合,这些键和值都是字符串,但是也可以是二进制数。元数据对 gRPC 本身是不透明的 - 它匀速客户端提供与服务器相关联的信息,反过来也是如此。
访问元数据视具体开发语言决定。
通道(Channel)
gRPC Channel 在服务端上指定一个 host 和端口号来提供一个连接。它是用来创建一个客户端存根。客户端能指定 channel 参数来修改 gRPC 默认的行为,比如开启或关闭消息压缩。channel 有状态,包括 connected
和 idle
。
参考资料
- https://www.grpc.io/
- https://www.grpc.io/docs/what-is-grpc/introduction/
- https://grpc.io/docs/what-is-grpc/core-concepts/