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

Vert.x gRPC

关于 gRPC 的最佳描述可见维基百科。

gRPC 是一个开源远程过程调用(RPC)系统,最初由 Google 开发。它使用 HTTP/2 作为传输层,使用 Protocol Buffers 作为接口描述语言,并提供身份认证、双向流式传输与流量控制、阻塞或非阻塞绑定以及请求取消与超时等特性。它可以为多种编程语言生成跨平台的客户端和服务端绑定。

— Wikipedia

Vert.x gRPC 是一个将 gRPC 的编程风格与 Vert.x 风格对齐的模块。作为该模块的使用者,你可以在享受 gRPC 所有优势的同时,使用熟悉的 Vert.x Stream 和 Future 编码风格。

有关 gRPC 的更多信息,请查阅官方文档网站 http://www.grpc.io/。

Vert.x gRPC 划分为以下几个组成部分:

  • Vert.x gRPC Server

  • Vert.x gRPC Client

  • Vert.x gRPC/IO Server

  • Vert.x gRPC/IO Client

  • 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.1.10</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-server:5.1.10'
}

创建 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.1.10</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-reflection:5.1.10'
}

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

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.1.10</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-health:5.1.10'
}

以下演示如何创建健康检查服务并将其绑定到你的 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.1.10</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpc-client:5.1.10'
}

创建 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 消息进行交互。

gRPC 转码(Transcoding)

gRPC 转码(Transcoding)允许你的服务同时接收 gRPC 和 HTTP/JSON(接收/回复 application/json 内容类型)请求,提供了更高的灵活性。该特性在以下场景尤其有用:

  • 你希望向不支持 gRPC 的客户端暴露你的 gRPC 服务

  • 你需要同时支持传统的 REST API 和 gRPC 端点

  • 你希望在利用 gRPC 高效性能的同时,保持对 HTTP/JSON 的兼容性

你不需要为 gRPC 转码进行特定的协议配置,因为它本身就是基于 HTTP 协议对外服务的。

服务开箱即支持转码,不过通过额外的配置(例如挂载路径),你能够充分发挥转码的最佳效果以投入实际运行。

转码功能在 Vert.x 5.0 中处于技术预览阶段(Tech Preview),直至 API 趋于稳定。

使用转码

要使用转码功能,请添加以下依赖:

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

符合惯用法(Idiomatic)的转码生成

Vert.x gRPC protoc 插件 为 gRPC 服务生成转码定义。

该插件支持通过 http.proto 为 gRPC 服务生成转码定义。

google.api.http 注解允许你将 gRPC 方法映射到 HTTP 端点,使你的服务能够同时处理 gRPC 和 HTTP/REST 请求。该插件支持各种 HTTP 方法(GET、POST)以及自定义方法。

gRPC 转码定义示例
syntax = "proto3";

import "google/api/http.proto";

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      post: "/v1/hello"
      additional_bindings {
        get: "/v1/hello/{name}"
      }
    };
  }
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

当服务声明了 HttpRule 时,Vert.x gRPC protoc 插件将生成转码服务方法:

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

server.addService(service);
服务会响应服务端已启用的所有协议,默认包括 application/grpc、application/grpc+json、application/grpc-web、application/grpc-web-text,当然还有 application/json。

若要测试 gRPC 转码,可以使用类似 curl 的工具向你的 gRPC 服务发送 HTTP 请求:

curl -X POST -H "Content-Type: application/json" -d '{"name":"vert.x"}' http://localhost:8080/v1/hello

你也可以请求带有路径参数映射的版本:

curl -X GET -H "Content-Type: application/json" http://localhost:8080/v1/hello/vert.x

自动 HTTP 映射

当部署 gRPC 服务且未指定特定转码配置时,该服务将被默认映射为 HTTP POST,转码器会将请求/响应体转换为相应的服务消息。

基础 HTTP 映射

service Greeter {
  // 映射带有 URL 参数的 GET 端点
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      get: "/v1/hello/{name}"
      additional_bindings {
        post: "/v1/hello"  // 备选的 POST 端点
        body: "*"          // 将 HTTP 请求体作为请求消息
      }
    };
  }

  // 映射一个带有备选 GET 绑定的 POST 端点
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      post: "/v2/hello"
      body: "*"
      additional_bindings {
        get: "/v2/hello/{name}"
      }
    };
  }
}

高级配置

自定义方法

