跳到主要内容
版本:dev

Java gRPC

Fory 可以为定义了服务的 Schema 生成 Java gRPC 服务配套代码。生成的服务代码使用标准的 grpc-java 通道、服务器、截止时间、状态码、拦截器和传输安全机制,而请求和响应对象则使用 Fory 而不是 protobuf 进行序列化。

当 RPC 两端均由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且你希望结合 gRPC 传输语义与 Fory 载荷编码时,请使用此模式。如果 API 必须由通用 protobuf 客户端、反射工具或要求 protobuf 消息字节的组件使用,请采用标准的 protobuf gRPC 代码生成方式。

有关 Scala 生成的 grpc-java 配套代码,请参阅 Scala gRPC 支持。有关 Kotlin 协程存根和服务实现基类,请参阅 Kotlin gRPC 支持

添加依赖

生成的 Java 服务文件依赖 grpc-java 进行编译。Fory Java 构件不会将 gRPC 作为硬依赖引入,因此请在应用构建中添加 grpc-java 依赖,并使其版本与服务技术栈中的其他组件保持一致。

Maven:

<dependencies>
<dependency>
<groupId>org.apache.fory</groupId>
<artifactId>fory-core</artifactId>
<version>${fory.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-api</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>

Gradle:

dependencies {
implementation("org.apache.fory:fory-core:$foryVersion")
implementation("io.grpc:grpc-api:$grpcVersion")
implementation("io.grpc:grpc-stub:$grpcVersion")
implementation("io.grpc:grpc-netty-shaded:$grpcVersion")
}

定义服务

服务定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers 的 rpc_service 定义。Fory IDL 服务如下所示:

package demo.greeter;

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

使用 --grpc 生成 Java 模型和 gRPC 配套代码:

foryc service.fdl --java_out=./generated/java --grpc

对于此 Schema,Java 生成器会生成:

文件用途
HelloRequest.java请求对应的 Fory 模型类型
HelloReply.java响应对应的 Fory 模型类型
GreeterForyModule.java生成类型的 Fory 注册模块
GreeterGrpc.javagrpc-java 服务实现基类、存根和编解码器

实现服务端

继承生成的 GreeterGrpc.GreeterImplBase 类,并将其注册到标准的 grpc-java Server

package demo.greeter;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

final class GreeterService extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(
HelloRequest request, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = new HelloReply();
reply.setReply("Hello, " + request.getName());
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}

public final class GreeterServer {
public static void main(String[] args) throws Exception {
Server server =
ServerBuilder.forPort(50051)
.addService(new GreeterService())
.build()
.start();
server.awaitTermination();
}
}

生成的代码会注册生成的请求和响应类型,因此服务实现不需要执行自定义序列化器注册。

创建客户端

通过普通的 grpc-java 通道使用生成的存根:

package demo.greeter;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

public final class GreeterClient {
public static void main(String[] args) {
ManagedChannel channel =
ManagedChannelBuilder.forAddress("localhost", 50051)
.usePlaintext()
.build();
try {
GreeterGrpc.GreeterBlockingStub stub =
GreeterGrpc.newBlockingStub(channel);

HelloRequest request = new HelloRequest();
request.setName("Fory");
HelloReply reply = stub.sayHello(request);
System.out.println(reply.getReply());
} finally {
channel.shutdownNow();
}
}
}

对于异步调用,请使用 GreeterGrpc.newStub(channel)。对于基于 Future 的单请求单响应调用,请使用 GreeterGrpc.newFutureStub(channel)

流式 RPC

Fory 服务定义可以使用相同的 gRPC 流式调用形式:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

生成的 Java 服务方法遵循 grpc-java 约定:

IDL 形式服务端方法形式客户端方法形式
rpc A (Req) returns (Res)void a(Req request, StreamObserver<Res> responses)阻塞、异步和 Future 单请求单响应存根
rpc A (Req) returns (stream Res)void a(Req request, StreamObserver<Res> responses)阻塞迭代器或异步观察者
rpc A (stream Req) returns (Res)StreamObserver<Req> a(StreamObserver<Res> responses)异步请求观察者
rpc A (stream Req) returns (stream Res)StreamObserver<Req> a(StreamObserver<Res> responses)异步请求观察者

服务端实现可以直接使用生成的流式方法形式:

package demo.greeter;

import io.grpc.stub.StreamObserver;
import java.util.ArrayList;
import java.util.List;

final class GreeterService extends GreeterGrpc.GreeterImplBase {
@Override
public void lotsOfReplies(
HelloRequest request, StreamObserver<HelloReply> responseObserver) {
HelloReply first = new HelloReply();
first.setReply("Hello, " + request.getName());
responseObserver.onNext(first);

HelloReply second = new HelloReply();
second.setReply("Welcome, " + request.getName());
responseObserver.onNext(second);
responseObserver.onCompleted();
}

@Override
public StreamObserver<HelloRequest> lotsOfGreetings(
StreamObserver<HelloReply> responseObserver) {
List<String> names = new ArrayList<>();
return new StreamObserver<>() {
@Override
public void onNext(HelloRequest request) {
names.add(request.getName());
}

@Override
public void onError(Throwable error) {
responseObserver.onError(error);
}

@Override
public void onCompleted() {
HelloReply reply = new HelloReply();
reply.setReply(String.join(", ", names));
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
};
}

@Override
public StreamObserver<HelloRequest> chat(
StreamObserver<HelloReply> responseObserver) {
return new StreamObserver<>() {
@Override
public void onNext(HelloRequest request) {
HelloReply reply = new HelloReply();
reply.setReply("Hello, " + request.getName());
responseObserver.onNext(reply);
}

@Override
public void onError(Throwable error) {
responseObserver.onError(error);
}

@Override
public void onCompleted() {
responseObserver.onCompleted();
}
};
}
}

生成的客户端会返回标准的 grpc-java 调用形式:

package demo.greeter;

import io.grpc.stub.StreamObserver;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

final class StreamingClient {
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private final GreeterGrpc.GreeterStub asyncStub;

StreamingClient(
GreeterGrpc.GreeterBlockingStub blockingStub,
GreeterGrpc.GreeterStub asyncStub) {
this.blockingStub = blockingStub;
this.asyncStub = asyncStub;
}

void run() throws InterruptedException {
Iterator<HelloReply> replies =
blockingStub.lotsOfReplies(newRequest("Fory"));
while (replies.hasNext()) {
System.out.println(replies.next().getReply());
}

CountDownLatch greetingsDone = new CountDownLatch(1);
StreamObserver<HelloRequest> greetings =
asyncStub.lotsOfGreetings(new StreamObserver<>() {
@Override
public void onNext(HelloReply reply) {
System.out.println(reply.getReply());
}

@Override
public void onError(Throwable error) {
greetingsDone.countDown();
}

@Override
public void onCompleted() {
greetingsDone.countDown();
}
});
greetings.onNext(newRequest("Ada"));
greetings.onNext(newRequest("Grace"));
greetings.onCompleted();
greetingsDone.await(5, TimeUnit.SECONDS);

CountDownLatch chatDone = new CountDownLatch(1);
StreamObserver<HelloRequest> chat =
asyncStub.chat(new StreamObserver<>() {
@Override
public void onNext(HelloReply reply) {
System.out.println(reply.getReply());
}

@Override
public void onError(Throwable error) {
chatDone.countDown();
}

@Override
public void onCompleted() {
chatDone.countDown();
}
});
chat.onNext(newRequest("Fory"));
chat.onCompleted();
chatDone.await(5, TimeUnit.SECONDS);
}

private static HelloRequest newRequest(String name) {
HelloRequest request = new HelloRequest();
request.setName(name);
return request;
}
}

生成的描述符会为 gRPC 路径保留 IDL 中准确的服务名和方法名。

gRPC 运行时行为

生成的服务代码只会替换请求和响应的序列化方式。所有常规的 gRPC 运维功能仍由 grpc-java 负责:

  • 截止时间和取消
  • TLS 和身份认证
  • 名称解析和负载均衡
  • 客户端和服务端拦截器
  • 状态码和元数据
  • 通道池化和生命周期管理

故障排查

缺少 io.grpc 或 Guava 类

请添加上文所示的 grpc-java 依赖。生成的 Fory 服务文件会导入 grpc-java API,但 Fory Java 构件有意不依赖 gRPC。

UNIMPLEMENTED

请确认已通过 ServerBuilder.addService(...) 注册生成的服务实现,并且客户端和服务端由相同的包名、服务名和方法名生成。

Protobuf 客户端无法解码服务

Fory gRPC 配套代码不会对消息使用 protobuf 编码格式。请为 Fory 生成的服务使用 Fory 生成的客户端,或者为通用 protobuf 客户端提供单独的 protobuf 服务端点。