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

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

RPC 远程过程调用,是我们工作中最常用的技术,然而发现很多同学仅停留在使用层面,而对其内部原理并不是很熟悉,于是参考了dubbo原理相关知识并结合自己的理解,写一个简易版RPC框架。它抛开dubbo内部的技术细节,模拟了服务提供方与消费方的启动和调用的核心流程,并且支持本地调试,不依赖第三方软件。一方面是总结,另一方面希望大家看完有所收获。代码地址:https://github.com/HenryWangXin/rpcRealize.git。

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

9fa2160097f2d2936f53199bc7f74402.png

简单介绍就是:

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

步骤2:服务消费方启动时从注册中心订阅自己需要服务的地址。

步骤3:.注册中心把已经注册的服务提供者地址,返回给消费方。

步骤4:消费方根据路由规则与负载均衡策略,选择一个提供者地址进行调用。

步骤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相关组件的类。如下图所示:

a4811da9d6372b1746979d9a733286c3.png

演示效果

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

42c95792fd555171a6bcad2721b819b8.png

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

d397ff2a43894e2fe59a50c3fafc4c10.png

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

1f6ce6e3ffed0213ae8da6c49bd1f8a7.png

4.结果展示:

4e5ba2c2b68eee7e7d23df1cc98c26ad.png

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 中有如下代码:

c2df6b7e86a80b7cef737f568491d8be.png

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

7390e3d4d529287e5ecf04b917618370.png

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

425288ec5a2d7ac8e6247e00f61240b6.png

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

815e7b4f5eb37ebf7b7ae1e88b27f6b7.png

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

4c56e2be711ca8f9191a7a6b94eaacc7.png

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

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

418e93cc20c46e18ecd7253d22ec5d22.png

服务发布流程

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

a55106992d12f016a72de2ccd0b21589.png

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

cd656ff8eaf5b161f0229ea721d16af2.png

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

a721ebadcd77768429de9c03f00bee32.png

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

6d6df519eb9b43762823a32f39228407.png

服务调用流程

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

b48b6eefa81ba73c56cecb62a2e4c54b.png

消费端启动流程

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

6e053a2c4b804e3e765f275fe57ba1d4.png

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

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

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

aaac0c31562f4b5fc4b5d7bb133074c8.png

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

4b52828781804b300914ccae40685460.png

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

ac8a03f4098277e8f1e37f422eb1b4a3.png

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

fbb57b9aa7f0f8ae45898088643886cb.png

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

c6ece6f049ae053fc29193fde07c7945.png

消费端调用流程

9fdcceea84a44a390fc2912d1d4b7ebf.png

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

356a05f71362381ee2a7be150d67c343.png

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

bf134b95e52604896147ed85f5056c6e.png

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

09c4e4b83f5cc11c4447c59b4c5ee8b6.png

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

4cefd0ec4bcafe2b00d08f5735158bdd.png

容错策略

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

39249ca7c1d4004b20cc5e641d221021.png

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

1818d72a3482c2ca07e397bb5da1acdb.png

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

负载均衡策略

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

9fef677b1918ae7f2cdffae80f4291e2.png

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

9d5e13b78b51750ebfe32767ddbaa3c5.png

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

总结

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值