gRPC 是 Google 开源的基于 Protobuf 和 Http2.0 协议的通信框架,底层由netty提供。之前也简单的介绍过 HTTP/2 重要特性,gRPC提供四种模式:unary,client streaming,server streaming 以及 bidirectional streaming,对于底层 HTTP/2 来说,它们都是 stream,并且仍然是一套 request + response 模型。
采用gRPC作为底层通讯的开源产品也比较多,比如: Tensorflow、CoreOS、Tidb。
gRPC 特色之处在于它的客户端是多路复用的线程安全的,可以拿过来直接使用,而且多路复用的客户端在效率上有明显优势。
插播句
gRPC 出来的时间要比 Thrift (Facebook )要晚,Google 自然是知道 gRPC 要比 Thrift 相比有明显的优势所以才敢放出来开源的,在开源影响力上Google 一直很强势
Protobuf
gRPC 的 service 接口是基于 protobuf 定义的,我们可以非常方便的将 service 与 HTTP/2 关联起来。
思考:我们知道Protobuf 协议仅仅定义了单个消息的序列化格式,HTTP/2模式下该如何protobuf集成呢?
难道我们需要自定义头部的长度和消息的类名称来区分消息边界?
Protobuf 将消息序列化后,内容放在 HTTP2.0 的 Data 帧的 Payload 字段里,消息的长度在 Data 帧的 Length 字段里,消息的类名称放在 Headers 帧的 Path 头信息中。我们需要通过头部字段的 Path 来确定具体消息的解码器来反序列化 Data 帧的 Payload 内容,而这些工作 gRPC 都已经封装好了。
- Path : /Service-Name/{method name}
- Service-Name : ?( {proto package name} "." ) {service name}
- Message-Type : {fully qualified proto message name}
- Content-Type : "application/grpc+proto"
其实request-response的奥秘都在这张图中,下面将展开讲
Request
request 通常包含:
Call-Definition 里面包括 Method(其实就是用的 HTTP/2 的 POST),Content-Type 等。
Custom-Metadata 则是应用层自定义的任意 key-value,key 不建议使用 grpc- 开头,因为这是为 gRPC 后续自己保留的。
Compressed flag 用来表示改 message 是否压缩,如果为 1,表示该 message 采用了压缩,而压缩算啊定义在 header 里面的 Message-Encoding 里面。
思考:Request-Header何时算响应结束呢?
上图也有答案,Headers Frame中带上了END_HEADERS 表示已经是头信息的最后一个帧
Response
主要包含:
HTTP-Status 就是通常的 HTTP 200,301,400 这些不再解释
Status 也就是 gRPC 的 status
Status-Message 则是 gRPC 的 message,采用了 Percent-Encoded 的编码方式,具体参考这里
思考:response何时算响应结束呢?
从上图就可以找到答案,最后收到的 Headers frame 里面,带上了 Trailers,并且DATA frame有 END_STREAM 这个 flag,那么就意味着 response结束!
接下来将带你从实操中理解gRPC的一些抽象的概念,可下载完整的源码。
os-maven-plugin
它主要是用于根据不同操作系统提供不同的变量(protoc版本信息)
[INFO] os.detected.name: windows
[INFO] os.detected.arch: x86_64
[INFO] os.detected.version: 6.1
[INFO] os.detected.version.major: 6
[INFO] os.detected.version.minor: 1
[INFO] os.detected.classifier: windows-x86_64
maven-dependency-plugin
自动下载protoc(可执行程序)到本地目录
protobuf-maven-plugin
针对*.proto文件生成java文件(主要是contract,dto)
服务提供方
gRPC 的一个特色之处在于提供了 Streaming 模式,有了 Streaming 模式,客户端可以将一连串的请求连续发送到服务器,服务器也可以将一连串连续的响应回复给客户端。Streaming 就是双工对话,它允许一个人同时说又同时听,如果你对rxjava有一定的了解的话,我擦,是不是有种熟悉的感觉!
public class OrderServiceProvider extends OrderServiceGrpc.OrderServiceImplBase{
@Override
public void getOrder(OrderId request, StreamObserver responseObserver) {
responseObserver.onNext(OrderDTO.newBuilder().setName("test").build());
responseObserver.onCompleted();
}
@Override
public void addOrder(OrderDTO request, StreamObserver responseObserver) {
responseObserver.onNext(OrderId.newBuilder().setId(9527).build());
responseObserver.onCompleted();
}
@Override
public void queryOrder(OrderDTO request, StreamObserver responseObserver) {
responseObserver.onNext(OrderId.newBuilder().setId(9527).build());
responseObserver.onNext(OrderId.newBuilder().setId(9528).build());
responseObserver.onCompleted();
}
}
客户消费端
gRPC提供了丰富的Contract(客户端Sevice协议)的实现方式,google的解决方案显然很棒,不像dubbo比较凑合。
回顾下dubbo
在2.7版本之前:
- 将同步接口声明成
async=true,比如
- 通过上下文类获取 future,比如:Future
fooFuture = RpcContext.getContext().getFuture(); 问题:不太符合异步编程的习惯,如果同时进行多个异步调用,使用不当很容易造成上下文污染,jdk原生Future 并不支持 callback 的调用方式。
在2.7.1(孵化中的正式版本)中改造:显式声明异步接口使用了JDK1.8 的CompletableFuture
//声明 public interface AsyncService { String sayHello(String name); default CompletableFuture
sayHiAsync(String name) { return CompletableFuture.completedFuture(sayHello(name)); } } //调用 CompletableFuture future = asyncService.sayHiAsync("Han MeiMei"); future.whenComplete((retValue, exception) -> { if (exception == null) { System.out.println(retValue); } else { exception.printStackTrace(); } }); 而这个异步更多是客户端异步,虽然2.7 新增了服务端异步的支持,然而个人认为服务端异步的特性较为鸡肋
but,dubbo在3.0.0-SNAPSHOT中已经有了彻底的改变,支持响应式之RSocket
提供了3种Contract:
public class OrderClient {
private String host = "localhost";
private int serverPort = 9527;
private ManagedChannel managedChannel;
private OrderServiceGrpc.OrderServiceBlockingStub orderConsumer;
private OrderServiceGrpc.OrderServiceFutureStub orderAsyncConsumer;
private OrderServiceGrpc.OrderServiceStub orderStreamConsumer;
private OrderId orderDTO = OrderId.newBuilder().setId(123).build();
@Before
public void init() {
managedChannel = ManagedChannelBuilder.forAddress(host, serverPort)
//Channels are secure by default (via SSL/TLS)
.usePlaintext().
build();
orderConsumer = OrderServiceGrpc.newBlockingStub(managedChannel);
orderAsyncConsumer = OrderServiceGrpc.newFutureStub(managedChannel);
orderStreamConsumer = OrderServiceGrpc.newStub(managedChannel);
}
@Test
public void getOrder() {
OrderDTO result = orderConsumer.getOrder(orderDTO);
System.out.println(result);
}
@Test
public void asyncGetOrder() {
ListenableFuture result = orderAsyncConsumer.getOrder(orderDTO);
System.out.println(result);
}
@Test
public void streamGetOrder() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
StreamObserver responseObserver = new StreamObserver() {
@Override
public void onNext(OrderDTO value) {
System.out.println("get result :" + value);
}
@Override
public void onError(Throwable t) {
Status status = Status.fromThrowable(t);
System.out.println("failed with status : " + status);
latch.countDown();
}
@Override
public void onCompleted() {
System.out.println("finished!");
latch.countDown();
}
};
orderStreamConsumer.getOrder(orderDTO, responseObserver);
latch.await();
}
}
总结,gRPC 的基石就是 HTTP/2,然后在上面使用 protobuf 协议定义好 service RPC,如果对这些不了解,将无法很好的掌握。
引用资料