service Greeter {
  // 定义自定义 HTTP 方法
  rpc SayHelloCustom (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      custom: {
        kind: "ACL"
        path: "/v1/hello/custom/{name}"
      }
    };
  }
}

请求体处理

HttpRule 中的 body 字段控制如何将 HTTP 请求体映射到 gRPC 消息中:

  • 省略:不使用 HTTP 请求体;消息仅根据路径参数和查询参数构建。

  • body: "*":将整个 HTTP 请求体映射到 gRPC 消息的根对象。

  • body: "<field>":将 HTTP 请求体映射到指定的顶层字段。

当发生命名冲突时,路径参数和查询参数绑定始终优先于请求体字段。

service Greeter {
  // 指定哪个字段应映射到 HTTP 请求体
  rpc SayHelloWithBody (HelloBodyRequest) returns (HelloReply) {
    option (google.api.http) = {
      post: "/v1/hello/body"
      body: "request"  // 将 "request" 字段映射到请求体
    };
  }
}

message HelloBodyRequest {
  HelloRequest request = 1;
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string reply = 1;
}

响应体映射

service Greeter {
  // 配置哪个字段应作为 HTTP 响应体
  rpc SayHelloWithResponseBody (HelloRequest) returns (HelloBodyResponse) {
    option (google.api.http) = {
      post: "/v1/hello/body/response"
      response_body: "response"  // 将 "response" 字段映射到响应体
    };
  }
}

message HelloRequest {
  string name = 1;
}

message HelloBodyResponse {
  HelloResponse response = 1;
}

message HelloResponse {
  string reply = 1;
}

转码错误处理

如果在转码过程中发生错误,服务端将返回带有对应状态码的 HTTP 错误响应。大多数 gRPC 状态码都会尽最大努力映射为对应的 HTTP 状态码。如果状态码未映射,服务端将返回 500 Internal Server Error。

gRPC 状态码

HTTP 状态码

描述

OK

200

操作已成功完成。

CANCELLED

408

操作已取消(通常由调用方发起)。

UNKNOWN

500

未知错误。

INVALID_ARGUMENT

400

客户端指定了无效的参数。

DEADLINE_EXCEEDED

504

在操作完成之前截止时间已过。

NOT_FOUND

404

未找到请求的实体(例如文件或目录)。

ALREADY_EXISTS

409

尝试创建的实体(例如文件或目录)已存在。

PERMISSION_DENIED

403

调用方没有执行指定操作的权限。

RESOURCE_EXHAUSTED

429

某种资源已耗尽,例如单个用户的配额耗尽,或者整个文件系统磁盘空间不足。

FAILED_PRECONDITION

400

操作被拒绝,因为系统未处于执行该操作所需的状态。

ABORTED

409

操作已中止,通常是由于并发问题,如序列号检查失败、事务中止等。

OUT_OF_RANGE

400

操作尝试超出了有效范围。

UNIMPLEMENTED

501

该服务未实现或不支持/未启用此操作。

INTERNAL

500

内部错误。这意味着底层系统预期的某些不变量遭到了破坏。

UNAVAILABLE

503

服务当前不可用。

DATA_LOSS

500

不可恢复的数据丢失或损坏。

UNAUTHENTICATED

401

该请求没有执行操作所需的有效身份认证凭证。

gRPC EventBus 传输层

gRPC EventBus 传输层允许 gRPC 服务通过 Vert.x EventBus 而非 HTTP 进行通信。 该传输层支持 Protobuf 和 JSON 两种传输格式,并与生成的 gRPC 服务存根(Stub)兼容配合。

该模块在 Vert.x 5.1 中处于技术预览阶段(Tech Preview),直至 API 趋于稳定。

使用 EventBus 传输层

要使用 EventBus 传输层,请添加以下依赖:

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

EventBus 服务端

EventBusGrpcServer 为每个服务注册 EventBus 消费者,并将接收到的消息分发给服务处理器。它继承自 ServiceContainer,因此生成的服务存根可以直接开箱即用。

EventBusGrpcServer server = EventBusGrpcServer.server(vertx);

你可以像在普通 gRPC 服务端一样添加生成的服务:

EventBusGrpcServer server = EventBusGrpcServer.server(vertx);

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

server.addService(service);

服务端使用服务的全限定类名作为地址(例如 examples.grpc.Greeter)注册 EventBus 消费者,并通过 action 发送头路由到具体的方法。

EventBus 客户端

