服务工厂与辅助工具
你当前正在浏览历史版本 Vert.x 4.4.9 的文档。 访问本页面的 最新版本。

Vert.x gRPC 模块

Preview

关于 gRPC 最权威的定义可以参考 Wikipedia:

gRPC 是由 Google 最初发起的开源高性能远程过程调用 (RPC) 框架。它基于 HTTP/2 协议进行高效传输,采用 Protocol Buffers (Protobuf) 作为服务接口描述语言,提供全套身份认证、双向流式通信、流量控制、背压绑定以及请求取消与超时处理机制。它能为众多编程语言自动生成跨平台的客户端与服务端强类型绑定。

— wikipedia
wikipedia

Vert.x gRPC 模块将 gRPC 的异步通信模式深度融入 Vert.x 原生编程哲学。作为该模块的使用者,你可以在充分享受 gRPC 跨语言生态优势的同时,使用 Vert.x 流(Streams)与 Future 契约开展全异步响应式编程。

如需了解 gRPC 的更多全局背景,请访问官方主页 http://www.grpc.io/。

自 Vert.x 4.3 起,本模块成为 Vert.x 技术栈推荐的全新 gRPC 支持实现;基于老旧 Netty 原生绑定的早期实现已被重命名为 `vertx-grpc-netty\`。

Vert.x gRPC 模块主要由以下部分构成:

  • Vert.x gRPC 服务端 (Server)

  • Vert.x gRPC 客户端 (Client)

  • Vert.x gRPC/IO 服务端

  • Vert.x gRPC/IO 客户端

  • Vert.x gRPC/IO 上下文存储 (Context Storage)

Vert.x gRPC Protoc 插件 2

开始使用 Vert.x gRPC 最简单的方式是使用其内置的代码生成器插件。为此,你需要按照 gRPC 的要求使用 protobuffer 格式来定义通信协议。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "examples";
option java_outer_classname = "HelloWorldProto";
package helloworld;

// 问候服务定义。
service Greeter {
  // 发送问候
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 包含用户姓名的请求消息。
message HelloRequest {
  string name = 1;
}

// 包含问候语的响应消息。
message HelloReply {
  string message = 1;
}

这是一个非常简单的示例,展示了单个请求、单个响应的模式。

编译 RPC 定义

使用上面的定义后,我们需要对其进行编译。

如果你愿意,可以使用 protoc 编译器编译 proto 文件(参见 相关说明),也可以将其集成到构建流程中。

如果你使用的是 Apache Maven,可以从两个插件中选择:io.github.ascopes:protobuf-maven-plugin(推荐)或 org.xolstice.maven.plugin:protobuf-maven-plugin:

<plugin>
    <groupId>io.github.ascopes</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>${ascopes.grpc.version}</version>
    <configuration>
        <protocVersion>${protobuf.version}</protocVersion>
    </configuration>
    <executions>
        <execution>
            <id>compile-java</id>
            <configuration>
                <jvmMavenPlugins>
                    <jvmMavenPlugin>
                        <groupId>io.vertx</groupId>
                        <artifactId>vertx-grpc-protoc-plugin2</artifactId>
                        <version>${vertx.version}</version>
                        <mainClass>io.vertx.grpc.plugin.VertxGrpcGenerator</mainClass>
                        <options>grpc-client=true,grpc-service=true,grpc-io=true</options>
                        <!-- 或者你也可以使用 jvmArgs -->
                        <jvmArgs>
                            <jvmArg>--grpc-client=true</jvmArg>
                            <jvmArg>--grpc-service=true</jvmArg>
                            <jvmArg>--grpc-io=true</jvmArg>
                        </jvmArgs>
                    </jvmMavenPlugin>
                </jvmMavenPlugins>
            </configuration>
        </execution>
    </executions>
</plugin>

该插件使用 protocVersion 代替平台特定的构件,并会自动根据你的操作系统平台和版本下载 protoc。

有关更多详细信息,请参阅 ascopes protobuf-maven-plugin 文档。

<plugin>
    <groupId>org.xolstice.maven.plugins</groupId>
    <artifactId>protobuf-maven-plugin</artifactId>
    <version>0.6.1</version>
    <configuration>
        <protocArtifact>com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
        <protocPlugins>
            <protocPlugin>
                <id>vertx-grpc-protoc-plugin2</id>
                <groupId>io.vertx</groupId>
                <artifactId>vertx-grpc-protoc-plugin2</artifactId>
                <version>${stack.version}</version>
                <mainClass>io.vertx.grpc.plugin.VertxGrpcGenerator</mainClass>
                <args>
                    <arg>--grpc-client=true</arg>
                    <arg>--grpc-service=true</arg>
                    <arg>--grpc-io=true</arg>
                </args>
            </protocPlugin>
        </protocPlugins>
    </configuration>
    <executions>
        <execution>
            <id>compile</id>
            <configuration>
                <outputDirectory>${project.basedir}/src/main/java</outputDirectory>
                <clearOutputDirectory>false</clearOutputDirectory>
            </configuration>
            <goals>
                <goal>compile</goal>
                <goal>compile-custom</goal>
            </goals>
        </execution>
    </executions>
</plugin>
根据 https://github.com/xolstice/protobuf-maven-plugin/issues/121#issuecomment-2832430955,org.xolstice.maven.plugin:protobuf-maven-plugin 已不再受支持。建议用户改用 io.github.ascopes:protobuf-maven-plugin。

有关更多详细信息,请参阅 xolstice protobuf-maven-plugin 文档。

${os.detected.classifier} 属性用于实现跨操作系统构建,在 macOS 上它会被替换为 osx-x86_64 等。要使用该属性,你需要在 pom.xml 的 build 配置节中添加 os-maven-plugin[https://github.com/trustin/os-maven-plugin]:

<build>
  ...
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.4.1.Final</version>
    </extension>
  </extensions>
  ...
</build>

该插件将编译 src/main/proto 目录下的 proto 文件,并使其在你的项目中可用。

默认情况下,该插件会同时生成客户端和服务端文件。如果你只需要其中一端,可以配置生成器仅生成特定的类:

<jvmMavenPlugin>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpc-protoc-plugin2</artifactId>
  <version>${vertx.version}</version>
  <mainClass>io.vertx.grpc.plugin.VertxGrpcGenerator</mainClass>
  <options>grpc-client=true,grpc-service=true,grpc-io=true</options>
</jvmMavenPlugin>

参数说明

  • [--]grpc-client[=true/false]:生成客户端文件

  • [--]grpc-service[=true/false]:生成服务(服务端)文件

  • [--]grpc-io[=true/false]:生成与 io.grpc:grpc-stub 兼容的存根(Stub)文件,默认不生成。使用此选项时,你还必须确保已配置 grpc-java 插件(如上文 Maven 配置所示)

  • [--]grpc-transcoding[=true/false]:是否为带有 HTTP 注解的方法生成转码(Transcoding)选项

  • [--]vertx-codegen[=true/false]:是否在生成的类中添加 Vert.x 代码生成注解(@VertxGen)。默认处于禁用状态

  • [--]service-prefix[=前缀名称]:生成带有指定前缀的服务类。例如设置为 MyService 时,生成的服务类将是 MyServiceGreeterService 而不是 GreeterService。

    • [--] 表示当作为 JVM 参数使用时可以带有 -- 前缀,但在 options 标签中指定时应省略 --。如果可行,建议用户使用插件 options,这是一种更为通用的 protoc 插件配置方式。

    • [=value] 表示该参数可以可选地指定一个值。对于布尔值参数(true/false),如果未指定值,则参数存在时的默认值为 true。对于类似 service-prefix 的字符串参数,必须显式提供值。

若未提供特定的生成选项,默认将同时生成客户端和服务端文件。默认情况下,支持所有扩展(目前仅支持 'http')。

如果你使用的是 Gradle,需要添加该插件:

...
apply plugin: 'com.google.protobuf'
...
buildscript {
  ...
  dependencies {
    // 假设使用 Gradle 2.12 或更高版本。若为更早的 Gradle 版本,请使用插件版本 0.7.5
    classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
  }
}
...
protobuf {
  protoc {
    artifact = 'com.google.protobuf:protoc:3.2.0'
  }
  plugins {
    grpc {
      artifact = "io.grpc:protoc-gen-grpc-java:1.25.0"
    }
    vertx {
      artifact = "io.vertx:vertx-grpc-protoc-plugin2:${vertx.grpc.version}"
    }
  }
  generateProtoTasks {
    all()*.plugins {
      grpc
      vertx
    }
  }
}

该插件将编译 build/generated/source/proto/main 目录下的 proto 文件,并使其在你的项目中可用。

代码生成器插件目前处于技术预览阶段(Tech Preview),未来可能会有所变更。

生成的 RPC 文件

对于每个服务定义(例如 Greeter),该插件都会生成若干具有特定用途的 Java RPC 文件:

生成的文件 描述 详情

Greeter.java

定义服务契约的基础接口,所有 RPC 方法均声明为抽象方法

客户端与服务端通用

GreeterClient.java

高级客户端接口,提供用于调用服务的 Vert.x Future 风格方法

客户端 API

GreeterService.java

高级服务端接口,用于通过 Vert.x Future 风格处理器实现服务

服务端 Stub

GreeterGrpcClient.java

低级客户端实现,提供对 gRPC 请求/响应流的直接访问

客户端请求 / 响应 API

GreeterGrpcService.java

低级服务端实现,提供对 gRPC 请求/响应流的直接访问以进行处理

服务端请求 / 响应 API

GreeterGrpcClient 和 GreeterGrpcService 类提供了更底层的 API,赋予你对流式传输和流量控制更大的掌控力;而 GreeterClient 和 GreeterService 提供了更高层、更符合 Vert.x 惯用法的 API,在大多数场景下使用更加简单方便。

此外,当启用 grpc-io=true 时,该插件还会生成:

生成的文件 描述 详情

GreeterGrpcIo.java

兼容层,用于配合标准 io.grpc 存根(Stub)及现有的 gRPC/IO 生态系统使用该服务

gRPC/IO 服务端 / gRPC/IO 客户端

Vert.x gRPC 服务端

Vert.x gRPC Server 是一个由 Vert.x HTTP 服务端驱动的 gRPC 服务端,取代了原有的基于 Netty 的集成 gRPC 客户端。

该服务端既提供了面向 gRPC 请求/响应的 API,也提供了借助 Vert.x gRPC 生成器生成的存根(Stub)机制。

使用 Vert.x gRPC 服务端

要使用 Vert.x gRPC 服务端,请在构建描述文件的 dependencies 部分添加以下依赖:

pom.xml
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpc-server</artifactId>
  <version>5.0.12</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-server:5.0.12'
}

创建 gRPC 服务端

GrpcServer 是一个 Handler<HttpServerRequest>,可用作 HTTP 服务端的请求处理器。

GrpcServer grpcServer = GrpcServer.server(vertx);

HttpServer server = vertx.createHttpServer(config, sslOptions);

server
  .requestHandler(grpcServer)
  .listen();

GrpcServer 也可以在 Vert.x Web 路由中使用:

route.consumes("application/grpc").handler(rc -> grpcServer.handle(rc.request()));

符合惯用法(Idiomatic)的服务端 Stub 生成

Vert.x gRPC protoc 插件 生成了符合惯用法(Idiomatic)的服务端存根:

  • examples/Greeter.java

  • examples/GreeterService.java

  • examples/GreeterGrpcService.java

Greeter 接口定义了该服务的基础_契约_:

public interface Greeter {

  Future<examples.grpc.HelloReply> sayHello(examples.grpc.HelloRequest request);

}

GreeterService 类实现了 Greeter 契约:

public class GreeterService implements Greeter {

  public Future<examples.grpc.HelloReply> sayHello(examples.grpc.HelloRequest request) {
    // ...
  }

  protected void sayHello(examples.grpc.HelloRequest request, Completable<examples.grpc.HelloReply> response) {
    // ...
  }
}

通常,你可以继承该类来提供具体的服务实现。

GreeterGrpcService 类继承了 GreeterService,使你能够将自己的服务实现转换为可以绑定到 Vert.x gRPC 服务端的 gRPC 服务:

GreeterGrpcService service = GreeterGrpcService.create(new GreeterService() {
  @Override
  public Future<HelloReply> sayHello(HelloRequest request) {
    return Future.succeededFuture(HelloReply.newBuilder()
      .setMessage("Hello " + request.getName())
      .build());
  }
});

grpcServer.add(service);

每个服务方法都提供两种风格,你可以根据自己的编码偏好重写相应的方法。

一元方法(Unary methods)

一元方法可以返回一个 Vert.x Future

GreeterService service = new GreeterService() {
  @Override
  public Future<HelloReply> sayHello(HelloRequest request) {
    return Future.succeededFuture(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
  }
};

或者处理一个 Vert.x Promise

GreeterService service = new GreeterService() {
  @Override
  public void sayHello(HelloRequest request, Completable<HelloReply> response) {
    response.succeed(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
  }
};

在这两种情况下,你都需要将存根绑定到现有的 GrpcServer 上

server.addService(service);

流式请求(Streaming requests)

流式请求使用 ReadStream 实现:

StreamingGrpcService service = new StreamingGrpcService() {
  @Override
  public void sink(ReadStream<Item> stream, Completable<Empty> response) {
    stream.handler(item -> {
      System.out.println("Process item " + item.getValue());
    });
    // 发送响应
    stream.endHandler(v -> response.succeed(Empty.getDefaultInstance()));
  }
};
server.addService(service);

流式响应(Streaming responses)

流式响应基于 Vert.x Stream 实现,并提供两种风格。

你可以返回一个 Vert.x ReadStream 并由服务代为发送:

StreamingService service = new StreamingService() {
  @Override
  public Future<ReadStream<Item>> source(Empty request) {
    return streamOfItems();
  }
};

或者你可以处理一个 WriteStream:

StreamingService service = new StreamingService() {
  @Override
  public void source(Empty request, WriteStream<Item> response) {
    response.write(Item.newBuilder().setValue("value-1").build());
    response.end(Item.newBuilder().setValue("value-2").build());
  }
};

服务端请求 / 响应 API

gRPC 请求/响应服务端 API 提供了另一种与客户端交互的方式,无需继承任何 Java 类。

绑定 gRPC 服务方法

你将使用 ServiceMethod 来绑定 gRPC 服务方法

ServiceName serviceName = ServiceName.create("examples.grpc", "Greeter");
ServiceMethod<HelloRequest, HelloReply> sayHello = ServiceMethod.server(
  serviceName,
  "SayHello",
  GrpcMessageEncoder.encoder(),
  GrpcMessageDecoder.decoder(HelloRequest.newBuilder()));

然而,大多数情况下你将使用 Vert.x gRPC protoc 插件 生成的 ServiceMethod 常量:

ServiceMethod<HelloRequest, HelloReply> sayHello = GreeterGrpcService.SayHello;

请求 / 响应

每个服务方法均由一个处理器进行处理,该处理器使用 ServiceMethod 进行绑定。

server.callHandler(GreeterGrpcService.SayHello, request -> {

  request.handler(hello -> {

    GrpcServerResponse<HelloRequest, HelloReply> response = request.response();

    HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + hello.getName()).build();

    response.end(reply);
  });
});

流式请求

你可以设置处理器来处理请求事件

server.callHandler(StreamingGrpcService.Sink, request -> {
  request.handler(item -> {
    // 处理条目
  });
  request.endHandler(v ->{
    // 没有更多条目
    // 发送响应
    request.response().end(Empty.getDefaultInstance());
  });
  request.exceptionHandler(err -> {
    // 发生异常错误
  });
});

流式响应

流式响应需要在流中的每个元素上调用 write,并使用 end 结束该流

server.callHandler(StreamingGrpcService.Source, request -> {
  GrpcServerResponse<Empty, Item> response = request.response();
  request.handler(empty -> {
    for (int i = 0;i < 10;i++) {
      response.write(Item.newBuilder().setValue("1").build());
    }
    response.end();
  });
});

双向流式请求 / 响应

双向流式请求/响应只是流式请求与流式响应的结合体

server.callHandler(StreamingGrpcService.Pipe, request -> {

  request.handler(item -> request.response().write(item));
  request.endHandler(v -> request.response().end());
});

gRPC 协议支持

HTTP/2 协议

Vert.x gRPC 服务端默认提供的协议是 HTTP/2。

此外,Vert.x gRPC 服务端还支持 gRPC-Web 协议以及 HTTP/JSON 转码(Transcoding)。

gRPC-Web 协议

Vert.x gRPC 服务端默认支持 gRPC-Web 协议。

如果你的网站服务器与 gRPC 服务器不同,你需要为 gRPC 服务器配置 CORS。这可以通过 Vert.x Web 路由和 CORS 处理器来实现:

CorsHandler corsHandler = CorsHandler.create()
  .addRelativeOrigin("https://www.mycompany.com")
  .allowedHeaders(Set.of("keep-alive","user-agent","cache-control","content-type","content-transfer-encoding","x-custom-key","x-user-agent","x-grpc-web","grpc-timeout"))
  .exposedHeaders(Set.of("x-custom-key","grpc-status","grpc-message"));
router.route("/com.mycompany.MyService/*").handler(corsHandler);

gRPC 转码(Transcoding)

Vert.x gRPC 服务端支持 gRPC 转码(Transcoding),能够在 HTTP/JSON 请求与 gRPC 服务之间建立映射。

协议配置

默认情况下,gRPC 服务端接受所有协议。

若要禁用对特定协议的支持,可以使用 removeEnabledProtocol 配置选项,然后使用 GrpcServer#server(vertx, options) 创建服务端。

移除 gRPC-Web 支持
GrpcServer server = GrpcServer.server(vertx, new GrpcServerOptions()
  .removeEnabledProtocol(GrpcProtocol.WEB)
  .removeEnabledProtocol(GrpcProtocol.WEB_TEXT)
);

流量控制

请求和响应都是支持背压的 Vert.x Stream。

你可以暂停(pause)、恢复(resume)或拉取(fetch)请求

request.pause();

performAsyncOperation().onComplete(ar -> {
  // 然后恢复
  request.resume();
});

你可以检查响应的可写状态并设置排空处理器(drain handler)

if (response.writeQueueFull()) {
  response.drainHandler(v -> {
    // 再次可写
  });
} else {
  response.write(item);
}

超时与截止时间(Deadlines)

gRPC 服务端支持处理超时与截止时间(Deadlines)。

每当服务收到包含超时信息的请求时,均可检索该超时时间。

long timeout = request.timeout();

if (timeout > 0L) {
  // 已接收到超时信息
}

默认情况下,服务端:

  • 不会自动为给定请求调度截止时间定时器

  • 不会自动将截止时间传播到 Vert.x 客户端

服务端可以调度截止时间:当请求携带超时信息时,服务端会在本地调度一个定时器,在响应未及时发出时取消该请求。

服务端可以传播截止时间:当请求携带超时信息时,服务端会计算截止时间,并将当前服务端请求与该截止时间关联起来。Vert.x gRPC 客户端可以使用该截止时间计算待发送的超时时间,并将超时级联传播到另一个 gRPC 服务端。

GrpcServer server = GrpcServer.server(vertx, new GrpcServerOptions()
  .setScheduleDeadlineAutomatically(true)
  .setDeadlinePropagation(true)
);

JSON 传输格式(JSON wire format)

gRPC 默认假定使用 Protobuf 传输格式。

Vert.x gRPC 服务端同样支持 JSON 传输格式,即携带 application/grpc+json 内容类型的 gRPC 请求。

com.google.protobuf:protobuf-java-util 库负责执行 JSON 编码/解码。

通过 Vert.x JsonObject 还可以支持贫血式(Anemic)JSON

ServiceMethod<JsonObject, JsonObject> sayHello = ServiceMethod.server(
  ServiceName.create("helloworld", "Greeter"),
  "SayHello",
  GrpcMessageEncoder.JSON_OBJECT,
  GrpcMessageDecoder.JSON_OBJECT
);

server.callHandler(sayHello, request -> {
  request.last().onSuccess(helloRequest -> {
    request.response().end(new JsonObject().put("message", "Hello " + helloRequest.getString("name")));
  });
});

压缩

你可以在发送任何消息*之前*通过设置响应编码来压缩响应消息。

在设置编码之前,应使用 acceptedEncodings 确保客户端 RPC 支持该编码算法。

if (response.acceptedEncodings().contains("gzip")) {
  response.encoding("gzip");
}

// 在定义编码后写入条目
response.write(Item.newBuilder().setValue("item-1").build());
response.write(Item.newBuilder().setValue("item-2").build());
response.write(Item.newBuilder().setValue("item-3").build());
gRPC-Web 协议不支持压缩。

解压缩

当客户端发送编码请求时,服务端会透明地执行解压缩。

gRPC-Web 协议不支持解压缩。

消息级 API

服务端提供了消息级 API,可直接与经过 Protobuf 编码的 gRPC 消息进行交互。

服务端消息级 API 可以与客户端消息级 API 配合使用,以编写 gRPC 反向代理。

当你对消息的具体内容不感兴趣,而是希望将它们转发给另一个服务时(例如编写代理),该 API 非常有用。

ServiceName greeterServiceName = ServiceName.create("helloworld", "Greeter");

server.callHandler(request -> {

  if (request.serviceName().equals(greeterServiceName) && request.methodName().equals("SayHello")) {

    request.handler(protoHello -> {
      // 处理 Protobuf 编码的 hello
      performAsyncOperation(protoHello)
        .onSuccess(protoReply -> {
          // 以 Protobuf 编码的 reply 进行回复
          request.response().end(protoReply);
        }).onFailure(err -> {
          request.response()
            .status(GrpcStatus.ABORTED)
            .end();
        });
    });
  } else {
    request.response()
      .status(GrpcStatus.NOT_FOUND)
      .end();
  }
});

你还可以设置 messageHandler 来处理 GrpcMessage。此类消息会保留客户端编码,这在被转发的目标服务能够直接处理压缩消息时非常有用,此时消息无需解压缩再重新压缩。

ServiceName greeterServiceName = ServiceName.create("helloworld", "Greeter");

server.callHandler(request -> {

  if (request.serviceName().equals(greeterServiceName) && request.methodName().equals("SayHello")) {

    request.messageHandler(helloMessage -> {

      // 可以是 identity 或 gzip
      String helloEncoding = helloMessage.encoding();

      // 处理 hello 消息
      handleGrpcMessage(helloMessage)
        .onSuccess(replyMessage -> {
          // 回复 reply 消息

          // 可以是 identity 或 gzip
          String replyEncoding = replyMessage.encoding();

          // 发送回复
          request.response().endMessage(replyMessage);
        }).onFailure(err -> {
          request.response()
            .status(GrpcStatus.ABORTED)
            .end();
        });
    });
  } else {
    request.response()
      .status(GrpcStatus.NOT_FOUND)
      .end();
  }
});

writeMessage 与 endMessage 将会处理消息编码:

  • 当消息使用响应编码时,消息将按原样发送

  • 当消息使用不同的编码时,它将被重新编码(例如压缩或解压缩)

gRPC 反射服务(Reflection service)

你可以为 Vert.x gRPC 服务端添加对 gRPC 反射服务 的支持。

要使用反射服务,请添加以下依赖:

pom.xml
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpc-reflection</artifactId>
  <version>5.0.12</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-reflection:5.0.12'
}

随后你即可在服务端部署反射服务:

GrpcServer grpcServer = GrpcServer.server(vertx);

// 添加反射服务
grpcServer.addService(ReflectionService.v1());

GreeterGrpcService greeterService = new GreeterGrpcService() {
  @Override
  public Future<HelloReply> sayHello(HelloRequest request) {
    return Future.succeededFuture(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
  }
};

grpcServer.addService(greeterService);

// 启动服务端
vertx.createHttpServer(config, sslOptions)
  .requestHandler(grpcServer)
  .listen();

gRPC 健康检查服务(Health service)

gRPC 健康检查服务实现了标准 gRPC 健康检查协议,允许客户端检查服务的健康状态。

默认情况下,HealthService 返回所有已注册服务的状态均为 SERVING。如果你希望覆盖此行为,需要为特定服务注册健康检查。

健康检查服务提供了一种通过 gRPC 暴露服务健康状态的简便方式。它实现了以下 RPC:

  • Check:用于检查服务的健康状态

  • List:用于列出服务及其健康状态

  • Watch:用于随着时间持续监控服务的健康状态

要使用健康检查服务,请添加以下依赖:

pom.xml
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpc-health</artifactId>
  <version>5.0.12</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-health:5.0.12'
}

以下演示如何创建健康检查服务并将其绑定到你的 gRPC 服务端:

GrpcServer grpcServer = GrpcServer.server(vertx);

// 创建健康检查服务实例
HealthService healthService = HealthService.create(vertx);

// 为你的服务注册健康检查
healthService.register("my.service.name", () -> Future.succeededFuture(true));

// 将健康检查服务添加到 gRPC 服务端
grpcServer.addService(healthService);

// 启动服务端
vertx.createHttpServer(config, sslOptions)
  .requestHandler(grpcServer)
  .listen();

客户端随后便可使用标准 gRPC 健康检查协议来检查你的服务健康状况。

Vert.x gRPC 客户端

Vert.x gRPC Client 既提供了面向 gRPC 请求/响应的 API,也提供了基于代码生成的客户端机制。

使用 Vert.x gRPC 客户端

要使用 Vert.x gRPC 客户端,请在构建描述文件的 dependencies 部分添加以下依赖:

pom.xml
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpc-client</artifactId>
  <version>5.0.12</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-client:5.0.12'
}

创建 gRPC 客户端

你可以轻松创建 gRPC 客户端:

GrpcClient client = GrpcClient.client(vertx);

符合惯用法(Idiomatic)的客户端 API 生成

Vert.x gRPC protoc 插件 生成了符合惯用法(Idiomatic)的客户端代码:

  • examples/Greeter.java

  • examples/GreeterClient.java

  • examples/GreeterGrpcClient.java

惯用法客户端对 GrpcClient 进行了封装,并提供符合 Vert.x 惯用风格的 API 来与服务进行交互。

Greeter 接口定义了该服务的基础_契约_:

public interface Greeter {

  Future<examples.grpc.HelloReply> sayHello(examples.grpc.HelloRequest request);

}

GreeterClient 接口继承了 Greeter 契约,并添加了 Vert.x codegen 注解,这使你能够进行进一步的代码生成(例如生成 RxJava 或 Mutiny 客户端):

@io.vertx.codegen.annotations.VertxGen
public interface GreeterClient extends Greeter {

  @io.vertx.codegen.annotations.GenIgnore(io.vertx.codegen.annotations.GenIgnore.PERMITTED_TYPE)
  Future<examples.grpc.HelloReply> sayHello(examples.grpc.HelloRequest request);
}

GreeterGrpcClient 接口继承了 GreeterClient,并提供了一个使用 Vert.x gRPC Client 与服务交互的具体实现:

GreeterGrpcClient greeterClient = GreeterGrpcClient.create(client, SocketAddress.inetSocketAddress(port, host));

一元 RPC 服务(Unary services)

一元 RPC 服务返回一个 Vert.x Future:

Future<HelloReply> response = greeterClient.sayHello(HelloRequest.newBuilder().setName("John").build());

response.onSuccess(result -> System.out.println("Service responded: " + response.result().getMessage()));

response.onFailure(err -> System.out.println("Service failure: " + response.cause().getMessage()));

流式请求(Streaming requests)

流式请求使用一个 Lambda 表达式,其中传入用于向服务发送消息的 Vert.x WriteStream:

Future<Empty> response = streamingClient.sink((stream, err) -> {
  stream.write(Item.newBuilder().setValue("Value 1").build());
  stream.write(Item.newBuilder().setValue("Value 2").build());
  stream.end(Item.newBuilder().setValue("Value 3").build());
});

或者,你也可以直接传入要发送到服务的消息流:

Future<Empty> response = streamingClient.sink(stream);

流式响应(Streaming responses)

流式响应会获取一个由服务发送的消息组成的 Vert.x ReadStream:

Future<ReadStream<Item>> response = streamingClient.source(Empty.getDefaultInstance());

response.onSuccess(stream -> stream
  .handler(item -> System.out.println("Item " + item.getValue()))
  .exceptionHandler(err -> System.out.println("Stream failed " + err.getMessage()))
  .endHandler(v -> System.out.println("Stream ended")));

response.onFailure(err -> System.out.println("Service failure: " + err.getMessage()));

生成 RxJava 客户端包装器

如果你希望在生成客户端时带有 Vert.x codegen 注解,可以使用 vertx-grpc-protoc-plugin2 中的选项来生成带有 Vert.x codegen 注解的客户端服务接口。为此,你需要向 vertx-grpc-protoc-plugin 传递 --vertx-codegen 选项:

<protocPlugin>
  <id>vertx-grpc-protoc-plugin2</id>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpc-protoc-plugin2</artifactId>
  <version>${stack.version}</version>
  <mainClass>io.vertx.grpc.plugin.VertxGrpcGenerator</mainClass>
  <args>
    <arg>--grpc-client</arg>
    <arg>--vertx-codegen</arg>
  </args>
</protocPlugin>

生成的客户端服务接口将带有 @io.vertx.codegen.annotations.VertxGen 注解。

@io.vertx.codegen.annotations.VertxGen
public interface GreeterClient {
  ...
}

因此,Vert.x codegen 注解处理器可以处理它,例如 Vert.x RxJava 生成器将生成一个具备符合 RxJava 惯用风格 API 的 RxJava 客户端包装器。

以下是一个 Maven 配置示例:

<plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <executions>
    <execution>
      <id>default-compile</id>
      <configuration>
        <annotationProcessorPaths>
          <annotationProcessorPath>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-codegen</artifactId>
            <classifier>processor</classifier>
            <version>${vertx.version}</version>
          </annotationProcessorPath>
          <annotationProcessorPath>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-rx-java3-gen</artifactId>
            <version>${vertx.version}</version>
          </annotationProcessorPath>
        </annotationProcessorPaths>
      </configuration>
    </execution>
  </executions>
</plugin>

生成的类随后即可使用:

// 使用 RxJava 版本
GreeterClient client = io.grpc.examples.rxjava3.helloworld.GreeterClient.create(grpcClient, SocketAddress.inetSocketAddress(8080, "localhost"));

// 获取 Single 而不是 Future
Single<HelloReply> reply = client.sayHello(HelloRequest.newBuilder().setName("World").build());

Flowable<HelloRequest> requestStream = Flowable.just("World", "Monde", "Mundo")
      .map(name -> HelloRequest.newBuilder().setName(name).build());

// 使用 Flowable 代替 Vert.x Stream
Flowable<String> responseStream = client
  .sayHelloStreaming(stream)
  .map(HelloReply::getMessage);
这需要 RxJava 版本的 GrpcClient

客户端请求 / 响应 API

gRPC 请求/响应客户端 API 提供了另一种与服务端交互的方式,无需依赖生成的存根(Stub)。

定位 gRPC 服务方法

你将使用 ServiceMethod 来定位 gRPC 服务方法:

ServiceName serviceName = ServiceName.create("examples.grpc", "Greeter");
ServiceMethod<HelloReply, HelloRequest> sayHello = ServiceMethod.client(
  serviceName,
  "SayHello",
  GrpcMessageEncoder.encoder(),
  GrpcMessageDecoder.decoder(HelloReply.newBuilder()));

然而,大多数情况下你可以直接使用 Vert.x gRPC protoc 插件 生成的 ServiceMethod 常量:

ServiceMethod<HelloReply, HelloRequest> sayHello = GreeterGrpcClient.SayHello;

请求 / 响应

与 gRPC 服务端交互需要创建发送到远端 gRPC 服务的请求。

SocketAddress server = SocketAddress.inetSocketAddress(443, "example.com");
Future<GrpcClientRequest<HelloRequest, HelloReply>> fut = client.request(server, GreeterGrpcClient.SayHello);
fut.onSuccess(request -> {
  // end 方法触发对服务的调用
  request.end(HelloRequest.newBuilder().setName("Bob").build());
});

response 持有响应,而 last 保存最终结果:

request.response().onSuccess(response -> {
  Future<HelloReply> fut = response.last();
  fut.onSuccess(reply -> {
    System.out.println("Received " + reply.getMessage());
  });
});

通过 Future 组合可以将上述所有步骤以紧凑的方式串联起来:

client
  .request(server, GreeterGrpcClient.SayHello).compose(request -> {
    request.end(HelloRequest
      .newBuilder()
      .setName("Bob")
      .build());
    return request.response().compose(response -> response.last());
  }).onSuccess(reply -> {
    System.out.println("Received " + reply.getMessage());
  });

流式请求

流式请求需要针对流中的每个元素调用 write,并使用 end 来结束流:

client
  .request(server, StreamingGrpcClient.Sink)
  .onSuccess(request -> {
  for (int i = 0;i < 10;i++) {
    request.write(Item.newBuilder().setValue("1").build());
  }
  request.end();
});

流式响应

你可以设置处理器来处理流式响应的各种响应事件:

client
  .request(server, StreamingGrpcClient.Source)
  .compose(request -> {
    request.end(Empty.getDefaultInstance());
    return request.response();
  })
  .onSuccess(response -> {
    response.handler(item -> {
      // 处理条目
    });
    response.endHandler(v -> {
      // 完成
    });
    response.exceptionHandler(err -> {
      // 发生异常
    });
  });

双向流式请求 / 响应

双向流式请求/响应只是流式请求与流式响应的结合体。

流量控制

无论你是使用请求/响应 API 还是符合惯用法的客户端 API,与之交互的流都是具备背压能力的 Vert.x Stream。

你可以检查请求的可写状态并设置排空处理器(drain handler):

if (request.writeQueueFull()) {
  request.drainHandler(v -> {
    // 再次可写
  });
} else {
  request.write(item);
}

你可以对响应执行 pause/resume/fetch 操作,以精确控制所读取的消息:

response.pause();

performAsyncOperation().onComplete(ar -> {
  // 然后恢复
  response.resume();
});
你可以在 Vert.x Core 文档中了解有关 Vert.x Stream 的更多信息

超时与截止时间(Deadlines)

gRPC 客户端支持处理超时与截止时间(Deadlines)。在 gRPC 请求上设置超时会指示客户端发送超时信息,以便告知服务端客户端希望在指定时间内获得响应。

此外,还可以将客户端配置为自动调度截止时间:当在请求上设置了超时时间时,客户端会在本地调度一个定时器,在未及时收到响应时自动取消该请求。

GrpcClient client = GrpcClient.client(vertx, new GrpcClientOptions()
  .setTimeout(10)
  .setTimeoutUnit(TimeUnit.SECONDS)
  .setScheduleDeadlineAutomatically(true));

也可以针对单个请求单独设置超时:

Future<GrpcClientRequest<HelloRequest, HelloReply>> fut = client.request(server, GreeterGrpcClient.SayHello);
fut.onSuccess(request -> {

  request
    // 针对该请求设置 10 秒超时,该超时信息将发送给 gRPC 服务
    .timeout(10, TimeUnit.SECONDS);

  request.end(HelloRequest.newBuilder().setName("Bob").build());
});

取消请求

你可以调用 cancel 来取消请求:

request.cancel();
取消操作会向服务端发送一个 HTTP/2 RST_STREAM 帧。

客户端负载均衡

gRPC Client 可以配置为执行客户端负载均衡。

基于 DNS 的负载均衡

基于 DNS 的负载均衡通过 DNS 查询将单个主机解析为多个 IP 地址(通常为 A 记录)。

你可以设置负载均衡器以启用基于 DNS 的负载均衡:

GrpcClient client = GrpcClient
  .builder(vertx)
  .withLoadBalancer(LoadBalancer.ROUND_ROBIN)
  .build();

client
  .request(SocketAddress.inetSocketAddress(port, server), GreeterGrpcClient.SayHello)
  .compose(request -> {
    request.end(HelloRequest
      .newBuilder()
      .setName("Bob")
      .build());
    return request.response().compose(response -> response.last());
  }).onSuccess(reply -> {
    System.out.println("Received " + reply.getMessage());
  });

提供常见的负载均衡策略,你可以参阅 Vert.x HTTP 客户端负载均衡文档进行配置。

基于 Address 的负载均衡

基于 Address 的负载均衡依赖 Vert.x 地址解析器将单个地址解析为多个 host/port 套接字地址。

你可以设置地址解析器以启用负载均衡,Vert.x Service Resolver 实现了若干地址解析器,例如 Kubernetes 解析器。

GrpcClient client = GrpcClient
  .builder(vertx)
  .withAddressResolver(KubeResolver.create())
  .withLoadBalancer(LoadBalancer.ROUND_ROBIN)
  .build();

与基于 DNS 的负载均衡不同,基于 Address 的负载均衡使用抽象的 Address 代替 SocketAddress。地址解析器实现会将地址解析为套接字地址列表。

Vert.x Service Resolver 定义了 ServiceAddress:

ServiceAddress address = ServiceAddress.of("GreeterService");

client
  .request(address, GreeterGrpcClient.SayHello)
  .compose(request -> {
    request.end(HelloRequest
      .newBuilder()
      .setName("Bob")
      .build());
    return request.response().compose(response -> response.last());
  }).onSuccess(reply -> {
    System.out.println("Received " + reply.getMessage());
  });

你可以参阅 Vert.x Service Resolver 项目文档了解更多详细信息。

JSON 传输格式(JSON wire format)

gRPC 默认假定使用 Protobuf 传输格式。

Vert.x gRPC 客户端同样支持 JSON 传输格式。

你可以使用 application/grpc+json 内容类型调用 JSON 服务方法:

client
  .request(server, GreeterGrpcClient.SayHello).compose(request -> {
    request.format(WireFormat.JSON);
    request.end(HelloRequest
      .newBuilder()
      .setName("Bob")
      .build());
    return request.response().compose(response -> response.last());
  }).onSuccess(reply -> {
    System.out.println("Received " + reply.getMessage());
  });

客户端将发送 application/grpc+json 请求。

JSON 编码/解码由 com.google.protobuf:protobuf-java-util 库实现。

通过 Vert.x JsonObject 还可以支持贫血式(Anemic)JSON:

ServiceMethod<JsonObject, JsonObject> sayHello = ServiceMethod.client(
  ServiceName.create("helloworld", "Greeter"),
  "SayHello",
  GrpcMessageEncoder.JSON_OBJECT,
  GrpcMessageDecoder.JSON_OBJECT
);
client
  .request(server, sayHello).compose(request -> {
    request.end(new JsonObject().put("name", "Bob"));
    return request.response().compose(response -> response.last());
  }).onSuccess(reply -> {
    System.out.println("Received " + reply.getString("message"));
  });

在创建符合惯用法的客户端时,你也可以指定 JSON 传输格式:

GreeterGrpcClient greeterClient = GreeterGrpcClient.create(client, SocketAddress.inetSocketAddress(port, host), WireFormat.JSON);

压缩

你可以在发送任何消息*之前*通过设置请求编码来压缩请求消息:

request.encoding("gzip");

// 在定义编码后写入条目
request.write(Item.newBuilder().setValue("item-1").build());
request.write(Item.newBuilder().setValue("item-2").build());
request.write(Item.newBuilder().setValue("item-3").build());

解压缩

当服务端发送经过编码的响应时,客户端会透明地完成解压缩。

消息级 API

客户端提供了消息级 API,可直接与经过 Protobuf 编码的 gRPC 消息进行交互。

Vert.x gRPC Context Storage

Vert.x gRPC Context Storage overrides the default io.grpc.Context.Storage implementation.

The default implementation always stores the gRPC context in a thread-local variable. This implementation stores the gRPC context the same way as Vert.x core stores request tracing data.

This means, for example, that when you implement a service method, the gRPC context is propagated across Vert.x async API calls:

Context grpcCtx1 = Context.current();

vertx.<String>executeBlocking(prom -> {

  // Same as grpcCtx1
  Context grpcCtx2 = Context.current();

  String result = doSomething();
  prom.complete(result);

}, ar -> {

  // Same as grpcCtx1 and grpcCtx2
  Context grpcCtx3 = Context.current();

});

The gRPC context is propagated across Vert.x async API calls only when the current Vert.x Context is bound to a Vert.x HTTP server request.

It is not propagated if, for example, you invoke a stub on a non-Vert.x thread or from a verticle start method.

Using Vert.x gRPC Context Storage

To use Vert.x gRPC Context Storage, add the following dependency to the dependencies section of your build descriptor:

  • Maven (in your pom.xml):

<dependency>
 <groupId>io.vertx</groupId>
 <artifactId>vertx-grpc-context-storage</artifactId>
 <version>4.4.9</version>
</dependency>
  • Gradle (in your build.gradle file):

dependencies {
 compile 'io.vertx:vertx-grpc-context-storage:4.4.9'
}