三天三夜总算是搞懂了RPC远程过程调用,SpringCloud集成gRPC

Spring Cloud集成gRPC

gRPC本身的跨平台特性及性能上的优势都促使很多大公司采用gRPC的RPC解决方案作为微服务交互的标准交互集成方式。

到目前为止,Spring Cloud官方并没有支持gRPC,但是在GitHub上有非常多的第三方开源项目支持gRPC与Spring Cloud的集成,start数 目 最 多 的 开 源 项 目 是 grpc-spring-boot-starter 。该 项 目 也 是Spring Cloud社区推荐的gRPC项目。下面是这个项目的主要特性:

在Spring Boot应用中,通过@GrpcService自动配置并运行一个嵌入式的gRPC服务。

使用@GrpcClient自动创建和管理gRPC通道(Channels)和桩代码(Stub)。

支持Spring Sleuth作为分布式链路跟踪解决方案。

支持全局和自定义的gRPC服务端/客户端拦截器。

支持Spring Security。

支持Metric(基于micrometer/actuator)。

适用于(non-shaded)grpc-netty。

Spring Boot中gRPC的接入gRPC接入Spring Cloud主要分为三个工程模块,即服务定义模块、服务提供模块和服务消费模块。下面是接入gRPC的主要步骤。

1.服务定义

和其他RPC框架类似,gRPC需要做接口定义规范,默认情况下,会使用Protocal Buffers作为接口定义语言(IDL)。

首先,引入Maven依赖:

然后,编写一个.proto文件,定义好服务端的请求数据和响应数据,执行mvn clean install命令,protobuf-maven-plugin插件会根据.proto文件生成对应的Java代码,Maven的install命令会将接口工程打包上传到代码中央仓库,服务端和客户端可以通过Maven将远程中央 仓 库 加 载 到 本 地 并 打 包 到 各 自 的 工 程 中 。下 面 是 IDL 的 定 义(demo.proto):

2.gRPC Server实现

首先,引入Maven依赖:

其次,使用注解@GrpcService实现服务暴露:

然后,启动gRPC Server。默认情况下,gRPC Server会监听9090端口,也可以使用grpc.server.前缀自定义配置。

3.gRPC Client实现

首先,引入Maven依赖:

其次,使用注解@GrpcClient(serverName)作为gRPC的桩代码(Stub):

然后,实现gRPC Client的RestController远程调用:

gRPC的工作原理

gRPC的工作原理是先通过IDL文件定义服务接口的参数和返回值类型,然后通过代码生成程序生成服务端和客户端的具体实现代码。

gRPC的主要特性包括三个方面。

(1)通信协议采用了HTTP 2,因为HTTP 2提供了连接复用、双向流、服务器推送、请求优先级、首部压缩等机制,所以在通信过程中可以节省带宽、降低TCP连接次数、节省CPU资源,尤其对于移动端应用来说,可以延长电池寿命。

(2)IDL使用了ProtoBuf,ProtoBuf是由Google开发的一种数据序列化协议,它的压缩和传输效率极高,语法也简单,所以被广泛应用在数据存储和通信协议上。

(3)多语言支持,能够基于多种语言自动生成对应语言的客户端和服务端代码。

gRPC的核心概念

基于服务定义:ProtoBuffer IDL基于服务定义的思想,默认情况下gRPC使用ProtoBuffer作为IDL(接口定义语言)进行服务和消息的定义,示例代码如下:

gRPC可以定义四种类型的服务方法。

Unary RPC:客户端向服务端发送请求,并得到响应,类似于方法调用。

Server streaming RPC:客户端可以向服务端发送请求,获取服务端返回的流响应,客户端可从流中读取一组消息,客户端可以持续读取消息直至消息全部读取完成,gRPC保证消息顺序的正确性。

Client streaming RPC:客户端会写入一组消息,然后基于流的方式发送给服务端。当客户端写完全部消息后,就等待服务端进行消息的读取并等待服务端响应,gRPC保证消息顺序的正确性。

Bidirectional streaming RPC:服务端和客户端都可以使用读写流发送一组消息。服务端的流和客户端的流是相互独立的,所以服务端和客户端可以按照自己的方式进行流的写入和读取。例如,服务端可以决定在全部接收完客户端发送的消息后再进行响应,或者它可以读取一条消息,就写入一条消息。同样,在流中的消息的顺序是可以保证的。

HTTP 2

HTTP 2通过Stream支持了连接的多路复用,提高了连接的利用率。Stream的重要特性如下:

一个连接可以包含多个Stream,多个Stream发送的数据互相不影响。

Stream可以被客户端和服务端单方面或者共享使用。

