上一小节,简单熟悉了一下 RPC,接下来继续学习 gRPC 的基础概念:

  • 什么是 gRPC ?
  • gRPC 的关键概念有哪些 ?
  • 在 gRPC 中,如何去定义一个服务 ?
  • gRPC 服务类型有哪几种?
  • 一个 gRPC 客户端调用一个 gRPC 服务端方法时,会发生什么?

Statement: 文中内容多参考自网络博客[地址在文中(末)], 感谢这些作者们!🙂

1. 什么是 gRPC ?

Google 对 gRPC 的定义:

A high performance, open-source universal RPC framework (高性能、开源、通用的 RPC 框架)

官方:gRPC Guides

gRPC 是Google开源的一款语言和平台均中立的高性能的RPC框架, 它使用 HTTP/2 协议进行网络通信,并使用 Protocl Buffers 作为它的 IDL(Interface Description Language), 同时也可以将它作为底层消息交换格式。

gRPC是面向服务端和移动端设计的RPC框架,它基于 HTTP/2 协议,带来双向流、请求压缩、单连接多路复用等功能,使得其在移动设备上表现更好,能够进一步节省移动端的耗电量和网络流量。

在 gRPC 里,客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:

  • 定义一个服务,指定其能够被远程调用的方法以及它的参数和返回值
  • 在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用
  • 在客户端持有一个与服务端一样的方法存根,使客户端与服务端的调用就像本地方法的调用一样

下图是gRPC官方主页的架构图,可以明确的看出它的特点是使用跨语言的ProtoBuf协议进行通信传输,且完全透明化远程调用的细节:

使用 gRPC 可以在一个 .proto 文件中定义服务的契约,并使用任何支持它的语言去生成客户端和服务端的 RPC 代码。gRPC 解决了不同语言及环境间通信的复杂性和性能问题。

1.1. 使用 protocol buffers

gRPC 默认使用 Protocl Buffers ,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。

使用 protocol buffers 的第一步是在一个 .proto 文件中定义你想序列化的数据结构:它是一个普通的文本文件。protocol buffer 的数据被结构化为消息,每一个 message 都是一个小的逻辑记录,包含了一系列被称为 fields 的 name-value 对信息。下面是一个简单的样例:

1
2
3
4
5
message Person {
    string name = 1;
    int32 id =2;
    bool has_ponycopter = 3;
}

更详细的内容可以查看:

2. gRPC 的关键概念

关键概念包括:gRPC 的架构描述和 RPC 的生命周期

2.1. gRPC 架构相关概述

2.1.1. 服务定义

类似于其他 RPC 系统,gRPC 也是基于以下思想,即:定义一个服务,通过参数和返回类型指定可被远程调用的方法。默认情况下,gRPC 使用 protocol buffers 作为IDL,来描述服务接口,以及消息中有效数据(payload)的结构。如果需要,也可以使用其它选择。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
service HelloService {
    rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}

gRPC 支持 4 中服务类型,分别是:

  • Unary RPCs
  • Server streaming RPCs
  • Client streaming RPCs
  • Bidirectional streaming RPCs

Note: gRPC 通过参数和返回类型是否有stream关键字来标识区分服务的类型

下面就对着 4 中服务类型做简要说明

2.1.1.1. Unary RPCs

Unary RPCs: 最简单的 RPC 调用,即以此请求对应一次应答。客户端发送一次请求给服务端并从服务端获取一次应答,和一次普通的函数调用一样。

1
rpc SayHello(HelloRequest) returns (HelloResponse) {}

2.1.1.2. Server streaming RPCs

Server streaming RPCs: 服务端流式RPC,即一次请求可以对应多个响应结果。客户端发送一次请求给服务端,可获取一个数据流用来读取一系列消息,客户端从返回的数据流里一直读到没有更多的详细为止。在定义接口时,只需要将返回值增加 stream 关键字即可。

1
rpc SayHello(HelloRequest) returns (stream HelloResponse) {}

2.1.1.3. Client streaming RPCs

Client streaming RPCs: 客户端流式RPC,可以多次请求对应一个应答结果。客户端提供数据流写入,并批量发送消息给服务端,当客户端完成消息写入后,再等待服务端读取消息并应答。在定义接口时,只需要将方法参数 增加 stream 关键字即可。

1
rpc SayHello(stream HelloRequest) returns (HelloResponse) {}

2.1.1.4. Bidirectional streaming RPCs

Bidrectional streaming RPCs: 双向流式RPC,是服务端流式RPC和客户端流式RPC的结合,可以多次请求对应多个应答结果。服务端和客户端都可以分别通过读写数据流批量发送消息,两个数据流相互独立,客户端和服务端都能按照它的期望的顺序完成读写。举例说明:服务端可以在发送应答消息之前等待接受所有的客户端消息;也可以先接受一条消息之后,再应答一条消息,每个数据流里的消息的顺序会被保持。在定义接口时,需要将 方法参数返回值 都增加 stream 关键字。

1
rpc SayHello(stream HelloRequest) returns (stream HelloResponse) {}

通过以上四种服务类型可以看出,基于 HTTP/2 的 gRPC, 在交互模型采用 请求/响应 模式的同时,充分利用了 HTTP/2 协议的流式处理特性。

