gRPC是谷歌开源的一款RPC框架,基于HTTP/2协议,使用protocol buffer作为接口定义语言(Interface Definition Language)和底层数据交换格式。gRPC最大的优点是能跨语言,因为使用和语言无关的protobuf来定义接口,并且使用标准的http协议来进行传输,能使不同语言的服务端和客户端互相兼容。
gRPC-java的github地址是https://github.com/grpc/grpc-java。它是gRPC的java语言实现。在它的源代码中已经包含了一个example,里面有最简单的gRPC作为rpc框架的demo。本文是基于它,探讨单独建立一个项目时,如何通过gRPC进行远程过程调用。
一.下载gRPC源码
可以直接下载github上的源码,也可以先fork到自己的仓库,再从自己的仓库下载。推荐第二种。
二.通过Intellij新建一个基于maven的项目grpc-demo
三.pom文件中加入如下内容:
UTF-8
1.19.0
3.6.1
3.6.1
1.7
1.7
io.grpc
grpc-bom
${grpc.version}
pom
import
io.grpc
grpc-netty-shaded
runtime
io.grpc
grpc-protobuf
io.grpc
grpc-stub
javax.annotation
javax.annotation-api
1.2
provided
io.grpc
grpc-testing
test
com.google.protobuf
protobuf-java-util
${protobuf.version}
junit
junit
4.12
test
org.mockito
mockito-core
2.25.1
test
kr.motd.maven
os-maven-plugin
1.5.0.Final
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:${protoc.version}:exe:${os.detected.classifier}
grpc-java
io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}
compile
compile-custom
org.apache.maven.plugins
maven-enforcer-plugin
1.4.1
enforce
enforce
以上内容是从grpc源代码的example的pom中拷贝出来的。
三.编辑proto文件
在src/main下新建文件夹proto,并且新建文件grpc-demo.proto。添加如下内容:
syntax = "proto3";
package GrpcDemo;
option java_package="com.wts.grpc.demo";
option java_outer_classname="GrpcDemoProto";
option java_multiple_files=true;
service Greeter {
rpc sayHello(HelloRequest) returns (HelloReply){}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
proto文件的作用是通过它来自动生成实现rpc调用的辅助代码。
简单说一下这个文件的内容:
syntax指定了使用的是protobuf3这个版本;
java_package是指定了生成的辅助类所在的package;
java_outer_classname是生成的Protobuf描述类的类名;
java_multiple_files是指定是否将生成的类放到不同的文件中,如果选择false,那么所有类都会存在一个文件中(以内部类的形式)
service定义了要生成的rpc接口。
msssage则是定义了rpc接口要传输的对象。
四.编译proto文件生成java类
在项目目录下执行maven的compile命令(可以直接在idea的右侧的maven选项卡中选择compile)。会生成以下文件:
有了这些类,就可以实现rpc调用,将它们拷贝到源代码目录中对应的包下面。
四.编辑server和client类:
server:
package com.wts.grpc.demo;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
import java.util.logging.Logger;
public class GrpcDemoServer {
private static final Logger logger = Logger.getLogger(GrpcDemoServer.class.getName());
private Server server;
private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
logger.info("Server started, listening on " + port);
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GrpcDemoServer.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 IOException, InterruptedException {
final GrpcDemoServer server = new GrpcDemoServer();
server.start();
server.blockUntilShutdown();
}
static 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();
}
}
}
client:
package com.wts.grpc.demo;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class GrpcDemoClient {
private static final Logger logger = Logger.getLogger(GrpcDemoClient.class.getName());
private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
public GrpcDemoClient(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}
GrpcDemoClient(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name) {
logger.info("Will try to greet " + name + " ...");
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Greeting: " + response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
GrpcDemoClient client = new GrpcDemoClient("localhost", 50051);
try {
String user = "world";
if (args.length > 0) {
user = args[0];
}
client.greet(user);
} finally {
client.shutdown();
}
}
}
这两个类的代码也是从grpc-java的example中拷贝过来的。
五.测试
执行GrpcDemoServer的main方法,控制台输出:
执行GrpcDemoClient的main方法,控制台输出
调用成功。
六.总结
本文根据grpc-java的源代码中的示例,新建项目实现了基于gRPC的远程调用。可以看到,因为需要跨语言,所以gRPC需要借助protobuf这种独立的接口定义语言。定义好之后,通过不同语言的插件,可以生成对应语言的辅助类,通过辅助类可以实现远程调用。gRPC的优点是跨语言,通过http2进行传输,扩展性强,有一定的前瞻性。确定是目前官方没有自己的线程池,没有负载均衡,也没有很成熟的服务治理方案,需要使用者自己实现。