在蚂蚁金服的分布式技术体系下,大量的技术产品(非网关类产品),都需要在内网,进行节点间通信。BOLT 提供了优秀的通信协议与通信框架,在 BOLT 的基础上,研发了自己的 RPC 框架,提供了负载均衡
,流量转发
,链路追踪
,链路数据透传
,故障剔除
等基础能力。
RPC框架应该包含的几个部分:User、User-stub、RPC-Runtime、Server-stub、Server。当 Client 想发起一个远程调用时,实际是通过本地调用 Client-stub,而 Client-stub 负责将调用的接口、方法和参数通过约定的协议规范进行编码并通过本地的 RPC-Runtime 实例传输到远端的实例。远端 RPC-Runtime 实例收到请求后交给 Server-stub 进行解码后发起本地端调用,在 Java中可以认为就是反射调用,调用结果再返回给 Client 端。
存在几个疑问:
1.Stub 怎么出现? ——>创建代理解决了 Stub 的问题
2.怎么打包参数? ——>序列化和网络协议编码解决了打包的问题
3.怎么传输? ——>Bolt,Netty 等解决了网络传输的问题
4.怎么知道目标地址? ——>服务发现与路由寻址解决了如何知道目标地址的问题
5.怎么发布一个 RPC 服务? ——>Registry 来解决
SOFARPC 结构设计
其中 core和 core-impl 是核心的功能,包含 API 和一些扩展机制,extension-impl 中,则包含了不同的实现和扩展,比如对 http,rest,对 metrics,以及其他注册中心的集成和扩展。如 bootstrap 中对协议的支持,remoting 中对网络传输的支持,registry 中对注册中心的支持等。
客户端调用流程
当使用方对服务进行了引用配置之后:
1.RPC 生成 Proxy,作为用户可以操作的入口。
2.向服务中心订阅这个 RPC 的地址信息。
3.使用方发起调用,经过路由,负载均衡,各类 Filter 发起调用。
服务端处理流程
在服务端看来,通过 TCP 监听端口后:
1.接到 RPC 请求后,进行解码和反序列化。
2.选择线程池,进行分发。
3.经过 Filter,进行反射调用。
4.将结果序列化,编码,进行写回。
可扩展的机制
为了对 RPC 各个环节的都有充足的可扩展性,提供 SPI 的能力。相比原生 SPI,实现了更强大的功能:按需加载、可以有别名、可以有优先级进行排序和覆盖、可以控制是否单例、可以在某些场景下使用编码、可以指定扩展配置位置、可以排斥其他扩展点。 SPI 机制的整个流程如图所示:
在启动加载阶段,RPC 会根据对应的配置,加载需要调用方法ExtensionLoader(Class
逻辑如下:首先读取rpc-config-default.json
和rpc-config.json
,找到扩展描述文件存放的文件夹:extension.load.path
属性。找到接口类对应的扩展描述文件的文件名(默认就是接口名,也可以自己指定)。循环加载这个文件下的扩展描述文件,按行读取。(同一个接口的同一个别名对应唯一的一个实现类,可以重复,允许覆盖。)保存扩展实现类的alias和实现类的对应关系。如果 ExtensionLoaderListener 不为空,则通知 Listener。最终,将会构造出各个不同的 Filter,Invoker 等等。
微服务已经被广泛应用在工业界,微服务带来易于团队并行开发、独立部署、模块化管理等诸多优点。然而微服务将原单体拆分多个模块独立部署,各模块之间链接变得错综复杂,在大规模分布式系统中这种复杂链路给维护带来了诸多困难。 如果对整个微服务架构不能了然于胸,便很难理清各模块之间的调用关系。 例如修改一个服务接口,对哪些服务造成影响不能快速定位。SOFARPC 在5.4.0 以后提供了链路追踪技术,可以有效协助开发运营人员进行故障诊断、容量预估、性能瓶颈定位以及调用链路梳理。
链路追踪技术主要是收集、存储、分析分布式系统中的调用事件数据,协助开发人员进行故障诊断、容量预估、性能瓶颈定位以及调用链路梳理。链路追踪技术包含了数据埋点、收集、存储、分析等相关技术,是一套技术体系。
SOFARPC 作为一个基础的通讯中间件,对服务调用有很强的感知能力,容易获取链路追踪所需的服务调用信息。因此很多链路追踪系统都会选择RPC 作为埋点对象,通过对RPC中间件的埋点可以轻松做到对用户的无感知、透明化。
在 RPC 调用过程中,我们经常会和多个服务端进行远程调用,如果在每次调用的时候,都进行 TCP 连接,会对 RPC 的性能有比较大的影响,因此,实际的场景中,我们经常要对连接进行管理和保持。SOFARPC 应用心跳包以及断线重连实现,结合系统 tcp-keepalive 机制,来实现对 RPC 连接的管理和保持。
TCP keep-alive
tcp-keepalive 机制可以在连接无活动一段时间后,发送一个空 ack,使 TCP 连接不会被防火墙关闭。tcp_keepalive_time
,在 TCP 保活打开的情况下,最后一次数据交换到 TCP 发送第一个保活探测包的间隔,即允许的持续空闲时长,或者说每次正常发送心跳的周期,默认值为 7200s(2h)。 tcp_keepalive_probes
在 tcp_keepalive_time 之后,没有接收到对方确认,继续发送保活探测包次数,默认值为 9(次)。 tcp_keepalive_intvl
,在 tcp_keepalive_time 之后,没有接收到对方确认,继续发送保活探测包的发送频率,默认值为 75s。
SOFABolt 基于系统 tcp-keepalive 机制实现;SOFABolt 基于 Netty IdleStateHandler 心跳实现。
SOFARPC 连接管理断开重连实现
连接管理是客户端的逻辑,启动好,连接管理开启异步线程。
其中,SOFARPC 连接管理 ConnectionHolder 维护存活的客户端列表healthConnections 和失败待重试的客户端列表 retryConnections,RPC 启动守护线程以默认 10 秒的间隔检查存活和失败待重试的客户端列表的可用连接:
1、检查存活的客户端列表 healthConnections 是否可用,如果存活列表里连接已经不可用则需要放到待重试列表 retryConnections 里面;
2、遍历失败待重试的客户端列表 retryConnections,如果连接命中重连周期则进行重连,重连成功放到存活列表 healthConnections 里面,如果待重试连接多次重连失败则直接丢弃。
SOFARPC 以基于 Netty 实现的网络通信框架 SOFABolt 用作远程通信框架,使用者不用关心如何实现私有协议的细节,直接使用内置 RPC 通信协议,启动客户端与服务端,同时注册用户请求处理器即可完成远程调用:SOFARPC 服务调用提供同步 Sync、异步 Future、回调 Callback 以及单向 Oneway 四种调用类型。
【1】Sync 同步调用
支持方法级别,接口级别的超时设置。同步调用是指的客户端发起调用后,当前线程会被阻塞,直到等待服务端返回结果或者出现了超时异常,再进行后续的操作
【2】Future 异步调用
客户端发起调用后不会同步等待服务端的结果,而是获取到 RPC框架给到的一个Future 对象,调用过程不会阻塞线程,然后继续执行后面的业务逻辑。服务端返回响应结果被 RPC 缓存,当客户端需要响应结果的时候需要主动获取结果,获取结果的过程阻塞线程。
【3】Callback 回调调用
客户端提前设置回调实现类,在发起调用后不会等待结果,但是注意此时是通过上下文或者其他方式向 RPC 框架注册了一个 Callback 对象,结果处理是在新的线程里执行。RPC在获取到服务端的结果后会自动执行该回调实现。
【4】Oneway 单向调用
客户端发送请求后不会等待服务端返回的结果,并且会忽略服务端的处理结果
I/O模型 JAVA NIO 和多路复用结合起来就是是最简单的 Reactor 模式:注册所有感兴趣的事件处理器,单线程轮询选择就绪事件,执行事件处理器。
Reactor 线程模型
Reactor 中定义了三个角色:
而一个标准的操作流程则是:步骤1:等待事件到来(Reactor 负责);步骤2:将读就绪事件分发给用户定义的处理器(Reactor 负责);步骤3:读数据(用户处理器负责);步骤4:处理数据(用户处理器负责)。在这个标准之下,Reactor 有几种演进模式:单线程模型、多线程模型、主从多线程模型。主从多线程模型是目前大部分 RPC 框架,或者服务端处理的主要选择。Reactor 主从多线程模型的特点:服务端用于接收客户端连接的不再是个1个单独的 NIO 线程,而是一个独立的 NIO 线程池。
主要的工作流程:
1、MainReactor 将连接事件分发给 Acceptor
2、Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证,黑名单等),将新创建的 SocketChannel 注册到 IO 线程池(sub reactor线程池)的某个 IO 线程上,Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证。
3、SubReactor 负责 SocketChannel 的读写和编解码工作。其 IO 线程负责后续的 IO 操作。
对于 SOFARPC 来说,和底层的 SOFABolt 一起,在使用 Netty 的 Reactor 主从模型的基础上,支持业务线程池的选择。
SOFARPC 提供了自动单机故障剔除能力,能够自动监控 RPC 调用的情况,对故障节点进行权重降级,并在节点恢复健康时进行权重恢复,提高系统可用性。
在分布式架构中常见可用性方案的是 集群(冗余),即将一个服务部署在多个机器上,通过硬负载或软负载实现服务的均衡负载。硬件负载因每次请求都需要经过硬件负载,承担所有的访问压力,当集群规模增加、流量增多,硬件负载可能因无法支撑所有流量而导致系统不可用。软负载则提供注册中心,并将负载能力转移到服务调用方( Consumer ),注册中心只有在 Consumer 首次订阅或服务发生变化时才会发生交互,避免了并发访问下的单点问题。虽然软负载可以避免单点问题,但可能存在以下场景导致服务不可用:
1、Provider 出现单点故障或宕机,与 Consumer 的长连接已断开,但注册中心尚未摘除或未及时通知Consumer。
2、Consumer 和 Provider的长连接还在,注册中心未下发摘除,但服务器端由于某些原因,例如长时间的 Full GC, 硬件故障(后文中为避免重复,统一描述为机器假死)等场景,处于假死状态。
这两种场景都是服务端出现故障,但由于长连接还保留等原因注册中心未摘除服务,导致服务调用失败。针对第一种情况 Consumer 不应调用出现故障的 Provider,否则会引起部分服务不可用;针对第二种情况,这个 Consumer 应该不调用或少调用该Provider,可以通过权重的方式来进行控制。目前 SOFARPC 5.3.0 以上的版本支持RPC 单机故障剔除能力。SOFARPC 通过服务权重控制方式来减少异常服务的调用,将更多流量打到正常服务机器上,提高服务可用性。
SOFARPC故障剔除 vs 注册中心故障剔除
SOFARPC 的故障剔除与注册中心故障服务剔除不同,它们从不同的维度来完成故障剔除提高服务可用性。主要两方面的区别:故障剔除的时机、故障剔除的细粒度。
故障剔除的时机:SOFARPC 的故障剔除与注册中心故障服务剔除不同,它们从不同的维度来完成故障剔除提高服务可用性。注册中心服务管理关注 Provider 与注册中心的心跳或长连接。如果 Provider 出现心跳异常或长连接不存在,则及时将服务从注册中心剔除,并告知 Consumer 移除本地缓存的故障 Provider 信息。Comsumer 在负载均衡选择时则不考虑被剔除的 Provider。而 SOFARPC 单机故障剔除针对的场景不同,针对的是注册中心还未剔除的服务,这些服务与 Consumer 仍然保持长连接,但由于机器假死,不能提供正常服务。
故障剔除的细粒度:注册中心剔除的是粒度是针对单机上的某个服务进程,属于进程级别。一旦这个进程和注册中心断开连接或心跳无感应,则将其从注册中心剔除。SOFARPC 故障剔除并控制精度会更精细一些,会细致到进程对外暴露的服务。
服务权重降级 vs 服务降级
服务降级是当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。这里的降级级别是整个系统服务,而不是针对接口级别。
SOFARPC 的服务降级,是指当某些个别机器因为存在机器假死,导致处于假死状态,导致一些服务接口响应异常,通过 SOFARPC 的故障剔除和服务权重降级来减少对这些异常机器接口的访问,而将更多的流量打到正常的机器上。 这里针对的维度主要还是 IP + 服务维度,如部署在 xxx 机器上交易系统对外提供的 TransQueryService 服务。
通常一个服务有多个服务提供者,其中部分提供者可能由于机器假死等导致长连接还存活但是程序已经无法正常响应 。 故障剔除功能会将这部分异常的服务提供者进行降级,使得客户端的请求更多地指向健康节点。当异常节点的表现正常后,故障剔除功能会对该节点进行恢复,使得客户端请求逐渐将流量分发到该节点。
SOFARPC 单机故障剔除模块是 FaultToleranceModule, 通过SOFARPC 的 SPI 机制完成模块的自动化加载,以完成该功能的插入。 FaultToleranceModule 模块包含了两个重要部分:
1、subscriber 事件订阅器 。 通过订阅事件总线 EventBus 的事件,以零侵入方式完成 RPC 调用的统计和信息收集。
2、regulator 调节器 。 根据收集的 RPC 调用信息,完成服务调用或服务权重的调节,达到服务降级和服务恢复的目的。内置了信息收集器、计算策略、度量策略、恢复策略, 是单机故障剔除的核心实现。
FaultToleranceModule 主要关心两种调用事件:
1、同步结果事件: ClientSyncReceiveEvent, 收集和统计 RPC 同步 调用次数和出现异常的次数。
2、异步结果事件: ClientAsyncReceiveEvent,收集和统计 RPC 异步 调用次数和出现异常的次数。
如图所示是整个 SOFARPC 故障自动剔除功能的整体结构:
整体流程如下:
1、在 RPC 同步或异步调用完成后会向事件总线 EventBus 发送对应事件。
2、FaultToleranceModule 的订阅者收到对应事件,开始进行调用统计,将统计结果存储到 信息收集器中。并在第一次存储时触发 Measure 定时任务。
3、Measure 定时任务会在指定窗口时间定时执行。获取信息收集器的所有信息并交给 度量策略 做度量计算,并开启计算线程负责进行计算和服务调节。
4、计算线程首先会 调用 计算策略,计算策略根据 度量策略的计算结果判断是否执行降级或恢复。
5、如果进行降级,则调用降级策略执行降级操作,如打印日志或降低故障服务权重。
6、如果进行恢复,则调用恢复策略执行恢复操作,如打印日志或恢复故障服务权重。
7、最后在 RPC 调用的时候,负载均衡器(默认是 random + weight 负载均衡)会根据根据权重来选择服务。权重越低的服务被调用概率越小,流量流入更少;权重越大的服务,被调用概率越大,流量流入增多。
使用泛化调用,将相应的请求包装成泛化调用,就能够实现不依赖接口 jar 包,多语言调用 RPC 服务,避免重复开发。泛化调用的关键就是对象表示和序列化,SOFARPC 提供了 GenericObject 等对象来表示参数对象或者返回值对象,而将GenericObject 对象序列化成目标对象,或者将返回值反序列化成 GenericObject 对象,是 SOFARPC 实现泛化的关键。如图所示为SOFARPC 泛化调用的流程图:
1.泛化 API 调用时,会加载泛化过滤器,作用是做一些参数转换,同时设置序列化工厂类型。
2.SOFARPC 在使用 SOFABolt 进行网络调用前,会创建 context 上下文并传递给 SOFABolt,上下文中包含着序列化工厂类型信息,这个信息将决定使用何种序列化器,同时这个上下文将流转于整个调用期间。
3.在 SOFABolt 正式发送数据之前,会将 GenericObject 对象序列化成普通对象的字节流,这样,服务提供方就不必关心是否为泛化调用,从图中可见,提供方不用对泛化调用做任何改变 —— 这是 SOFARPC 泛化区别于其他 RPC 泛化的关键。
4.当提供方成功接收请求后,使用普通序列化器即可反序列化数据,只需要正常调用并返回即可。
5.当消费者的 SOFABolt 接收到响应数据后,便根据 context 的序列化类型,对返回值做反序列化,即将普通的字节流反序列化成 GenericObject 对象 —— 因为客户端有可能不知道返回值的 Class 类型。
6.最终,泛化 API 即可得到 GenericObject 类型的返回值。
相比较其他 RPC 框架两端都需要对泛化进行支持,SOFARPC 显得要友好的多。服务端无需感知是否泛化,一切都是由客户端进行处理。带来的好处是:应用如果想要支持泛化,不需要改动服务端,只需要修改客户端即可。这是和其他 RPC 框架泛化调用最大的区别。
实现方式:通过SOFA-Hessian 序列化支持泛化序列化,在进行泛化调用时,bolt 会根据上下文的序列化标记来使用对应的序列化器,SOFA-Hessian 特有的泛化序列化器可将 GenericObject 对象序列化成目标对象的字节流,服务端按正常反序列化即可。SOFA-Hessian 特有的泛化反序列化器也可将目标返回值反序列化成 GenericObject 等对象。
在 RPC调用中,数据的传递,是通过接口方法参数来传递的,需要接口方定义好一些参数允许传递才可以,在一些场景下,我们希望,能够更通用的传递一些参数,比如一些标识性的信息。业务方可能希望,在每一次调用请求中都能够传递一些自定义的信息到下游。甚至也希望下游能够将一些数据传递回来。
而数据透传功能,就是指数据不需要以作为方法参数的形式在调用链路中进行传递,而是直接存储到调用上下文中,之后通过 RPC 的内置对象,进行传递,调用双端可从上下文中获取数据而不需要去关注数据的传输过程。
SOFARPC 提供的数据透传支持请求数据透传(客户端向服务端)和响应数据透传(服务端向客户端)。
1、用户通过 SOFARPC 提供的 API 进行数据传递设置
2、SOFARPC 在调用传输前,将透传的数据进行打包获取
3、进行正常的序列化和反序列化
4、SOFARPC 在反序列化时将用户设置的透传数据写回 Context
5、服务端用户即可进行获取使用
RPC 调用通过网络传输相关的调用方法及参数,在这个网络传输过程中,内存中的对象是无法直接传输的,只有二进制字节才能在网络上传输。而为了实现调用对象在网络上的传输,必须通过序列化实现对象 -> 字节的过程,以及反序列化实现字节 -> 对象的过程。在网络协议模型中,序列化属于应用层协议的一部分。SOFARPC 支持的序列化协议有:SOFA-Hessian、Protobuf、Json。