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 官方文档中文版
定义
- 什么是gRPC?
在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。
2 protocol buffers
gRPC 默认使用 protocol buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。正如你将在下方例子里所看到的,你用 proto files 创建 gRPC 服务,用 protocol buffers 消息类型来定义方法参数和返回类型。
protoc-Java插件
3.HTTP/2 特性
- 多路复用 在 HTTP/1.1 协议中浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞,而在HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。
因此 HTTP/2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。 - 二进制帧 HTTP/2 传输的数据是二进制的。相比 HTTP/1.1 的纯文本数据,二进制数据一个显而易见的好处是:更小的传输体积。这就意味着更低的负载。
二进制的帧也更易于解析而且不易出错,纯文本帧在解析的时候还要考虑处理空格、大小写、空行和换行等问题,而二进制帧就不存在这个问题。 - 首部压缩 HTTP是无状态协议。意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。
因为http2没有改变这个范式,所以它也需要这样(携带所有细节),因此 HTTP 请求的头部需要包含用于标识身份的数据比如 cookies,而这些数据的量也在随着时间增长。每一个请求的头部都包含这些大量的重复数据,无疑是一种很大的负担。
对请求头部进行压缩,将会大大减轻这种负担,尤其对移动端来说,性能提高非常明显。HTTP/2 使用的压缩方式是 HPACK。
HTTP2.0在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次。 - 服务器推送 服务端会判断客户端还要请求其他的什么资源,然后一同把这些资源都发送给客户端,即便客户端还没有明确表示它需要这些资源。
客户端可以选择把额外的资源放入缓存中(所以这个特点也叫 Cache push),也可以选择发送一个 RST_STREAM frame 拒绝任何它不想要的资源。 - 主动重置连接 HTTP/2 引入了一个 RST_STREAM frame 来让客户端在已有的连接中发送重置请求,从而中断或者放弃响应。
当浏览器进行页面跳转或者用户取消下载时,它可以防止建立新连接,避免浪费所有带宽。
代码块
1、写helloworld.proto文件
syntax = "proto3";
option java_multiple_files = true;
option java_package = "demo";
option java_outer_classname = "HelloWorldProto";
//option java_generic_services = true; // 可以生成rpc接口
package helloworld;
service GreetHelloWorld {
rpc SayHello (HelloWorldRequest) returns (HelloWorldResponse);
}
message HelloWorldRequest {
string name = 1;
}
message HelloWorldResponse {
string message = 1;
}
注:
proto文件编译命令
1、在protoc编辑器的bin目录下增加protoc-Java插件,实例是把proto文件放在bin目录下实践的
2、cmd命令行下操作
3、先编译生成model,命令protoc –java_out=D:\workspace\gPRC-RPC\src\main\java ./helloworld.proto
4、在编译生成service,命令protoc –plugin=protoc-gen-grpc-java=E:/develop/gRPC/protoc-3.0.0-win32/bin/protoc-gen-grpc-java-1.10.0-windows-x86_64.exe –grpc-java_out=D:\workspace\gPRC-RPC\src\main\java ./helloworld.proto
5、建议使用proto3进行文件编写
2、生成文件目录列表
3、服务端实现
package demo.end;
import io.grpc.Server;
import java.io.IOException;
import io.grpc.ServerBuilder;
import demo.HelloWorldRequest;
import demo.HelloWorldResponse;
import io.grpc.BindableService;
import io.grpc.stub.StreamObserver;
import demo.GreetHelloWorldGrpc.GreetHelloWorldImplBase;
public class HelloWorldServer {
private int port = 8851;
private Server server;
private void start() throws IOException {
server = ServerBuilder.forPort(port)
.addService((BindableService) new GreeterHelloWorldImpl())
.build()
.start();
System.out.println("service start...");
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
HelloWorldServer.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 HelloWorldServer server = new HelloWorldServer();
server.start();
server.blockUntilShutdown();
}
// 实现 定义一个实现服务接口的类
private class GreeterHelloWorldImpl extends GreetHelloWorldImplBase {
public void sayHello(HelloWorldRequest req, StreamObserver<HelloWorldResponse> responseObserver) {
// 具体其他丰富的业务实现代码
System.err.println("service:" + req.getName());
HelloWorldResponse reply = HelloWorldResponse.newBuilder().setMessage(("Hello: " + req.getName())).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
4、客户端实现
package demo.end;
import java.util.concurrent.TimeUnit;
import demo.GreetHelloWorldGrpc;
import demo.GreetHelloWorldGrpc.GreetHelloWorldBlockingStub;
import demo.HelloWorldRequest;
import demo.HelloWorldResponse;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
public class HelloWorldClient {
private final ManagedChannel channel;
private final GreetHelloWorldBlockingStub blockingStub;
public HelloWorldClient(String host,int port){
channel = ManagedChannelBuilder.forAddress(host,port)
.usePlaintext(true)
.build();
blockingStub = GreetHelloWorldGrpc.newBlockingStub(channel);
}
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
public void greet(String name){
HelloWorldRequest request = HelloWorldRequest.newBuilder().setName(name).build();
HelloWorldResponse response = blockingStub.sayHello(request);
System.out.println(response.getMessage());
}
public static void main(String[] args) throws InterruptedException {
HelloWorldClient client = new HelloWorldClient("127.0.0.1", 8851);
for(int i=0;i<5;i++){
client.greet("world:"+i);
}
}
}
5、pom.xml配置文件
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>gRPC</groupId>
<artifactId>gPRC-RPC</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.10.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.10.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.1.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:protoc:3.6.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.10.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
6、运行结果展示
gRPC优缺点
优点:
1)protobuf二进制消息,性能好/效率高(空间和时间效率都很不错)
2)proto文件生成目标代码,简单易用
3)序列化反序列化直接对应程序中的数据类,不需要解析后在进行映射(XML,JSON都是这种方式)
4)支持向前兼容(新加字段采用默认值)和向后兼容(忽略新加字段),简化升级
5)支持多种语言(可以把proto文件看做IDL文件)
6)Netty等一些框架集成
缺点:
1)GRPC尚未提供连接池,需要自行实现
2)尚未提供“服务发现”、“负载均衡”机制
3)因为基于HTTP2,绝大部多数HTTP Server、Nginx都尚不支持,即Nginx不能将GRPC请求作为HTTP请求来负载均衡,而是作为普通的TCP请求。(nginx1.9版本已支持)
4) Protobuf二进制可读性差
5)默认不具备动态特性(可以通过动态定义生成消息类型或者动态编译支持)
参考资料
- protobuf语法知识 https://blog.csdn.net/shensky711/article/details/69696392
- Google Protocol Buffer 的使用和原理 https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html