前言
本文将介绍 gRPC、Protocol Buffers 的概念,同时会给出 Protocol Buffers 代码生成器的使用教程,还有编写第一个基于 gRPC 的服务提供者与服务消费者的示例程序。
相关站点
gRPC 简介
gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java、Go 语言版本,分别是:grpc、grpc-java、grpc-go,其中 C 版本支持 C、C++、Node.js、Python、Ruby、Objective-C、PHP、C#。gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。值得说明的是,gRPC 客户端和服务端可以在多种环境中运行和交互,支持用任何 gRPC 支持的语言来编写,所以可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。
使用 Protocol Buffers
gRPC 默认使用 Protocol Buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。当使用 proto files 创建 gRPC 服务,用 Protocol Buffers 消息类型来定义方法参数和返回类型。尽管 Protocol Buffers 已经存在了一段时间,官方的示例代码种使用了一种名叫 proto3 的新风格的 Protocol Buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。当前针对 Java 和 C++ 发布了 beta 版本,针对 JavaNano(即 Android Java)发布 alpha 版本,在 Protocol Buffers Github 源码库里有 Ruby 支持, 在 Github 源码库里还有针对 Go 语言的生成器, 对更多语言的支持正在开发中。虽然可以使用 proto2 (当前默认的 Protocol Buffers 版本), 通常建议在 gRPC 里使用 proto3,因为这样可以使用 gRPC 支持全部范围的的语言,并且能避免 proto2 客户端与 proto3 服务端交互时出现的兼容性问题,反之亦然。
本地编译安装 Protocol Buffers(可选)
参考自 gRPC-Java、Protobuf 编译构建的官方教程 ,一般情况下不需要构建 gRPC-Java,只有在对 gRPC-Java 的源码进行了更改或测试使用 gRPC-Java 库的非发布版本(例如 master 分支)时才需要构建。若本地安装了 Protobuf,则可以直接通过命令的方式调用 Protobuf 的代码生成器,无需再依赖额外的 IDE 插件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 CentOS Linux release 7.6.1810 (Core) Linux develop 3.10.0-957.21.3.el7.x86_64 # git clone https://github.com/google/protobuf.git# cd protobuf# git checkout v3.7.1# git branch -v # make -j 8# make install# sh -c 'echo /usr/local/lib >> /etc/ld.so.conf' # ldconfig # protoc --version # protoc -I =$SRC_DIR --java_out =$DST_DIR $SRC_DIR /addressbook.proto
Eclipse 项目中添加 Protobuf 自动生成代码的 Maven 插件与 Protobuf 依赖
Protobuf 的原型文件和一些适合的插件,默认放在 src/main/proto 和 src/test/proto 目录中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-netty-shaded</artifactId > <version > 1.21.0</version > </dependency > <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-protobuf</artifactId > <version > 1.21.0</version > </dependency > <dependency > <groupId > io.grpc</groupId > <artifactId > grpc-stub</artifactId > <version > 1.21.0</version > </dependency > <build > <extensions > <extension > <groupId > kr.motd.maven</groupId > <artifactId > os-maven-plugin</artifactId > <version > 1.5.0.Final</version > </extension > </extensions > <plugins > <plugin > <groupId > org.xolstice.maven.plugins</groupId > <artifactId > protobuf-maven-plugin</artifactId > <version > 0.5.1</version > <configuration > <protocArtifact > com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact > <pluginId > grpc-java</pluginId > <pluginArtifact > io.grpc:protoc-gen-grpc-java:1.21.0:exe:${os.detected.classifier}</pluginArtifact > </configuration > <executions > <execution > <goals > <goal > compile</goal > <goal > compile-custom</goal > </goals > </execution > </executions > </plugin > </plugins > </build >
往 Gradle 构建的项目添加 Protobuf 自动生成代码的插件与 Protobuf 依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 plugins { id 'com.google.protobuf' version '0.8.8' } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.7.1" } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.21.0' } } generateProtoTasks { all()*.plugins { grpc {} } } } dependencies { compile 'io.grpc:grpc-stub:1.21.0' compile 'io.grpc:grpc-protobuf:1.21.0' compile 'io.grpc:grpc-netty-shaded:1.21.0' testCompile group : 'junit' , name: 'junit' , version: '4.12' }
编写 Proto 文件(定义服务),执行编译后自动生成 Java 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 grpc-demo-provider/ ├── build.gradle └── src ├── main │ ├── java │ ├── proto │ │ └── helloworld.proto │ └── resources └── test ├── java ├── proto └── resources # cd grpc-demo-provider# mkdir -p src/main/proto# vim src/main/proto/helloworld.protosyntax = "proto3" ; option java_multiple_files = true ; option java_package = "com.grpc.demo.generate" ; 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; } # gradle clean build# tree build/generated/source /protomain ├── grpc │ └── com │ └── grpc │ └── demo │ └── generate │ └── GreeterGrpc.java └── java └── com └── grpc └── demo └── generate ├── HelloReply.java ├── HelloReplyOrBuilder.java ├── HelloRequest.java ├── HelloRequestOrBuilder.java └── HelloWorldProto.java
Gradle 指定 Protobuf 代码自动生成的目录位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 protobuf { generatedFilesBaseDir = "src" } protobuf { generateProtoTasks { all()*.plugins { grpc { outputSubDir = 'java' } } } } protobuf { protoc { artifact = "com.google.protobuf:protoc:3.7.1" } plugins { grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.21.0' } } generateProtoTasks { all()*.plugins { grpc { outputSubDir = 'java' } } } generatedFilesBaseDir = 'src' }
RPC 服务提供者的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 package com.grpc.demo.provider.service;import com.grpc.demo.generate.GreeterGrpc;import com.grpc.demo.generate.HelloReply;import com.grpc.demo.generate.HelloRequest;import io.grpc.Server;import io.grpc.ServerBuilder;import io.grpc.stub.StreamObserver;import java.io.IOException;import java.util.logging.Logger;public class HelloWorldProvider { private Server server; private static final Logger logger = Logger.getLogger(HelloWorldProvider.class.getName()); 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" ); HelloWorldProvider.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 HelloWorldProvider server = new HelloWorldProvider(); server.start(); server.blockUntilShutdown(); } static class GreeterImpl extends GreeterGrpc .GreeterImplBase { @Override public void sayHello (HelloRequest req, StreamObserver<HelloReply> responseObserver) { HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build(); responseObserver.onNext(reply); responseObserver.onCompleted(); } } }
RPC 服务消费者的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 package com.grpc.demo.consumer.service;import com.grpc.demo.generate.GreeterGrpc;import com.grpc.demo.generate.HelloReply;import com.grpc.demo.generate.HelloRequest;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 HelloWorldConsumer { private final ManagedChannel channel; private final GreeterGrpc.GreeterBlockingStub blockingStub; private static final Logger logger = Logger.getLogger(HelloWorldConsumer.class.getName()); public HelloWorldConsumer (String host, int port) { this (ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build()); } HelloWorldConsumer(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 Exception { HelloWorldConsumer client = new HelloWorldConsumer("localhost" , 50051 ); try { String user = "World" ; client.greet(user); } finally { client.shutdown(); } } }
先后启动 Provider、Consumer 应用,最终输出的日志信息如下图所示
Provider 应用的日志信息:
Consumer 应用的日志信息: