vlambda博客
学习文章列表

dubbo源码-客户端调用-地址选取

简介

ReferenceConfig创建出invoker被包装进代理对象后,就可以对服务端发起调用了。

真正调用是这个inovker对象的invoker方法。主要用处是在服务器列表中选择一个发起远程调用。

本文讨论他的选取过程。


涉及对象

  • MockClusterInvoker 入口Invoker对象

  • RegistryDirectory    存放服务器列表 invokerList

  • Router接口    路由RegistryDirectory中的invokerList,过滤符合提交的invokerList

  • LoadBalance接口    负载均衡器,用于在一堆invokerList中按一定规则选取一个invoker。不同的实现有不同的选取规则

  • FailoverClusterInvoker   默认的ClusterInvoker。失败重试Invoker。在发起调用失败后执行重试策略。和他类似的有Failback,  Failfast等等,实现了不同的执行策略。


调用过程

MockClusterInvoker

代理类的inovker是MockClusterInvoker。(最后的对象图如下

MockClusterInvoker内部的invoker对象默认是FailoverClusterInvoker。我们从MockClusterInvoker的invoker方法入手。

dubbo源码-客户端调用-地址选取

  1. 入参invocation是调用的上下文。包括接口名,参数类型,参数值等调用相关的数据。会在整个调用过程中一直向下传递

  2. MockClusterInvoker主要实现的就是mock功能。主线业务逻辑传递给他自己属性invoker(FailoverClusterInvoker)的invoker方法。

  3. 如果配置了mock的方式,会使用mock的结果。方便开发调试场景。或者是先调用业务方法,异常了再用mock。


AbstractClusterInvoker

FailoverClusterInvoker继承AbstractClusterInvoker。作用是在一堆Invoker中,怎样找出合适的invoker以怎样的方式去执行。

dubbo源码-客户端调用-地址选取

  1. list获取到可以执行的invokers列表。这个invokers就是指向每台服务提供者的机器

  2. 初始化一个负载均衡器。

  3. 调用子类的doInvoke。传入可用的invokers列表和负载均衡器,选择invoker并调用


list

dubbo源码-客户端调用-地址选取

list方法是调用directory.list。

这个directory是XXX里面讲RegistryDirectory说的另一个核心方法。获取可用的invokersList。

  • RegistryDirectory里存放了所有的invokersList。且有一个路由链routerChain属性。通过routerChain.route的过滤,返回符合条件的invokersList。

dubbo源码-客户端调用-地址选取

routerChain默认有mock, tag, app, service4个,通过SPI的@Activate创建出来()。

另外routerChain也会随着RegistryDirectory监听zk的router节点上的url而更新,动态添加新的路由过滤逻辑。


loadBalance

负载均衡包括以下几个实现

dubbo源码-客户端调用-地址选取

默认使用随机random。当然也可以配置成其他的方式。有兴趣的可以看看实现方法。并不是简单粗暴的随机值或者取模。


doInvoker

  1. doInvoker交給子类实现。包括出错了要怎样处理,要调用几次,结果怎么处理。(因为一个调用同一个RPC服务,这些服务会有多台机器)。

  2. 虽然doInvoker由子类实现,但子类主要处理不同场景下失败调用,或者对结果特殊处理等逻辑。核心选择invoker的逻辑(select)还是在父类AbstractClusterInvoker中实现。


默认实现是FailoverClusterInvoker。

和FailoverClusterInvoker平级,也继承了AbstractClusterInvoker的还有AvailableClusterInvoker,BroadcastClusterInvoker,FailbackClusterInvoker,FailfastClusterInvoker,FailoverClusterInvoker,MergeableClusterInvoker

dubbo源码-客户端调用-地址选取

他们的功能可以总结如下:

  1. FailfastCluster 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。

  2. FailoverCluster  失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries="2" 来设置重试次数(不含第一次)

  3. FailbackCluster   失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。

  4. FailsafeCluster    失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。

  5. BroadcastCluster  广播调用所有提供者,逐个调用,任意一台报错则报错。通常用于通知所有提供者更新缓存或日志等本地资源信息。

  6. ForkingCluster    并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks="2" 来设置最大并行数。

  7. AvailableCluster   遍历所有的,只要一个有为true直接调用返回,否则就抛出异常

默认使用的是FailoverCluster。其实应该根据具体的业务场景,来配置不同的策略

比如默认FailoverCluster是会重试的,在一些对性能要求高的地方,不想多次调用,只想一次拿到结果的场景,就不应该配置FailoverCluster,而是FailfastCluster 。


FailoverClusterInvoker

核心作用在于调用失败后会发起重试

dubbo源码-客户端调用-地址选取

  1. len 失败重试次数。默认2 + 1次。如果不成功,默认一共会调用3次。

  2. select 通过负载均衡器可用invokerList已选择过的invokerList 三者的配合,来选取一个可执行的invoker


select

dubbo源码-客户端调用-地址选取

如果设置了sticky调用,这一直会使用选中的同一个invoker。方便在众多服务器中固定一个服务提供者。开发调试的时候方便指定固定机器。

真正的选择由doSelect实现。


doSelect

dubbo源码-客户端调用-地址选取

  1. 如果可选的只有一个,就直接返回

  2. 否则使用负载均衡器loadbalance(默认是random)进行选择。

  3. 选择出来的invoker如果已经被调用过,或者已经不可用,则会重新选择reselect

    1. 注意:已经被调用过是指本次调用中被选择调用过的invoker,客户端业务bean多次请求的invoker是不相通的比如FailoverCluster在一次调用中会发起重试,那么就会记录已经调用过的invoker。但是FailfastCluster只执行一次,所以这个已经被调用过的list对FailfastCluster就毫无意义。FailfastCluster的代码实现中selected为null。

  4. 如果重新选择的还是有问题,则会选择刚才被负载均衡器loadbalance选择的invoker的下一个。

reselect

基本也是按规则从可选的invokerList和已选的list中找一个可用的invoker。

dubbo源码-客户端调用-地址选取

  1. 先从不在已选list的列表中选取invoker

  2. 如果没有,则从已选的list中获取invoker

  3. 获取的逻辑都通过负载均衡器loadbalance进行选择

选取出invoker后,调用invoker.invoke发起远程调用

dubbo源码-客户端调用-地址选取


FailfastClusterInvoker

上面说的所有select的逻辑,全部在父类AbstractClusterInvoker中实现

也就说其他子类只负责调用策略,或者处理失败的问题

比如FailfastCluster:

dubbo源码-客户端调用-地址选取

选择一个invoker后,直接调用。出错了直接抛出异常。(好像这才是更直接的调用方式)

他和FailoverCluster就在于对调用失败上的,FailoverCluster有重试的机制。


invoker

选取的invoker是被各种wrapper包装的dubboInvoker对象,由他通过netty发送远程通信,调用服务端的接口。(netty被包这么深)下期我们再讨论咯。


整体流程如下:

dubbo源码-客户端调用-地址选取


RandomLoadBalance

随机负载均衡器的选取策略并非完全平等随机,而是加入了权重。可以配置服务端每台机器不同的权重来分配随机的概率。

dubbo源码-客户端调用-地址选取


整体逻辑

先获取各个invoker的权重weight。如果权重都相同,则直接按list长度进行随机。(最后一行代码)

如果invoker的权重不同,计算出总权重值。按总权重随机一个值,看这个值在哪个invoker的区间,就选那个invoker。

比如:3个权重是[20, 30, 50],则总权重是100。nextInt(totalWeight)出一个100以内的随机数。看这个数在0-100被20, 30, 50 拆分的哪个区间(0, 20), (21, 50), (51, 99)。(代码中红框的部分)

dubbo源码-客户端调用-地址选取


权重获取

获取每台服务器的权重除了配置以外,还配合了启动时间的预热机制。

dubbo源码-客户端调用-地址选取

warmup默认是10分钟。

timestamp是服务器服务提供的时间。uptime算出这台服务已经启动多久了。

如果小于warmup,则会按启动时间的比例将weight打折

  • 比如:某台服务器只启动了5分钟,这时候就他的权重就会按50%参与random计算。


权重随机是一种纯算法,和dubbo没有必然联系。需要用随机算法的时候可以借鉴。同样也可以借鉴dubbo中实现的轮询,一致性hash,最小活跃等算法。这些算法的详细过程以后再讨论咯。


总结

  1. ClusterInvoker会在registryDirectory的invokerList中选取一个执行器来发起远程调用

  2. 选取的逻辑由路由器routerChain,负载均衡器LoadBalance来控制

  3. 执行策略有各种失败情况处理FailXxx,有可执行即调用的AvailableClusterInvoker等等



觉得不错,订阅关注吧