Introduction to gRPC – gRPC简介

最后修改: 2017年 8月 8日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

gRPC is a high performance, open source RPC framework initially developed by Google. It helps in eliminating boilerplate code and helps in connecting polyglot services in and across data centers.

gRPC是一个高性能、开源的RPC框架,最初由Google开发。它有助于消除模板代码,并有助于在数据中心内和跨数据中心连接多语种服务。

2. Overview

2.概述

The framework is based on a client-server model of remote procedure calls. A client application can directly call methods on a server application as if it was a local object.

该框架是基于远程过程调用的客户-服务器模型。客户端应用程序可以直接调用服务器应用程序的方法,就像它是一个本地对象一样。

This article will use following steps to create a typical client-server application using gRPC:

本文将使用以下步骤来创建一个使用gRPC的典型客户-服务器应用程序。

  1. Define a service in a .proto file
  2. Generate server and client code using the protocol buffer compiler
  3. Create the server application, implementing the generated service interfaces and spawning the gRPC server
  4. Create the client application, making RPC calls using generated stubs

Let’s define a simple HelloService which returns greetings in exchange for the first and the last name.

让我们定义一个简单的HelloService,返回问候语以换取名字和姓氏。

3. Maven Dependencies

3.Maven的依赖性

Let’s add grpc-netty, grpc-protobuf and grpc-stub dependencies:

让我们添加grpc-nettygrpc-protobufgrpc-stub依赖项。

<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.16.1</version>
</dependency>
<dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.16.1</version>
</dependency>

4. Defining the Service

4.定义服务

We start by defining a service, specifying methods that can be called remotely along with their parameters and return types.

我们首先定义一个服务,指定可以远程调用的方法及其参数和返回类型

This is done in the .proto file using the protocol buffers. They are also used for describing the structure of the payload messages.

这是在.proto文件中使用protocol buffers完成的。它们也被用来描述有效载荷消息的结构。

4.1. Basic Configurations

4.1.基本配置

Let us create a HelloService.proto file for our sample HelloService. We start by adding few basic configuration details:

让我们为我们的样本HelloService创建一个HelloService.proto文件。我们首先添加一些基本的配置细节。

syntax = "proto3";
option java_multiple_files = true;
package org.baeldung.grpc;

The first line tells the compiler what syntax is used in this file. By default, the compiler generates all the Java code in a single Java file. The second line overrides this setting, and everything will be generated in individual files.

第一行告诉编译器在这个文件中使用什么语法。默认情况下,编译器会在单个Java文件中生成所有的Java代码。第二行覆盖这一设置,所有的东西都将在单个文件中生成。

Finally, we specify the package we want to use for our generated Java classes.

最后,我们指定我们要为我们生成的Java类使用的包。

4.2. Defining the Message Structure

4.2.定义信息结构

Next, we define the message:

接下来,我们定义信息。

message HelloRequest {
    string firstName = 1;
    string lastName = 2;
}

This defines the request payload. Here each attribute that goes into the message is defined along with its type.

这定义了请求的有效载荷。这里定义了进入消息的每个属性以及其类型。

A unique number needs to be assigned to each attribute, called as the tag. This tag is used by the protocol buffer to represent the attribute instead of using the attribute name.

需要为每个属性分配一个独特的数字,称为标签。这个标签被协议缓冲器用来表示属性,而不是使用属性名称。

So, unlike JSON where we would pass attribute name firstName every single time, protocol buffer would use the number 1 to represent firstName. Response payload definition is similar to the request.

因此,与JSON不同,我们每次都会传递属性名称firstName,协议缓冲区会使用数字1来表示firstName。响应有效载荷的定义与请求类似。

Note that we can use the same tag across multiple message types:

请注意,我们可以在多种消息类型中使用同一个标签。

message HelloResponse {
    string greeting = 1;
}

4.3. Defining the Service Contract

4.3.定义服务合同

Finally, let’s define the service contract. For our HelloService we define a hello() operation:

最后,我们来定义服务合同。对于我们的HelloService,我们定义一个hello()操作。

service HelloService {
    rpc hello(HelloRequest) returns (HelloResponse);
}

The hello() operation accepts a unary request and returns a unary response. gRPC also supports streaming by prefixing stream keyword to the request and response.