EventBusGrpcClient 通过 EventBus 发送 gRPC 请求。它继承自 ServiceInvoker,因此生成的客户端存根可以直接开箱即用。

EventBusGrpcClient client = EventBusGrpcClient.client(vertx);

你可以使用生成的客户端存根:

EventBusGrpcClient client = EventBusGrpcClient.client(vertx);

GreeterClient greeter = GreeterGrpcClient.create(client);

greeter.sayHello(HelloRequest.newBuilder().setName("World").build())
  .onSuccess(reply -> System.out.println("Received: " + reply.getMessage()));

JSON 传输格式

默认情况下,该传输层使用 Protobuf 编码。你可以在创建客户端时传入传输格式以使用 JSON:

EventBusGrpcClient client = EventBusGrpcClient.client(vertx);

GreeterClient greeter = GreeterGrpcClient.create(client, WireFormat.JSON);

greeter.sayHello(HelloRequest.newBuilder().setName("World").build())
  .onSuccess(reply -> System.out.println("Received: " + reply.getMessage()));

服务端会以 grpc-wire-format 发送头中指定的相同传输格式进行响应。

局限性

该传输层仅支持一元(Unary,请求/响应)调用,流式传输可能会在后续版本中提供支持。

Vert.x gRPC/IO 服务端

Vert.x gRPC/IO Server 通过集成 grpc-java 扩展了 Vert.x gRPC 服务端。

该服务端通过服务桥接(Service bridge)提供了与 grpc-java 生成存根方式的兼容性。

使用 Vert.x gRPC/IO 服务端

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

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

服务桥接(Service bridge)

Vert.x gRPC 服务端可以桥接 gRPC 服务,以便配合 grpc-java 生成的服务端类一起使用。

GrpcIoServer grpcServer = GrpcIoServer.server(vertx);

GreeterGrpc.GreeterImplBase service = new GreeterGrpc.GreeterImplBase() {
  @Override
  public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    responseObserver.onNext(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
    responseObserver.onCompleted();
  }
};

// 在 gRPC 服务端中绑定服务
grpcServer.addService(service);

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

该服务也可以添加到 EventBus 传输层中。

EventBusGrpcServer server = EventBusGrpcServer.server(vertx);

GrpcIoServiceBridge bridge = GrpcIoServiceBridge.bridge(service);

server.addService(bridge);

该桥接器支持截止时间自动取消:当收到携带超时的 gRPC 请求时,截止时间将与 io.grpc.Context 关联并可从当前上下文中获取。当关联的超时触发时,该截止时间会自动取消正在进行的请求。

符合惯用法(Idiomatic)的 gRPC/IO 服务

Vert.x gRPC protoc 插件 支持生成 gRPC/IO 客户端代码:

  • examples/Greeter.java

  • examples/GreeterService.java

  • examples/GreeterGrpcIo.java

默认情况下不生成 GreeterGrpcIo,要激活它,需要告知 Vert.x gRPC protoc 插件进行生成:

<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-io</arg>
  </args>
</protocPlugin>

GreeterGrpcIo 提供了一个可绑定的服务(BindableService),它使用 Vert.x gRPC/IO 服务端来暴露该服务:

GrpcIoServer grpcServer = GrpcIoServer.server(vertx);

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

// 在 gRPC 服务端中绑定服务
grpcServer.addService(service);

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

你可以阅读 符合惯用法(Idiomatic)的服务端 章节以了解更多内容。

gRPC 反射 API

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

GrpcIoServer grpcServer = GrpcIoServer.server(vertx);

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

GreeterGrpc.GreeterImplBase greeterService = new GreeterGrpc.GreeterImplBase() {
  @Override
  public void sayHello(HelloRequest request, StreamObserver<HelloReply> responseObserver) {
    responseObserver.onNext(HelloReply.newBuilder().setMessage("Hello " + request.getName()).build());
    responseObserver.onCompleted();
  }
};

// 在 gRPC 服务端中绑定服务
grpcServer.addService(greeterService);

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

随后你即可使用类似 gRPCurl 的工具来探索和调用你的 gRPC API。

grpcurl -plaintext localhost:50051 list

grpcurl -plaintext localhost:50051 describe .helloworld.HelloRequest

grpcurl -plaintext -d '{"name": "Vert.x"}' localhost:50051 helloworld.Greeter

Vert.x gRPC/IO 客户端

