概述
- Grpc是Google开发的一款高性能、跨语言的RPC框架,使用Google的数据序列化框架protobuf作为IDL(接口定义语言)跨语言的服务接口定义,以及数据序列化。在通信层面基于HTTP2.0实现,这是与大多数RPC框架基于TCP实现不一样的,主要原因是HTTP2.0在性能方面有了比较大的提升。除此之外,Grpc的设计的其中一个目的就是提供移动客户端与服务端的RPC调用,所以基于HTTP协议实现可以应对TCP层面会遇到的内网防火墙问题,因为大部分的防火墙会屏蔽掉除80和443之外的其他端口。
- 以下使用Java语言实现一个HelloWorld项目来展示Grpc和protobuf的相关使用方法。项目完整源码在个人Github:Github grpc-demo
1. 使用protobuf定义RPC服务接口与相关数据结构
- 使用protobuf文件来定义业务数据类型和RPC服务方法并编译。如下在hello_message.proto文件定义数据结构,在rpc_hello_service.proto文件中定义服务方法,实现如下:(关于protobuf的更多语言可以参考官方文档)
-
hello_message.proto:定义了请求数据结构HelloRequest,响应数据结构HelloResponse。
syntax = "proto3"; package com.yzxie.demo.java.grpc.proto; // 编译生成的Java文件对应的包和类名 option java_multiple_files = true; option java_package = "com.yzxie.demo.java.grpc.rpc"; option java_outer_classname = "HelloMessageProto"; // 请求数据结构 message HelloRequest { string userName = 1; } // 响应数据结构 message HelloResponse { string message = 1; }
-
rpc_hello_service.proto:声明了一个RpcHelloServer服务接口和一个sayHello方法。
syntax = "proto3"; package com.yzxie.demo.java.grpc.proto; // 编译生成的Java文件对应的包和类名 option java_multiple_files = true; option java_package = "com.yzxie.demo.java.grpc.rpc"; option java_outer_classname = "RpcHelloServiceProto"; import "com/yzxie/demo/java/grpc/proto/hello_message.proto"; // RPC接口 service RpcHelloService { // RPC方法声明 rpc sayHello(HelloRequest) returns (HelloResponse); }
-
接着需要编译为Java对应的类文件,可以使用命令来生成,不过一般会结合maven的编译插件来实现,这样可以在IDEA直接编译,如下为maven的插件配置:
<plugin> <groupId>org.xolstice.maven.plugins</groupId> <artifactId>protobuf-maven-plugin</artifactId> <version>0.5.0</version> <configuration> <protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin>
-
然后可以在IDEA的右边看到如下所示的编译选项,故直接执行即可编译出对应的Java类文件,最后打包成标准的jar被Grpc服务端和Grpc客户端引用即可。
2.实现RPC服务的方法和Grpc服务端
-
通常步骤1编译得到的接口文件为RpcHelloServiceGrpc,所以RPC服务方法的实现主要是通过继承RpcHelloServiceGrpc的静态内部类RpcHelloServiceImplBase并重写对应的方法来实现业务逻辑。
如下实现类RpcHelloServiceImpl继承于RpcHelloServiceGrpc.RpcHelloServiceImplBase并重写sayHello方法:
public class RpcHelloServiceImpl extends RpcHelloServiceGrpc.RpcHelloServiceImplBase { @Override public void sayHello(HelloRequest request, StreamObserver<HelloResponse> responseObserver) { HelloResponse response = HelloResponse.newBuilder() .setMessage("Hello, " + request.getUserName()) .build(); // 响应RPC客户端的RPC调用请求 responseObserver.onNext(response); responseObserver.onCompleted(); } }
-
Grpc服务端的核心实现如下:主要是在指定的端口监听Grpc客户端的RPC请求到来,然后进行处理。
@PostConstruct public void init() { // 指定监听端口和RPC服务的业务逻辑实现类rpcHelloService server = ServerBuilder.forPort(port).addService(rpcHelloService).build(); } // 开启在指定端口监听Grpc客户端的RPC方法调用请求 public void start() { try { server.start(); LOG.info("GrpcServer listen on {}", port); server.awaitTermination(); } catch (Exception e) { LOG.error("GrpcServer listen on {}", port, e); } // 关闭回调hook Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { LOG.info("GrpcServer shutdown {}", port); GrpcServer.this.stop(); } }); }
3.实现Grpc客户端
-
首先通过指定Grpc服务端的域名host和监听端口号port来创建Grpc客户端和Grpc服务端通信的channel,然后需要创建该RPC服务对应的服务端点(stub),如下:
// Grpc客户端channel private ManagedChannel channel; // RPC服务端点(stub) private RpcHelloServiceGrpc.RpcHelloServiceBlockingStub blockingStub; @PostConstruct public void init() { // 指定Grpc服务端的域名和端口 channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true).build(); blockingStub = RpcHelloServiceGrpc.newBlockingStub(channel); }
-
然后通过该RPC服务端点来对Grpc服务端发起对该RPC服务端点的RPC方法调用:
public String getHello(String userName) { try { HelloRequest request = HelloRequest.newBuilder().setUserName(userName).build(); // 通过RPC服务端点(stub)发起RPC方法调用 HelloResponse response = blockingStub.sayHello(request); return response.getMessage(); } catch (StatusRuntimeException e) { LOG.error("getHello {}", userName, e); } return ""; }