hello()操作接受一个单数请求,并返回一个单数响应。gRPC还支持流媒体,在请求和响应前加上stream关键字。

5. Generating the Code

5.生成代码

Now we pass the HelloService.proto file to the protocol buffer compiler protoc to generate the Java files. There are multiple ways to trigger this.

现在我们把HelloService.proto文件传递给协议缓冲区编译器protoc以生成Java文件。有多种方法可以触发这一点。

5.1. Using Protocol Buffer Compiler

5.1.使用协议缓冲区编译器

First, we need the Protocol Buffer Compiler. We can choose from many precompiled binaries available here.

首先,我们需要协议缓冲区编译器。我们可以从许多预编译的二进制文件中选择,这些文件可以在这里

Additionally, we need to obtain the gRPC Java Codegen Plugin.

此外,我们需要获得gRPC Java Codegen Plugin

Finally, we can use the following command to generate the code:

最后,我们可以使用以下命令来生成代码。

protoc --plugin=protoc-gen-grpc-java=$PATH_TO_PLUGIN -I=$SRC_DIR 
  --java_out=$DST_DIR --grpc-java_out=$DST_DIR $SRC_DIR/HelloService.proto

5.2. Using Maven Plugin

5.2.使用Maven插件

As a developer, you would want the code generation to be tightly integrated with your build system. gRPC provides a protobuf-maven-plugin for the Maven build system:

gRPC为Maven构建系统提供了一个protobuf-maven-plugin的插件,作为一名开发者,您希望代码生成与构建系统紧密结合。

<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.6.1</version>
    </extension>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.6.1</version>
      <configuration>
        <protocArtifact>
          com.google.protobuf:protoc:3.3.0:exe:${os.detected.classifier}
        </protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>
          io.grpc:protoc-gen-grpc-java:1.4.0:exe:${os.detected.classifier}
        </pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

The os-maven-plugin extension/plugin generates various useful platform-dependent project properties like ${os.detected.classifier}

os-maven-plugin扩展/插件可生成各种有用的、与平台相关的项目属性,如${os.detected.classifier}

6. Creating the Server

6.创建服务器

Irrespective of which method you use for code generation, the following key files will be generated:

无论你使用哪种方法来生成代码,都会生成以下密钥文件。

  • HelloRequest.java – contains the HelloRequest type definition
  • HelloResponse.javathis contains the HelleResponse type definition
  • HelloServiceImplBase.javathis contains the abstract class HelloServiceImplBase which provides an implementation of all the operations we defined in the service interface

6.1. Overriding the Service Base Class

6.1.重写服务基类

The default implementation of the abstract class HelloServiceImplBase is to throw runtime exception io.grpc.StatusRuntimeException saying that the method is unimplemented.

抽象类HelloServiceImplBase默认实现是抛出运行时异常io.grpc.StatusRuntimeException,称该方法未实现。

We shall extend this class and override the hello() method mentioned in our service definition:

我们将扩展这个类,并重写我们的服务定义中提到的hello()方法。

public class HelloServiceImpl extends HelloServiceImplBase {

    @Override
    public void hello(
      HelloRequest request, StreamObserver<HelloResponse> responseObserver) {

        String greeting = new StringBuilder()
          .append("Hello, ")
          .append(request.getFirstName())
          .append(" ")
          .append(request.getLastName())
          .toString();

        HelloResponse response = HelloResponse.newBuilder()
          .setGreeting(greeting)
          .build();

        responseObserver.onNext(response);
        responseObserver.onCompleted();
    }
}

If we compare the signature of hello() with the one we wrote in the HellService.proto file, we’ll notice that it does not return HelloResponse. Instead, it takes the second argument as StreamObserver<HelloResponse>, which is a response observer, a call back for the server to call with its response.

如果我们将hello()的签名与我们在HellService.proto文件中写的签名进行比较,我们会发现它并没有返回HelloResponse。相反,它的第二个参数是StreamObserver<HelloResponse>,它是一个响应观察器,是让服务器用它的响应来回调。

This way the client gets an option to make a blocking call or a non-blocking call.

这样,客户端就可以选择进行阻塞式调用或非阻塞式调用

gRPC uses builders for creating objects. We use HelloResponse.newBuilder() and set the greeting text to build a HelloResponse object. We set this object to the responseObserver’s onNext() method to send it to the client.

gRPC使用构建器来创建对象。我们使用HelloResponse.newBuilder()并设置问候语文本来建立一个HelloResponse对象。我们将这个对象设置到responseObserver的onNext()方法中,将其发送到客户端。

Finally, we need to call onCompleted() to specify that we’ve finished dealing with the RPC, else the connection will be hung, and the client will just wait for more information to come in.

最后,我们需要调用onCompleted()来指定我们已经完成了对RPC的处理,否则连接将被挂起,而客户端将只是在等待更多的信息进来。

6.2. Running the Grpc Server

6.2.运行Grpc服务器

Next, we need to start the gRPC server to listen for incoming requests:

接下来,我们需要启动gRPC服务器来监听传入的请求。

public class GrpcServer {
    public static void main(String[] args) {
        Server server = ServerBuilder
          .forPort(8080)
          .addService(new HelloServiceImpl()).build();

        server.start();
        server.awaitTermination();
    }
}

Here, again we use the builder to create a gRPC server on port 8080 and add the HelloServiceImpl service that we defined. start() would start the server. In our example, we will call awaitTermination() to keep the server running in the foreground blocking the prompt.

在这里,我们再次使用构建器在8080端口创建一个gRPC服务器,并添加我们定义的HelloServiceImpl服务。start()将启动服务器。在我们的例子中,我们将调用awaitTermination()来保持服务器在前台运行,阻止提示。

7. Creating the Client

7.创建客户

gRPC provides a channel construct that abstracts out the underlying details like connection, connection pooling, load balancing, etc.

gRPC提供了一个通道结构,抽象出底层的细节,如连接、连接池、负载平衡等。

We’ll create a channel using ManagedChannelBuilder. Here, we specify the server address and port.

我们将使用ManagedChannelBuilder创建一个通道。在这里,我们指定服务器地址和端口。

We’ll be using plain text without any encryption:

我们将使用没有任何加密的纯文本。

public class GrpcClient {
    public static void main(String[] args) {
        ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
          .usePlaintext()
          .build();

        HelloServiceGrpc.HelloServiceBlockingStub stub 
          = HelloServiceGrpc.newBlockingStub(channel);

        HelloResponse helloResponse = stub.hello(HelloRequest.newBuilder()
          .setFirstName("Baeldung")
          .setLastName("gRPC")
          .build());

        channel.shutdown();
    }
}

Next, we need to create a stub which we’ll use to make the actual remote call to hello(). The stub is the primary way for clients to interacts with the server. When using auto-generated stubs, the stub class will have constructors for wrapping the channel.

接下来,我们需要创建一个存根,用来对hello()进行实际的远程调用。存根是客户端与服务器交互的主要方式。当使用自动生成的存根时,存根类将有构造函数来包装通道。

Here we’re using a blocking/synchronous stub so that the RPC call waits for the server to respond, and will either return a response or raise an exception. There are two other types of stubs provided by gRPC, which facilitate non-blocking/asynchronous calls.

这里我们使用一个阻塞/同步存根,这样RPC调用就会等待服务器的响应,并且会返回一个响应或者引发一个异常。gRPC还提供了另外两种类型的存根,它们有利于非阻塞/异步调用。

Finally, time to make the hello() RPC call. Here we pass the HelloRequest. We can use the auto-generated setters to set the firstName, lastName attributes of the HelloRequest object.

最后,是时候进行hello() RPC调用了。这里我们传递HelloRequest。我们可以使用自动生成的设置器来设置firstNamelastName对象的属性。

We get back the HelloResponse object returned from the server.

我们得到从服务器返回的HelloResponse对象。

8. Conclusion

8.结论

In this tutorial, we saw how we could use gRPC to ease the development of communication between two services by focusing on defining the service and letting the gRPC to handle all the boilerplate code.

在本教程中,我们看到了如何使用gRPC来简化两个服务之间的通信开发,重点是定义服务并让gRPC来处理所有的模板代码。

As usual, you’ll find the sources over on GitHub.

像往常一样,你可以在GitHub上找到源代码