在接下来的 RPC 生命周期部分,会介绍这几种 RPC 更多的细节。

2.1.2. 使用 API

.proto 文件中的服务定义开始,gRPC 提供了用来生客户端和服务端代码的 protocol buffer 编译插件。gRPC 用户通常在客户端调用这些 APIs,并且在服务端实现对应的 API。

  • 在服务端,服务器实现了服务中定义的方法,并且运行一个gRPC服务器处理客户端调用。gRPC 底层架构会解码传入的请求,执行服务方法,编码服务应答。
  • 在客户端,客户端有一个本地对象,叫做存根(stub)(在一些语言中,更常用的术语就叫做客户端),它实现了和服务同样的方法。客户端就只需要在本地对象上调用这些方法,将调用的参数包装进合适的protocol buffer消息类型 – 由gRPC负责将请求传送给服务器,并返回服务器的protocol buffer响应。

2.1.3. 同步和异步

同步RPC调用会阻塞住,直到服务端返回的响应到达,这是与RPC期望的调用过程最近似抽象。而另一方面,网络本质上就是异步的,并且在很多场景下,启动 RPC 而不阻塞当前的线程是非常有用的。

在大多数语言的gRPC实现中,都包含了同步和异步2种版本。你可以在各个语言的教程和手册中找到更多详情(完整手册文档即将发布)

2.2. RPC 生命周期

官方原文(英文) 原文译文

现在让我们细致看看,当一个gRPC客户端调用一个gRPC服务端方法时,会发生什么。我们不看实现细节,你可以在语言规范文档里面看到关于这些的更多细节。

2.2.1. Unary RPC

First let’s look at the simplest type of RPC, where the client sends a single request and gets back a single response.

  • Once the client calls the method on the stub/client object, the server is notified that the RPC has been invoked with the client’s metadata(元数据) for this call, the method name, and the specified deadline if applicable.
  • The server can then either send back its own initial metadata (which must be sent before any response) straight away(直接), or wait for the client’s request message - which happens first is application-specific.
  • Once the server has the client’s request message, it does whatever work is necessary to create and populate(填充) its response. The response is then returned (if successful) to the client together with status details (status code and optional status message) and optional trailing metadata.
  • If the status is OK, the client then gets the response, which completes the call on the client side.

2.2.2. Server streaming RPC

A server-streaming RPC is similar to our simple example, except(除了) the server sends back a stream of responses after getting the client’s request message. After sending back all its responses, the server’s status details (status code and optional status message) and optional trailing metadata are sent back to complete on the server side. The client completes once it has all the server’s responses.

2.2.3. Client streaming RPC

客户端流式 RPC 也基本与我们的简单例子一样,区别在于客户端通过发送一个请求流给服务端,取代了原先发送的单个请求。服务端通常(但并不必须)会在接收到客户端所有的请求后发送回一个应答,其中附带有它的状态详情和可选的跟踪数据。

2.2.4. Bidirectional streaming RPC

In a bidirectional streaming RPC, again the call is initiated(开始|启动) by the client calling the method and the server receiving the client metadata, method name, and deadline. Again the server can choose to send back its initial(初始) metadata or wait for the client to start sending requests.

What happens next depends on the application, as the client and server can read and write in any order - the streams operate completely independently(独立). So, for example, the server could wait until it has received all the client’s messages before writing its responses, or the server and client could “ping-pong”: the server gets a request, then sends back a response, then the client sends another request based on the response, and so on.

2.2.5. Deadlines/Timeouts

gRPC allows clients to specify how long they are willing to wait for an RPC to complete before the RPC is terminated with the error DEADLINE_EXCEEDED. On the server side, the server can query to see if a particular RPC has timed out, or how much time is left to complete the RPC.

How the deadline or timeout is specified varies(变化的) from language to language - for example, not all languages have a default deadline, some language APIs work in terms of a deadline (a fixed point in time), and some language APIs work in terms of timeouts (durations of time).

2.2.6. RPC termination

In gRPC, both the client and server make independent and local determinations of the success of the call, and their conclusions(结论) may not match. This means that, for example, you could have an RPC that finishes successfully on the server side (“I have sent all my responses!”) but fails on the client side (“The responses arrived after my deadline!”). It’s also possible for a server to decide to complete before a client has sent all its requests.

2.2.7. Cancelling RPCs

Either the client or the server can cancel an RPC at any time. A cancellation terminates the RPC immediately so that no further work is done. It is not an “undo”(撤销): changes made before the cancellation will not be rolled back(回滚).

2.2.8. Metadata

Metadata is information about a particular(特定的) RPC call (such as authentication details) in the form of a list of key-value pairs, where the keys are strings and the values are typically strings (but can be binary data). Metadata is opaque(不透明) to gRPC itself - it lets the client provide information associated with the call to the server and vice versa(反之亦然).

Access to metadata is language-dependent

2.2.9. Channels

A gRPC channel provides a connection to a gRPC server on a specified host and port and is used when creating a client stub (or just “client” in some languages). Clients can specify channel arguments to modify gRPC’s default behaviour, such as switching on and off message compression. A channel has state, including connected and idle.

How gRPC deals with closing down channels is language-dependent. Some languages also permit querying channel state.

3. See Also

Thanks to the authors 🙂