gRPC定义了如下四种RPC,刚开始接触的时候,感觉挺奇怪的,RPC不就是接口调用吗,区分这么多干啥?难道实现原理不一样?未读源码之前,还想着有啥神秘的地方,看完源码之后,才发现这四种RPC底层实现本质上是一样的,仅仅是部分接口调用细节不同。
Unary RPC (一元RPC or 简单RPC)
Client streaming RPC (客户端流式RPC)
Server streaming RPC (服务端流式RPC)
Bidirectional streaming RPC (双向流式RPC)
Unary RPC:
Unary 中文翻译为一元,这里可以理解为简单的意思。Unary RPC指的是客户端发送一个请求,服务端发送一个响应,和普通函数调用类似,这是最简单的RPC。通过protobuf定义unary rpc 格式如下, rpc 方法名(请求参数) returns(响应参数):
rpc SayHello(HelloRequest) returns (HelloResponse);
Client streaming RPC
客户端流式RPC指的是客户端可以发送一连串请求,发送完成之后等待服务器处理并返回一个响应。gRPC保证单个stream里的客户端的请求顺序一致,即按照其发送的顺序。(之所以能够保证顺序,应该是因为发送的时候是按照写入的顺序逐个发送的),我理解所谓的流式(streaming)就是多个。通过protobuf定义 client streaming rpc 格式如下, 可以看到请求中多了一个stream 占位符。
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
Server streaming RPC
服务端流式RPC,这个和客户端流式RPC刚好相反。客户端发送一个请求,服务端发送多个响应。同样,gRPC保证同一个stream上多个消息顺序和发送时一致。通过protobuf定义 server streaming rpc 格式如下:
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
Bidirectional streaming RPC
双向流式RPC, 顾名思义,两端都可以发送多条消息。两端收发顺序独立互不影响。典型的一个场景比如聊天程序,双向通信,不过这种场景下是不是使用websocket更好一点?
通过PB定义BidirectionalStreamingRPC格式如下,请求和响应参数里都有stream占位符。
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
gRPC-Go 代码库目录下examples/route_guide 定义了四种RPC使用例子,下面给出这四种RPC的调用流程图:
通过流程图可以看到,这四种RPC本质上都一样,都是建立传输层然后通过传输层读取数据。对于客户端来说,unary rpc 和 streaming rpc 除了发送请求次数不一样外,发送end_stream标记的时机也不一样。unary因为只发送一个请求,所以该请求会携带end_stream标志。streaming则存在多个请求,因此需要额外发送一个空data frame来携带end_stream。
对于server 侧streaming,客户端则是调用多次recvmsg。
另外,http2 协议规范定义end_stream标志为:如果该标志位为1,则表示当前frame为该端点最后一个frame。
综上,这四种RPC底层实现没太大区别,仅仅是调用方式上的异同。在我们的业务中使用的都是UnaryRPC,还没看到有其它类型RPC的使用。
上面的图基于gRPC-GO 1.54.0-dev版本制作,具体的代码就无需粘贴上来了,有兴趣的同学可以下载代码看看。
参考资料:
1. Core concepts, architecture and lifecycle | gRPC
2. RFC 7540 - Hypertext Transfer Protocol Version 2 (HTTP/2)