Stream可以被任意一端关闭。

Stream会确定好发送Frame的顺序,另一端会按照接收到的顺序来处理。

Stream用一个唯一ID来标识。

虽然看上去协议的格式和HTTP 1完全不同,实际上HTTP 2并没有改变HTTP 1的语义,只是把原来HTTP 1的Header和Body部分用Frame重新封装了一层而已,如下图所示。

HTTP 2的优势如下。

连 接 共 享 :HTTP 2 要 解 决 的 一 大 难 题 就 是 多 路 复 用(MultiPlexing),即连接共享。

Header压缩:HTTP 2使用encoder来减少需要传输的Header大小,通信双方各自缓存(Cache)一份Header Fields表,既避免了重复Header的传输,又减小了需要传输Header的大小。高效的压缩算法可以大幅度压缩Header的大小,减少发送包的数量从而降低延迟。

重置连接表现更好:HTTP 2引入RST_STREAM类型的Frame,可以在不断开连接的前提下取消某个请求的Stream,表现更好。

流量控制:每个HTTP 2流都拥有自己的公示的流量窗口,它可以限制另一端发送数据。对于每个Stream来说,两端都必须告诉对方自己还有足够的空间来处理新的数据,而在该窗口被扩大前,另一端只被允许发送那么多数据。

支持普通/流式RPC

普通RPC调用:指客户端发送一个请求并获取一个响应。当客户端调用本地的桩方法时,服务端会得到一个RPC被调用的通知,通知中包含了关于此次调用的元数据信息(方法名、指定的合适的超时时间)。服务端可以立即返回一些它自己的初始化元数据,或者等待客户端的请求信息,当然这两种方式是和具体的应用相关的。当服务端接收到客户端的请求信息后,它会执行具体的逻辑以便产生一个响应。响应会和一个描述状态的详细信息及一个可选的附属元数据一起被发送给客户端。如果响应的状态是OK,则客户端就得到了响应,完成了一次RPC调用。

服务端Streaming模式:指客户端发起1个请求,服务端返回N个响应,每个响应可以单独返回,它的原理如下图所示。

双向流式RPC:客户端发送N个请求,服务端返回N个或者M个响应,利用该特性,可以充分利用HTTP 2的多路复用功能。在某个时刻,HTTP 2链路上可以既有请求也有响应,实现了全双工通信,示例如下图所示。

gRPC服务调用解析过程

gRPC的线程模型在Java实现中主要基于Netty底层网络通信框架,它遵循一个基本原则:除了传输过程中的监听及解包相关流程,其他的逻辑处理都会放在业务线程池中。比如序列化与反序列化、拦截器逻辑、本地方法调用。这个设计符合Netty的线程模型实践规范,最大化地保障传输框架的性能,提高服务资源的利用率。gRPC框架向业务层暴露了两个入口,一个是拦截器,在进入本地方法调用前拦截请求,用于处理一些前置逻辑;另一个就是本地服务。为了更清晰地表达业务线程池和Netty I/O线程池的分工,我们用下面的流程图来示意。

(1)NettyServer实例创建:gRPC服务端创建,首先需要初始化NettyServer,它是gRPC基于Netty 4.1和HTTP 2协议栈之上封装的HTTP 2服务端。

(2)NettyServerBuilder的buildTransportServer方法构建:NettyServer构建完成之后,监听指定的Socket地址。

(3)绑定IDL定义的服务接口实现类:gRPC与其他一些RPC框架的差异在于服务接口实现类的调用不是通过动态代理和反射机制,而是通过proto工具生成代码。在服务端启动时,将服务接口实现类实例注册到gRPC内部的服务注册中心上。请求消息接入之后,可以根据服

名和方法名,直接调用启动时注册的服务实例,性能更优。

(4)gRPC服务实例(ServerImpl)构建:ServerImpl负责整个gRPC服务端消息的调度和处理,在创建ServerImpl实例的过程中,会对服务端依赖的对象进行初始化。例如Netty的线程池资源、gRPC的线程 池 、 内 部 的 服 务 注 册 类 ( InternalHandlerRegistry ) 等 。

ServerImpl初始化完成之后,就可以调用NettyServer的start方法启动HTTP 2服务端,接收gRPC客户端的服务调用请求。

grpc-spring-boot-starter源码解析

grpc-spring-boot-stater的框架设计同样遵循脚手架一章中自定义Starter的方式,以便融合到Spring Boot和Spring Cloud体系。自定义Starter步骤如下。

1.自定义配置

2.在配置文件中加载Bean并初始化

3.配置Bean,初始化GrpcService服务

4.配置GrpcServerLifecycle服务

5.启动gRPC服务