Vert.x gRPC/IO Client 通过集成 grpc-java 扩展了 Vert.x gRPC 客户端。

该客户端通过 gRPC Channel 提供了基于生成存根(Stub)的机制。

使用 Vert.x gRPC/IO 客户端

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

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

gRPC Channel

Vert.x gRPC/IO 客户端提供了一个 gRPC Channel,用于配合 grpc-java 生成的客户端类使用。

GrpcIoClientChannel channel = new GrpcIoClientChannel(client, SocketAddress.inetSocketAddress(443, "example.com"));

GreeterGrpc.GreeterStub greeter = GreeterGrpc.newStub(channel);

StreamObserver<HelloReply> observer = new StreamObserver<>() {
  @Override
  public void onNext(HelloReply value) {
    // 处理响应
  }

  @Override
  public void onCompleted() {
    // 完成
  }

  @Override
  public void onError(Throwable t) {
    // 发生异常
  }
};

greeter.sayHello(HelloRequest.newBuilder().setName("Bob").build(), observer);

你可以创建 EventBus Channel

EventBusGrpcClient client = EventBusGrpcClient.client(vertx);

GrpcIoClientChannel channel = new GrpcIoClientChannel(client);

GreeterGrpc.GreeterStub greeter = GreeterGrpc.newStub(channel);

通过通用的 gRPC API 支持超时与截止时间(Deadlines)。

GreeterGrpc.GreeterStub greeter = GreeterGrpc.newStub(channel).withDeadlineAfter(10, TimeUnit.SECONDS);

greeter.sayHello(HelloRequest.newBuilder().setName("Bob").build(), observer);

截止时间是级联传递的,例如当当前的 io.grpc.Context 携带了截止时间且存根未显式设置截止时间时,客户端会自动继承该隐式截止时间。在 gRPC 服务端调用中使用存根时,即可设置此类截止时间。

符合惯用法(Idiomatic)的 gRPC/IO 客户端

Vert.x gRPC protoc 插件 支持生成 gRPC/IO 客户端代码:

  • examples/Greeter.java

  • examples/GreeterClient.java

  • examples/GreeterGrpcIo.java

默认情况下不生成 GreeterGrpcIo,要激活它,需要告知 Vert.x gRPC protoc 插件进行生成:

<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-io</arg>
  </args>
</protocPlugin>

GreeterGrpcIo 提供了一个客户端存根(Stub),使用 Vert.x gRPC/IO 客户端与服务进行交互:

GrpcIoClientChannel channel = new GrpcIoClientChannel(client, SocketAddress.inetSocketAddress(443, "example.com"));

GreeterGrpcIo.GreeterStub greeter = GreeterGrpcIo.newStub(vertx, channel);

Future<HelloReply> future = greeter.sayHello(HelloRequest.newBuilder().setName("Bob").build());

future
  .onSuccess(reply -> {
    // 处理响应
  })
  .onFailure(err -> {
    // 发生异常
  });

你可以阅读 符合惯用法(Idiomatic)的客户端 章节以了解更多内容。

EventBus 传输层

你可以使用

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

Vert.x gRPC Context Storage 覆盖了默认的 io.grpc.Context.Storage 实现。

默认实现始终将 gRPC 上下文存储在线程局部(thread-local)变量中。 本实现存储 gRPC 上下文的方式与 Vert.x Core 存储请求链路追踪数据的方式相同。

举例来说,这意味着当你实现服务方法时,gRPC 上下文可以在 Vert.x 异步 API 调用之间透明传播:

Context grpcCtx1 = Context.current();

vertx.executeBlocking(() -> {

  // 与 grpcCtx1 相同
  Context grpcCtx2 = Context.current();

  return doSomething();

}).onComplete(ar -> {

  // 与 grpcCtx1 和 grpcCtx2 相同
  Context grpcCtx3 = Context.current();

});

仅当当前的 Vert.x Context 绑定到 Vert.x HTTP 服务端请求时,gRPC 上下文才会在 Vert.x 异步 API 调用之间传播。

如果例如在非 Vert.x 线程上调用存根,或者从 Verticle 的 start 方法中调用存根,上下文则不会传播。

使用 Vert.x gRPC 上下文存储

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

pom.xml
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-grpcio-context-storage</artifactId>
  <version>5.1.10</version>
</dependency>
build.gradle
dependencies {
  compile 'io.vertx:vertx-grpcio-context-storage:5.1.10'
}