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 特色之处在于它的客户端是多路复用的线程安全的,可以拿过来直接使用,而且多路复用的客户端在效率上有明显优势。
插播句Protobuf
gRPC 出来的时间要比 Thrift (Facebook )要晚,Google 自然是知道 gRPC 要比 Thrift 相比有明显的优势所以才敢放出来开源的,在开源影响力上Google 一直很强势
gRPC 的 service 接口是基于 protobuf 定义的,我们可以非常方便的将 service 与 HTTP/2 关联起来。
思考:我们知道Protobuf 协议仅仅定义了单个消息的序列化格式,HTTP/2模式下该如何protobuf集成呢?
难道我们需要自定义头部的长度和消息的类名称来区分消息边界?其实request-response的奥秘都在这张图中,下面将展开讲
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
request 通常包含:
- Request-Headers,直接使用的 HTTP/2 headers,在 Headers Frame 里面派发,定义的 header 主要有 Call-Definition 以及 Custom-Metadata
- Length-Prefixed-Message,主要在 Data Frame 里面派发,它有一个 Compressed flag ,然后后面跟着四字节的 message length 以及实际的 message
- EOS(end-of-stream),在最后的 DATA frame 里面带上了 END_STREAM 这个 flag。用来表示 stream 不会在发送任何数据,可以关闭了
Call-Definition 里面包括 Method(其实就是用的 HTTP/2 的 POST),Content-Type 等。思考:Request-Header何时算响应结束呢?
Custom-Metadata 则是应用层自定义的任意 key-value,key 不建议使用 grpc- 开头,因为这是为 gRPC 后续自己保留的。
Compressed flag 用来表示改 message 是否压缩,如果为 1,表示该 message 采用了压缩,而压缩算啊定义在 header 里面的 Message-Encoding 里面。
上图也有答案,Headers Frame中带上了END_HEADERS 表示已经是头信息的最后一个帧
Response
主要包含:
- Response-Headers,主要包括 HTTP-Status,Content-Type 以及 Custom-Metadata 等
- Length-Prefixed-Message,
- Trailers,包括了 Status 以及 0 或者多个 Custom-Metadata
- Trailers-Only,如果遇到了错误,也可以直接返回。也有 HTTP-Status ,Content-Type 和 Trailers
HTTP-Status 就是通常的 HTTP 200,301,400 这些不再解释思考:response何时算响应结束呢?
Status 也就是 gRPC 的 status
Status-Message 则是 gRPC 的 message,采用了 Percent-Encoded 的编码方式,具体参考这里
从上图就可以找到答案,最后收到的 Headers frame 里面,带上了 Trailers,并且DATA frame有 END_STREAM 这个 flag,那么就意味着 response结束!
接下来将带你从实操中理解gRPC的一些抽象的概念,可下载完整的源码。
os-maven-plugin
它主要是用于根据不同操作系统提供不同的变量(protoc版本信息)
[INFO] os.detected.name: windowsmaven-dependency-plugin
[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
自动下载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提供了3种Contract:
在2.7版本之前:
问题:不太符合异步编程的习惯,如果同时进行多个异步调用,使用不当很容易造成上下文污染,jdk原生Future 并不支持 callback 的调用方式。
- 将同步接口声明成
async=true,比如
- 通过上下文类获取 future,比如:Future fooFuture = RpcContext.getContext().getFuture();
在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
- BlockingStub,阻塞式
- FutureStub,guava的ListenableFuture
- Stub,支持StreamObserver
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,如果对这些不了解,将无法很好的掌握。
【#|gRPC快速入门】引用资料
- gRPC 官方英文文档
- gRPC Errors
- gGRPC 设计与实现
推荐阅读
- 数据结构和算法|LeetCode 的正确使用方式
- #|7.分布式事务管理
- #|算法设计与分析(Java实现)——贪心算法(集合覆盖案例)
- #|算法设计与分析(Java实现)—— 动态规划 (0-1 背包问题)
- #|阿尔法点亮LED灯(一)汇编语言
- #|Multimedia
- #|ARM裸机开发(汇编LED灯实验(I.MX6UL芯片))
- 基础课|使用深度优先搜索(DFS)、广度优先搜索(BFS)、A* 搜索算法求解 (n^2 -1) 数码难题,耗时与内存占用(时空复杂度)对比(附((n^2 - 1) 数码问题控
- #|学习笔记 | Ch05 Pandas数据清洗 —— 缺失值、重复值、异常值
- win10|搏一搏 单车变摩托,是时候捣鼓一下家中的小米电视机啦。