这里为了探索grpc的使用方法,简单的假设工程有服务提供方和服务调用方,并且服务提供方简单的暴露一个SayHello的接口给服务调用方消费。项目划分如下
core-knowledge-rpc- 定义协议明细,接口实现
core-knowledge-rpc-callee- 服务提供方
core-knowledge-rpc-caller- 服务消费方
1 定义协议明细
core-knowlege-rpc负责定义协议明细和接口实现,工程目录结构如下
在工程中创建源文件目录src/main/proto目录存放.proto协议文件。
定义协议
这里定义了一个helloworld.proto文件,内容如下
syntax="proto3";
option java_multiple_files = true;
option java_package = "org.core.knowledge.delivery.grpc";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
package helloworld;
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
文件第一句注明文件使用proto3的语法。协议中定义了一个Greeter服务,暴露SayHello接口,接收HelloRequest类型的输入,返回HelloReply类型的输出。HelloRequest类型携带了一个name的字符串信息;HelloReply类型返回一个message的字符串。
引入grpc依赖
这里grpc依赖netty网络通讯,pom依赖如下
UTF-8
1.25.0
junit
junit
4.10
test
io.grpc
grpc-netty-shaded
${grpc.version}
io.grpc
grpc-protobuf
${grpc.version}
io.grpc
grpc-stub
${grpc.version}
kr.motd.maven
os-maven-plugin
1.6.2
org.apache.maven.plugins
maven-compiler-plugin
1.8
1.8
UTF8
org.xolstice.maven.plugins
protobuf-maven-plugin
0.6.1
com.google.protobuf:protoc:3.10.0:exe:${os.detected.classifier}
grpc-java
io.grpc:protoc-gen-grpc-java:1.25.0:exe:${os.detected.classifier}
compile
compile-custom
这里引入grpc的版本为1.25.0,依赖的netty打包到了grpc-netty-shaded库中。接着,执行mvn compile命令生成服务源码。在工程目录结构中,target/generated-sources/protobuf/java和target/generated-sources/protobuf/grpc-java目录即为自动protoc工具自动生成的服务源文件。
实现Greeter接口
这里简单的将接收到的name字符串之前增加hello字符串返回,实现如下
GreeterImpl.java
public class GreeterImpl extends GreeterGrpc.GreeterImplBase{
@Override
public void sayHello(HelloRequest request, StreamObserver responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage("Hello "+request.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
2 服务提供方实现
core-knowledge-rpc-callee是服务提供方,依赖grpc将Greeter服务暴露到网络中。服务提供方主要依赖grpc库的api接口启动一个服务端ServerSocket,同时将服务实现GreeterImpl注册到grpc。详细实现如下
HelloWorldCallee.java
public class HelloWorldCallee {
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl()).build().start();
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldCallee.this.stop();
System.err.println("*** server shut down");
}
});
}
private void stop() {
if(server != null) {
server.shutdown();
}
}
private void blockUntilShutdown() throws InterruptedException {
if(server != null) {
server.awaitTermination();
}
}
public static void main(String[] args) throws InterruptedException, IOException {
HelloWorldCallee server = new HelloWorldCallee();
server.start();
server.blockUntilShutdown();
}
}
grpc提供ServerBuilder类构件Server并注册服务。当然,需要将服务实现引入到项目中,
${project.groupId}
core-knowledge-rpc
${project.version}
groupId和version值与服务提供方相同。
3 服务消费方实现
服务消费方只需要借助grpc的ManagedChannelBuilder.build()构件网络连接通道ManagedChannel;然后使用ManagedChannel构建GreeterBlockingStub实例即可调用远程服务sayHello方法。实现代码如下
public class HelloWorldCaller {
private ManagedChannel channel;
private GreeterGrpc.GreeterBlockingStub blockingStub;
public HelloWorldCaller(String host,int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext().build());
}
HelloWorldCaller(ManagedChannel channel){
this.channel = channel;
this.blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
this.channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name) {
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = this.blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
System.err.println(e.getMessage());
return ;
}
System.out.println("Greeting: "+response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
HelloWorldCaller caller = new HelloWorldCaller("localhost", 50051);
@SuppressWarnings("resource")
Scanner scanner = new Scanner(System.in);
try {
for(;;) {
System.out.print("please input message:");
String str = scanner.nextLine();
if("quit".equals(str)) break;
caller.greet(str);
}
}finally {
caller.shutdown();
}
}
}
main函数中,获取标准输入流中输入的字符串,作为远程服务的输入参数实现远程调用。调用结果如下
服务输入日志如下
16:37:33.332 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] INBOUND HEADERS: streamId=7 headers=GrpcHttp2RequestHeaders[:path: /helloworld.Greeter/SayHello, :authority: localhost:50051, :method: POST, :scheme: http, te: trailers, content-type: application/grpc, user-agent: grpc-java-netty/1.25.0, grpc-accept-encoding: gzip] streamDependency=0 weight=16 exclusive=false padding=0 endStream=false
16:37:33.333 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] INBOUND DATA: streamId=7 padding=0 endStream=true length=19 bytes=000000000e0a0c41726520796f75206f6b203f
16:37:33.335 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] OUTBOUND HEADERS: streamId=7 headers=GrpcHttp2OutboundHeaders[:status: 200, content-type: application/grpc, grpc-encoding: identity, grpc-accept-encoding: gzip] streamDependency=0 weight=16 exclusive=false padding=0 endStream=false
16:37:33.340 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] OUTBOUND DATA: streamId=7 padding=0 endStream=false length=25 bytes=00000000140a1248656c6c6f2041726520796f75206f6b203f
16:37:33.341 [grpc-nio-worker-ELG-3-1] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyServerHandler - [id: 0xdab71016, L:/127.0.0.1:50051 - R:/127.0.0.1:62749] OUTBOUND HEADERS: streamId=7 headers=GrpcHttp2OutboundHeaders[grpc-status: 0] streamDependency=0 weight=16 exclusive=false padding=0 endStream=true
上面的示例显然: grpc很方便使用。可能你会问: 市面上已经存在很多的RPC框架,诸如JDK提供的RMI,基于HTTP协议二进制传输的Hessian,基于HTTP协议xml传输的Burlap及国产基于TCP协议的dubbo等,为毛还要使用grpc?RMI实现冗杂,Hessian和Burlap效率略低,dubbo加入了太多的业务属性不够单纯,grpc够单纯(连与spring集成的库都没有)够高效(依赖netty4+protobuffer)。选择grpc,为了代码优雅,你也就选择一片技术略高地。