HTTP/2和Protobuf的组合——gRPC
gRPC源于被称为Stubby的Google内部项目,Google内部大量使用Stubby进行服务间通信。作为gRPC的前身,Stubby大量依赖Google的其他基础服务,所以不太方便开放出来给社区使用。随着HTTP/2的逐步成熟,2015年初Google开源了gRPC框架。截至2017年12月,gRPC已经发布了1.7.3版本,并且被CNCF(云原生计算基金会)所收录。gRPC在ETCD/Kubernetes上得到了大量使用。
gRPC是基于HTTP/2设计的,因此也继承了HTTP/2相应的诸多特性,这些特性使得其在移动设备上表现得更好,更节省空间、更省电。gRPC目前提供的C、Java和Go语言版本分别是grpc、grpc-java、grpc-go,其中C版本支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#。
说了这么多,gRPC到底能够给我们提供哪些优势呢?
· gRPC默认使用Protobuf进行序列化和反序列化,而Protobuf是已经被证明的高效的序列化方式,因此,gRPC的序列化性能是可以得到保障的。
· gRPC默认采用HTTP/2进行传输。HTTP/2支持流(streaming),在批量发送数据的场景下使用流可以显著提升性能——服务端和客户端在接收数据的时候,可以不必等所有的消息全收到后才开始响应,而是在接收到第一条消息的时候就可以及时响应。例如,客户端向服务端发送了一千条update消息,服务端不必等到所有消息接收完毕才开始处理,而是一边接收一边处理。这显然比以前的类HTTP 1.1的方式提供的响应更快、性能更优。gRPC的流可以分为三类:客户端流式发送、服务器流式返回,以及客户端/服务器同时流式处理,也就是单向流和双向流。在我写这本书的时候,Dubbo 3.0正在酝酿中,其中一个显著的变化是新版本将以streaming为内核,而不再是2.0时代的RPC,目的是去掉一切阻塞。
· 基于HTTP/2协议很容易实现负载均衡及流控的方案,可以利用Header做很多事情。
同时,gRPC也不是完美的。相比于非IDL描述的RPC(例如Hession、Kyro)方式,定义proto文件是一个比较麻烦的事情,而且需要额外安装客户端、插件等。另外HTTP/2相比于基于TCP的通信协议,性能上也有显著的差距。
下面通过一个简单的例子来理解一下gRPC的使用方式。假设我们要开发电商中的产品服务,通过id获取产品的信息,主要步骤及实现代码如下。
(1)定义proto文件。
syntax = "proto3";//声明支持的版本是proto3 option java_multiple_files = true;//以外部类模式生成 option java_package = "com.cloudnative.grpc";//声明包名,可选 option java_outer_classname="ProductProtos";//声明类名,可选 message ProductRequest{ int32 id = 1; } message ProductResponse { int32 id = 1; string name = 2; string price = 3; } service ProductService{ rpc GetProduct(ProductRequest) returns(ProductResponse); }
(2)生成相关类。可以采用Protobuf中介绍的方法,在命令行执行protoc生成相关代码。如果使用Maven,则可以通过Maven插件实现。
<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.0</version> <configuration> <protocArtifact>com.google.protobuf:3.5.1:exe:${os.detected.classifier} </protocArtifact> <pluginId>grpc-java</pluginId> <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.8.0:exe:${os.detected. classifier}</pluginArtifact> <protocExecutable>/usr/local/bin/protoc</protocExecutable> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>compile-custom</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
在pom.xml中配置,并且执行mvn compile命令会在target/generated-sources中生成相关类,可以将相关类移到src /main/java目录下备用。
(3)服务端实现代码。一是,实现ProductService。
public class GRPCServer{ private static final Logger logger = Logger.getLogger(GRPCServer.class.getName()); private final int port; private final Server server; public GRPCServer(int port){ this.port=port; this.server = ServerBuilder.forPort(port) .addService(new ProductService()) .build(); } /** Start serving requests. */ public void start() throws IOException { this.server.start(); logger.info("Server started, listening on " + port); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { // Use stderr here since the logger may has been reset by its JVM shutdown hook. logger.info("*** shutting down gRPC server since JVM is shutting down"); GRPCServer.this.stop(); logger.info("*** server shut down"); } }); } /** Stop serving requests and shutdown resources. */ public void stop() { if (server != null) { server.shutdown(); } } /** * Await termination on the main thread since the grpc library uses daemon threads. */ private void blockUntilShutdown() throws InterruptedException { if (server != null) { server.awaitTermination(); } } /** * Main method. This comment makes the linter happy. */ public static void main(String[] args) throws Exception { GRPCServer server = new GRPCServer(8888); server.start(); server.blockUntilShutdown(); } }
(4)客户端实现代码。
public class GRPCClient { private static final Logger logger = Logger.getLogger(GRPCServer.class.getName()); public static void main(String[] args) { ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8888) .usePlaintext(true) .build(); ProductServiceGrpc.ProductServiceBlockingStub blockStub=ProductServiceGrpc.newBlockingStub(channel); ProductResponse response=blockStub.getProduct(ProductRequest.newBuilder().setId(111).build()); logger.info(response.getName()); response=blockStub.getProduct(ProductRequest.newBuilder().setId(2).build()); logger.info(response.getName()); } }
上面是一个简单的实现,关于流式RPC可以参考官方的例子。
本文节选自《持续演进的Cloud Native:云原生架构下微服务最佳实践》一书,王启军 著。电子工业出版社出版。
public class ProductService extends ProductServiceGrpc.ProductServiceImplBase{ private static final Logger logger = Logger.getLogger(GRPCServer.class.getName()); @Override public void getProduct(ProductRequest request, StreamObserver<ProductResponse> responseObserver) { logger.info("接收到客户端的信息:"+request.getId()); ProductResponse responsed; if (111==request.getId()){ responsed=ProductResponse.newBuilder().setId(111).setName ("dddd").build(); }else { responsed=ProductResponse.newBuilder().setId(0).setName("---").build(); } responseObserver.onNext(responsed); responseObserver.onCompleted(); } }
二是,实现server代码。