Contents

GRPC简介

1. 简介

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

2. 概述

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

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

  1. 在*.proto*文件中定义服务
  2. 使用协议缓冲区编译器生成服务器和客户端代码
  3. 创建服务器应用程序,实现生成的服务接口并生成 gRPC 服务器
  4. 创建客户端应用程序,使用生成的存根进行 RPC 调用

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

3. Maven依赖

让我们添加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. 定义服务

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

这是使用协议缓冲区 在*.proto*文件中完成的。它们还用于描述有效负载消息的结构。

4.1.基本配置

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

syntax = "proto3";
option java_multiple_files = true;
package com.blogdemo.grpc;

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

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

4.2. 定义消息结构

接下来,我们定义消息:

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

这定义了请求有效负载。在这里,进入消息的每个属性都与其类型一起定义。

需要为每个属性分配一个唯一编号,称为标记。协议缓冲区使用此标记来表示属性,而不是使用属性名称。

因此,与 JSON 中我们每次都传递属性名称firstName不同,protocol buffer 将使用数字 1 来表示firstName。响应负载定义类似于请求。

请注意,我们可以在多种消息类型中使用相同的标签:

message HelloResponse {
    string greeting = 1;
}

4.3. 定义服务合同

最后,让我们定义服务契约。对于我们的HelloService,我们定义了一个*hello()*操作:

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

hello()操作接受一元请求并返回一元响应。gRPC 还通过在请求和响应中添加stream关键字来支持流式传输。

5. 生成代码

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

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

首先,我们需要 Protocol Buffer Compiler。我们可以从这里 提供的许多预编译二进制文件中进行选择。

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

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

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. 使用 Maven 插件

作为开发人员,您希望代码生成与您的构建系统紧密集成。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>

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

6. 创建服务器

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

  • HelloRequest.java – 包含HelloRequest类型定义
  • HelloResponse.java - 这包含HelleResponse类型定义
  • HelloServiceImplBase.java – 这包含抽象类HelloServiceImplBase,它提供了我们在服务接口中定义的所有操作的实现

6.1.覆盖服务基类

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

我们将扩展这个类并覆盖我们服务定义中提到的*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();
    }
}

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

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

gRPC 使用构建器来创建对象。我们使用HelloResponse.newBuilder()并设置问候文本来构建一个HelloResponse对象。我们将此对象设置为 responseObserver 的*onNext()*方法以将其发送给客户端。

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

6.2. 运行 Grpc 服务器

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

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

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

7. 创建客户端

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

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

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

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("Blogdemo")
          .setLastName("gRPC")
          .build());
        channel.shutdown();
    }
}

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

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

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

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