一文详解RPC框架核心原理与手写实战

Posted 京东零售技术

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文详解RPC框架核心原理与手写实战相关的知识,希望对你有一定的参考价值。

简易版RPC框架演示核心原理


首先来回顾下经典的RPC架构图:



简单介绍就是:

步骤1:服务提供方启动时将服务注册到注册中心。

步骤5:调用双方与监控平台建立定时发送机制,用来统计调用次数与耗时等相关数据。

本demo同样遵循此架构图,但是没有实现步骤5的Monitor,因为它不是架构必须的,因此并不影响效果。本文意图是通过文章讲解结合代码调试,熟悉整个RPC框架发布和调用流程。对其技术细节,这里不做深究。


已经实现内容:

1.SPI机制。

2.服务发布与调用流程。(支持netty或http两种协议,通过SPI选型)

3.服务注册中心。(本地文件形式)

4.服务集群容错。(安全失败 或 失败重试 策略)

5.负载均衡。(随机 或 轮询 策略 且支持权重)


未实现内容:

1.没有实现 SPI的增强(DI和AOP)。

2.为了方便本地调试,注册中心采用了本地文件形式,未实现通知机制。

3. 未实现 异步、泛化、隐式参数,mock降级等功能。

4. 监控平台。


模块简介


整个demo一共有 Provider 、Consumer 和 Sdk三个模块,其中Provider模块包括RPC接口的具体实现类、服务的发布流程、处理请求等逻辑的实现。Consumer包括消费端的启动与调用,通过SPI机制动态选择负载均衡和集群容错等逻辑。Sdk模块包含例如 注册中心、协议实现 等RPC相关组件的类。如下图所示:


一文详解RPC框架核心原理与手写实战


演示效果


1. 声明一个测试接口,并给出实现:


一文详解RPC框架核心原理与手写实战


2. 通过修改端口,本地开启两个服务,如图所示:


一文详解RPC框架核心原理与手写实战


3.客户端调用通过 轮询机制 调用10次:


一文详解RPC框架核心原理与手写实战


4.结果展示:


一文详解RPC框架核心原理与手写实战


SPI如何实现


由于框架每层都是组件化的,所以每层都用到了SPI机制,因此我们先从SPI原理讲起。SPI (Service Provider Interface) 是服务提供的发现机制,它类似策略模式,能将改动与原来的代码隔离开来,从而避免新增代码对原有代码的影响,通过 SPI 机制提供的灵活性,让框架拥有良好的插件特性,便于扩展。具体可以参考dubbo官网介绍:http://dubbo.apache.org/zh-cn/docs/source_code_guide/dubbo-spi.html

我们结合服务提供者核心类 ServerConfig 来讲解如何使用SPI加载扩展接口Protocal的实现类的,在ServerConfig 中有如下代码:


一文详解RPC框架核心原理与手写实战


方法1、getExtensionLoader方法,及获取当前扩展接口对应的ExtensionLoader 对象,在整个模块中每个扩展接口对应着自己的ExtensionLoader 对象,内部通过并发Map 来缓存扩展接口与对应的ExtensionLoader 的映射,其中key 为扩展接口的Class 对象, value为对应的ExtensionLoader 实例:


一文详解RPC框架核心原理与手写实战


方法2、getAdaptiveExtension方法,是采用JDK的动态代理方式,对本SPILoader中扩展接口生成一个对应的适配器类,适配器类里会根据特定export方法,选出合适的实现类,执行其方法。如下图所示:


一文详解RPC框架核心原理与手写实战


方法3、getExtension方法根据接口路径找到相应的配置文件,然后通过配置文件的实现类的名称找到对应的扩展实现类,实际底层是用了反射把class对象放入了map缓存中。如下图所示:


一文详解RPC框架核心原理与手写实战


上图loadResource方法是读取此接口相应的配置信息,key起任意名,value为实现类的全路径,然后通过反射把相应的class放入缓存中。


一文详解RPC框架核心原理与手写实战


上图的createExtension 方法是把上一步的class通过newInstance创建实例保存到缓存中。

下面画出了 SPIloader的方法时序图,可以根据时序图再结合代码,可以清楚它调用关系:


一文详解RPC框架核心原理与手写实战


服务发布流程


通过时序图,看下RPC服务提供方启动流程,如下图所示:


一文详解RPC框架核心原理与手写实战


步骤1、2、3、4启动spring环境,通过spring后置处理器对ServiceConfig对象进行初始化,然后执行其export方法,代码具体为:


一文详解RPC框架核心原理与手写实战


步骤5为doExport方法,先把配置信息,转换为URL对象,然后把URL转换为Invoker对象,最后通过动态代理类和入参动态挑选一个 Protocol 实现类(SPI策略)执行export方法具体为:


一文详解RPC框架核心原理与手写实战


