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;
}
Vert.x gRPC 模块
关于 gRPC 最权威的定义可以参考 Wikipedia:
gRPC 是由 Google 最初发起的开源高性能远程过程调用 (RPC) 框架。它基于 HTTP/2 协议进行高效传输,采用 Protocol Buffers (Protobuf) 作为服务接口描述语言,提供全套身份认证、双向流式通信、流量控制、背压绑定以及请求取消与超时处理机制。它能为众多编程语言自动生成跨平台的客户端与服务端强类型绑定。
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 格式来定义通信协议。
这是一个非常简单的示例,展示了单个请求、单个响应的模式。
编译 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 文件:
| 生成的文件 | 描述 | 详情 |
|---|---|---|
|
定义服务契约的基础接口,所有 RPC 方法均声明为抽象方法 |
客户端与服务端通用 |
|
高级客户端接口,提供用于调用服务的 Vert.x Future 风格方法 |
|
|
高级服务端接口,用于通过 Vert.x Future 风格处理器实现服务 |
|
|
低级客户端实现,提供对 gRPC 请求/响应流的直接访问 |
|
|
低级服务端实现,提供对 gRPC 请求/响应流的直接访问以进行处理 |
GreeterGrpcClient 和 GreeterGrpcService 类提供了更底层的 API,赋予你对流式传输和流量控制更大的掌控力;而 GreeterClient 和 GreeterService 提供了更高层、更符合 Vert.x 惯用法的 API,在大多数场景下使用更加简单方便。
|
此外,当启用 grpc-io=true 时,该插件还会生成:
| 生成的文件 | 描述 | 详情 |
|---|---|---|
|
兼容层,用于配合标准 |
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 -> {
// 发生异常错误
});
});
流式响应
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) 创建服务端。
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 协议不支持压缩。 |
消息级 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' }
符合惯用法(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());
});
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());
});
流式请求
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());
});
客户端负载均衡
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/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.0.12</version> </dependency> - build.gradle
-
dependencies { compile 'io.vertx:vertx-grpcio-server:5.0.12' }
服务桥接(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.0.12</version> </dependency> - build.gradle
-
dependencies { compile 'io.vertx:vertx-grpcio-client:5.0.12' }
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 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 It is not propagated if, for example, you invoke a stub on a non-Vert.x thread or from a verticle |
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.5.35</version>
</dependency>
-
Gradle (in your
build.gradlefile):
dependencies {
compile 'io.vertx:vertx-grpc-context-storage:4.5.35'
}