RPC:gRPC

gRPC

gRPC是一个高性能、开源和通用的RPC框架,面向移动和HTTP/2设计。

gRPC基于HTTP/2标准设计,带来诸如双向流、流控、头部压缩、单TCP连接上的多复用请求等特。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。

应用

适用性

有了gRPC,我们可以一次性的在一个.proto文件中定义服务并使用任何支持它的语言去实现客户端和服务器,反过来,它们可以在各种环境中,从Google的服务器到你自己的平板电脑。gRPC帮你解决了不同语言及环境间通信的复杂性。使用protocol buffers还能获得其他好处,包括高效的序列号,简单的IDL以及容易进行接口更新。

概览

protocol buffer

gRPC 默认使用protocol buffers,这是Google开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如JSON)。正如你将在下方例子里所看到的,你用proto files创建gRPC服务,用protocol buffers消息类型来定义方法参数和返回类型。

Protocol buffers 版本

尽管protocol buffers对于开源用户来说已经存在了一段时间,例子内使用的却一种名叫proto3的新风格的protocol buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。虽然你可以使用 proto2 (当前默认的protocol buffers版本),我们通常建议你在gRPC里使用proto3,因为这样你可以使用gRPC支持全部范围的的语言,并且能避免proto2客户端与proto3服务端交互时出现的兼容性问题,反之亦然。

Hello World

Hello World将带领你创建一个简单的客户端——服务端应用,向你展示:

  • 通过一个protocol buffers模式,定义一个简单的带有Hello World方法的RPC服务。
  • 用你最喜欢的语言(如果可用的话)来创建一个实现了这个接口的服务端。
  • 用你最喜欢的(或者其他你愿意的)语言来访问你的服务端。

定义服务

创建我们例子的第一步是定义一个服务:一个RPC服务通过参数和返回类型来指定可以远程调用的方法。就像你在 概览里所看到的,gRPC通过protocol buffers来实现。

我们使用protocol buffers接口定义语言来定义服务方法,用protocol buffer来定义参数和返回类型。客户端和服务端均使用服务定义生成的接口代码。

这里有我们服务定义的例子,在helloworld.proto里用protocol buffers IDL定义的。Greeter服务有一个方法SayHello,可以让服务端从远程客户端接收一个包含用户名的HelloRequest消息后,在一个HelloReply里发送回一个Greeter。这是你可以在gRPC里指定的最简单的RPC例子。

helloworld.proto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
syntax = "proto3";

option java_package = "io.grpc.examples";

package helloworld;

// The greeter service definition.
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 代码

一旦定义好服务,我们可以使用protocol buffer编译器protoc来生成创建应用所需的特定客户端和服务端的代码。你可以生成任意gRPC支持的语言的代码,当然PHP和Objective-C仅支持创建客户端代码。生成的代码同时包括客户端的存根和服务端要实现的抽象接口,均包含Greeter所定义的方法。以Java为例:

  • 这个例子的构建系统也是Java gRPC本身构建的一部分。为了简单起见,我们推荐使用我们事先生成的例子代码。你可以参考README来看一下如何从你自己的.proto文件生成代码。

  • 以下类包含所有我们需要创建这个例子所有的代码:

    • HelloRequest.java,HelloResponse.java和其他文件包含所有protocol buffer用来填充、序列化和提取HelloRequestHelloReply消息类型的代码。
    • GreeterGrpc.java,包含(还有其他有用的代码):

    Greeter服务端需要实现的接口:

1
2
3
4
public static interface Greeter {
public void sayHello(Helloworld.HelloRequest request,
StreamObserver<Helloworld.HelloReply> responseObserver);
}

客户端用来与Greeter服务端进行对话的存根类。就像你所看到的,异步存根也实现了Greeter接口。

1
2
3
4
public static class GreeterStub extends AbstractStub<GreeterStub>
implements Greeter {
...
}

写一个服务器

首先我们将创建一个服务应用来实现服务(你会记起来,我们可以是使用除了Objective-C and PHP外的其他所有语言来实现)。在本节,我们不打算对如何创建一个服务端进行更深入地探讨,更详细的信息可以在你选择语言对应的教程里找到。Java服务的具体实现:

GreeterImpl.java准确地实现了Greeter服务所需要的行为。正如你所见,GreeterImpl类通过实现sayHello方法,实现了从IDL生成的GreeterGrpc.Greeter接口 。

1
2
3
4
5
6
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}

sayHello有两个参数:

  • HelloRequest,请求。

  • StreamObserver<HelloReply>:应答观察者,一个特殊的接口,服务器用应答来调用它。

为了返回给客户端应答并且完成调用:

  1. 用我们的激动人心的消息构建并填充一个在我们接口定义的HelloReply应答对象。
  2. HelloReply返回给客户端,然后表明我们已经完成了对RPC的处理。

服务端实现

需要提供一个gRPC服务的另一个主要功能是让这个服务实在在网络上可用:

HelloWorldServer.java提供了以下代码作为 Java 的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* The port on which the server should run */
private int port = 50051;
private Server server;
private void start() throws Exception {
server = ServerBuilder.forPort(port)
.addService(GreeterGrpc.bindService(new GreeterImpl()))
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
// Use stderr here since the logger may has been reset by its JVM shutdown hook.
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.this.stop();
System.err.println("*** server shut down");
}
});
}

在这里我们创建了合理的gRPC服务器,将我们实现的Greeter服务绑定到一个端口。然后我们启动服务器:服务器现在已准备好从Greeter服务客户端接收请求。我们将在具体语言对应的文档里更深入地了解这所有的工作是怎样进行的。

写一个客户端

客户端的gRPC非常简单。在这一步,我们将用生成的代码写一个简单的客户程序来访问我们在上一节里创建的Greeter服务器。

连接服务

首先我们看一下我们如何连接Greeter服务器。我们需要创建一个gRPC频道,指定我们要连接的主机名和服务器端口。然后我们用这个频道创建存根实例。

1
2
3
4
5
6
7
8
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public HelloWorldClient(String host, int port) {
channel = ManagedChannelBuilder.forAddress(host, port)
.usePlaintext(true)
.build();
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

在这个例子里,我们创建了一个阻塞的存根。这意味着RPC调用要等待服务器应答,将会返回一个应答或抛出一个异常。gRPC Java还可以有其他种类的存根,可以向服务器发出非阻塞的调用,这种情况下应答是异步返回的。

调用 RPC

现在我们可以联系服务并获得一个greeting:

  1. 我们创建并填充一个HelloRequest发送给服务。
  2. 我们用请求调用存根的SayHello(),如果RPC成功,会得到一个填充的HelloReply,从其中我们可以获得greeting。
1
2
HelloRequest req = HelloRequest.newBuilder().setName(name).build();
HelloReply reply = blockingStub.sayHello(req);

通信协议

Java教程

参考

  1. 《gRPC 官方文档中文版》