步骤7、8、9的export()方法实际完成了 启动NettyServer监听服务链接,然后将服务注册到文件中。具体实现为:


一文详解RPC框架核心原理与手写实战


服务调用流程


上文可知,在服务发布过程中指定了NettyServerHandler处理业务请求,当请求过来时,首先把消息转为Invocation 对象,根据Invocation的接口名获取服务发布时候缓存中的具体的实例,最后根据Invocation的方法名和参数类型,反射执行具体的RPC方法代码如下:


一文详解RPC框架核心原理与手写实战


消费端启动流程


通过时序图,看下RPC服务调用方启动流程:


一文详解RPC框架核心原理与手写实战


步骤1、2:是启动spring环境,从ApplicationContext

里获取rpc接口,spring环境初始化时,会对ReferenceConfig

对象进行初始化,然后执行其get方法,代码具体为:


一文详解RPC框架核心原理与手写实战


步骤3,get() 方法生成远程调用代理类,首先从配置中心获取此接口已经注册好的urls,然后循环urls调用protocol.refer方法(启动客户端链接)生成invokers list,再通过cluster.join方法,生成一个带有集群容错逻辑的invoker,最后在为此invoker做一层动态代理返回代理对象,具体代码为:


一文详解RPC框架核心原理与手写实战


步骤5,PROTOCOL同样也是通过SPI机制拿到实现类,调用refer方法,作用是开启netty客户端返回一个客户单端调动对象invoker代码如下:


一文详解RPC框架核心原理与手写实战


步骤6, CLUSTER 通过SPI机制获取实际类,调用join方法通过构造方法初始化一个带有集群容错机制的Invoker。


一文详解RPC框架核心原理与手写实战


步骤7,最终通过JDK动态代理,返回一个代理类。


一文详解RPC框架核心原理与手写实战


消费端调用流程


一文详解RPC框架核心原理与手写实战


步骤2 调用rpc方法,实际调用的代理的invoke方法,首先通过方法对象创建一个Invocation对象,然后通过SPI生成对应的LoadBalance(负载均衡策略),最后执行invoker.doInvoke方法; 


一文详解RPC框架核心原理与手写实战


步骤4 doInvoke方法实现了集群容错逻辑,同时也执行了负载均衡的select方法:


一文详解RPC框架核心原理与手写实战


步骤5 通过SPI筛选的负载均衡策略执行select方法返回具体的invoker对象例如:


一文详解RPC框架核心原理与手写实战


步骤6,7调用选好的Invoker执行其invoke方法,底层是nettyclient的send方法:


一文详解RPC框架核心原理与手写实战


容错策略


集群容错机制实现了当服务消费方调用服务提供者的服务,出现错误时的容错方案,目前实现了下面几种,其中Failfast Cluster为当消费者调用服务提供者失败后,立即报错,也就是只调用一次;Failsafe Cluster当出现调用异常时,直接忽略异常;Failover Cluster当调用失败后,自动切换到其他服务提供者进行重新调用。当然也可以通过SPI机制进行定制化的容错逻辑。本demo已经实现的有:


一文详解RPC框架核心原理与手写实战


下面介绍一下比较常用的 失败重试 策略的实现:


一文详解RPC框架核心原理与手写实战


我们看到失败重试实际通过对重试次数进行循环便可实现,如果第一次调用成功,则直接跳出循环返回,否则进行循环重试。如果第一次调用出现了异常,仍然会继续循环调用,直到服务提供者都调用一遍,记录最后一个的异常,再返回。


负载均衡策略


当服务提供方有多台时,为了解决某台负载过高问题,需要负载均衡策略。本例中实现了下面两种:1、random随机策略。可以支持设置权重来影响服务提供者的概率。2、roudrobin轮询策略,通过服务提供者设置的权重,设置轮询的比率。如果有定制化需求,可以基于loadBalance进行定制。本demo已经实现的有:



下面介绍一下默认的random随机策略:



随机策略不是简单的random一个值就可以了,而是与服务提供方的权重有关。通过步骤7可以知道如果权重不一样,会根据总权重选择随机选择一个数,再去确定要用那个invoker。权重越大,选到的概率越大。


总结


本文首先讲解了SPI原理,通过SPI 增强了框架对 协议层,集群容错,负载均衡 等相关组件的扩展性。然后对服务提供方启动 以及 接收的请求进行处理,和 服务消费方启动流程以及如何发起一次远程过程调用的过程,进行介绍。最后分别列举了一种集群容错,和负载均衡策略的具体实现,看完本文,相信大家对RPC的原理将会有直观的理解。

以上是关于一文详解RPC框架核心原理与手写实战的主要内容,如果未能解决你的问题,请参考以下文章

从零开始手写 dubbo rpc 框架

手写一个自己的 RPC 框架?

RPC实战与核心原理

10个类手写实现 RPC 通信框架原理

一文了解RPC框架原理

protobuf源码解析与netty+rpc实战