>[info] 原文地址:http://www.grpc.io/docs/guides/concepts.html
本文介绍gRPC一些核心概念,包含gRPC的架构概述,和RPC的生命周期。
**Overview**
服务定义
类似于许多RPC系统,gRPC的思想基础也是,定义一个服务,通过参数和返回类型指定可被远程调用的方法。默认情况下,gRPC使用protocol buffers作为IDL,来描述服务接口,以及消息中有效数据(payload)的结构。如果需要,也可以使用其它选择。
~~~
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest {
string greeting = 1;
}
message HelloResponse {
string reply = 1;
}
~~~
gRPC允许你定义4种服务方法:
* 一元RPC,客户端向服务器发送一个单独请求,并且获取一个单一响应,就像一个普通的方法调用。
~~~
rpc SayHello(HelloRequest) returns (HelloResponse){
}
~~~
* 服务端流式RPC,客户端向服务器发送一个请求,然后得到一个流,可以读取一个消息序列。客户端读取返回的流,直到它没有任何消息。
~~~
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
~~~
* 客户端流式RPC,客户端写一个消息序列,然后使用一个已经提供的流,将它们发送给服务器。当客户端完成了消息写入,它会等待服务端读取它们并返回响应。
~~~
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
~~~
* 双向流式RPC,2端都使用一个可读可写的流来发送一个消息序列。2个流的操作是独立的,所以客户端和服务器可以使用任何顺序来读写数据:比如,服务器可以等到接收完所有客户端消息后,再写入响应,或者它可以读一条消息然后就写一条消息,或者其它的读写组合。在每个流中的消息都是保序的。
~~~
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
~~~
我们将在下面的RPC生命周期部分,介绍这几种RPC的更多细节。
**使用API**
从在proto文件中定义一个服务开始,gRPC提供了protocol buffer编译插件生成客户端和服务端方法。gRPC的用户普遍都是在客户端调用这些APIs,在服务端实现对应的API。
* 在服务端,服务器实现了服务中定义的方法,并且运行一个gRPC服务器处理客户端调用。gRPC基础设施解码过来的请求,运行服务中的方法,然后将响应进行编码。
* 在客户端,客户端有一个本地对象,叫做stub(在一些语言中,更常用的术语就叫做客户端),它实现了和服务同样的方法。客户端就只需要在本地对象上调用这些方法,将调用的参数包装进合适的protocol buffer消息类型 -- 由gRPC负责将请求传送给服务器,并返回服务器的protocol buffer响应。
**同步 vs 异步**
同步RPC调用会阻塞住,直到服务端返回的响应到达,这是RPC期望调用过程的最近似抽象。而另一方面,网络天生就是异步的,并且在很多场景下,在当前线程中无阻塞的启动RPCs是很有用的。
在大多数语言的gRPC实现中,都包含了同步和异步2种版本。你可以在各个语言的教程和手册中找到更多详情(完整手册文档即将发布)
**RPC生命周期**
现在让我们细致看看,当一个gRPC客户端调用一个gRPC服务端方法时,会发生什么。我们不看实现细节,你可以在语言规范文档里面看到关于这些的更多细节。
**一元RPC(Unary RPC)**
首先看看最简单的RPC类型,客户端发送一个单独的请求,然后获取一个单独的响应。
* 当客户端调用本地对象stub上的方法时,服务端就会收到通知说RPC已经被调用,包含有这次调用的客户端元数据(metadata),方法名,还有指定的期限(deadline)如果可用。
* 服务器可以选择直接发回它自己的初始元数据(必须在任何响应之前发送),或者等待客户端的请求消息 -- 先发生那种情况,由应用程序指定。
* 一旦服务器有了客户端的请求消息,它就开始进行产生并填充响应的相关工作。响应随即被返回(如果成功)至客户端,包含了状态详情(状态码,和可选的状态消息),以及可选的拖尾元数据(trailing metadata)
* 如果状态是OK,客户端会得到响应,然后由客户端这边完成本次调用。
**服务端流式RPC(Server streaming RPC)**
一个服务端流式RPC和我们的简单样例很相似,除了服务端在收到客户端请求消息后,返回一个响应流。当返回完它的所有响应后,服务端的状态详情(状态码,和可选的状态消息)以及可选的拖尾元数据会被返回,以此完成服务端部分的工作。而客户端一旦得到服务器的所有响应就会完成本次调用。
**客户端流式RPC(Client streaming RPC)**
一个客户端流式RPC也和我们的例子很相似,除了客户端向服务器发送一个请求流,而不是一个单独请求。服务器返回一个单独响应,当服务器获取到客户端所有请求后,它一般都会在响应中,附带上状态详情和可选的拖尾元数据,但这不是必需的。
**双向流式RPC**
在双向流式RPC中,也同样是客户端发起通信,调用方法,并且服务端接收客户端元数据,方法名,和期限。服务端也同样是可以选择发回它的初始元数据,或者是等待客户端开始发送请求。
接下来发生什么取决于应用程序,由于客户端和服务器可以以任意顺序进行读写 -- 流操作是完全独立的。所以,举个例子,服务器可以等待直到它收到客户端的所有消息,然后开始写响应,或者服务器和客户端以打乒乓球的方式交流:服务器收到一个请求,然后发回一个响应,然后客户端基于响应再发起其它请求,以此类推。
**期限【Deadlines】**
gRPC允许客户端在调用一个远程方法时指定一个期限值。这个值指定了客户端会等待服务器响应最长多久,否则本次RPC会以**DEADLINE_EXCEEDED**结束。在服务端,服务器可以查询这个期限值,来判断一个特定的方法是否已经超时,或者距离完成还剩多长时间。
期限值如何指定根据语言不同而不同 -- 举例来说,在Python中期限值总是必需的,并且不是所有语言都有一个默认期限值。
#### RPC终止【RPC termination】
在gRPC中,对于本次调用是否成功,客户端和服务器都会做出独立,且本地的决定,它们的结论可能不相匹配。这就意味着,例如,你可能有一个RPC调用,在服务端成功结束(“我已经发送了所有的响应!”),但是在客户端失败了(“那些响应都在期限值后才到达”)。再比如,也可能会遇到,在客户端发完所有请求之前,服务端已决定结束这次RPC。
#### 取消RPCs【Cancelling RPCs】
客户端或者服务器可以随时取消一个RPC。取消操作会立即终结RPC以便后续不会再有任何操作。它并不是一个“撤销”操作:取消操作之前的更改并不会被回滚。当然了,一个同步的RPC方法调用无法被取消,因为直到RPC终止后,程序控制才会返回给应用程序
#### 元数据【Metadata】
元数据是关于一个特定RPC调用的信息(比如[验证详情](http://www.grpc.io/grpc.github.io/docs/guides/auth.html))。它的格式是一个键-值对的列表,键是字符串类型,值通常也是字符串类型(也可以是二进制数据)。元数据对gRPC是不透明的 -- 它让客户端提供关于本次调用的信息,并发送给服务器,反之亦然。
访问元数据也是依赖于具体语言的。
#### 通道【Channels】
一个gRPC通道在指定的地址和端口上,提供了和gRPC服务器的一个连接,并且当创建一个客户端存根的时候会被用到(在某些语言中直接称为客户端)。客户端可以指定通道参数,来修改gRPC的默认行为,例如开启和关闭消息压缩。通道是有状态的,包括**connected**和**idle**。
gRPC如何关闭通道依赖于具体语言。一些语言也允许查询通道状态。