您的位置:首页 > 其它

服务治理中间件 Dubbo 原理解析(集群容错)读书笔记

2018-08-24 15:01 232 查看

 

[code]第八章: 集群&容错
Dubbo 作为一个分布式的服务治理框架,提供了集群部署,路由,软负载均衡及
容错机制
下图描述了 dubbo 调用过程中的对于集群,负载等的调用关系。一: cluster
将 Directory 中的多个 Invoker 伪装成一个 Invoker, 对上层透明,包含集群的容错机制
Cluster 接口定义
@SPI(FailoverCluster.NAME)
public interface Cluster {
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
Cluster 可以看做是工厂类, 将目录 directory 下的 invoker 合并成一个统
一的 Invoker,根据不同集群策略的 Cluster 创建不同的 Invoker
我们来看下默认的失败转移,当出现失败重试其他服务的策略, 这个
Cluster 实现很简单就是创建 FailoverCluseterInvoker 对象
public class FailoverCluster implements Cluster {
public final static String NAME = "failover";
public <T> Invoker<T> join(Directory<T> directory) throws
RpcException{
return new FailoverClusterInvoker<T>(directory);
}
}
下图展示了 dubbo 提供的所有集群方案
1) AvailableCluster: 获 取 可 用 的 调 用 。 遍 历 所 有 Invokers 判 断
Invoker.isAvalible,只要一个有为 true 直接调用返回,不管成不成功
2) BroadcastCluster: 广播调用。遍历所有 Invokers, 逐个调用每个调用
catch 住异常不影响其他 invoker 调用
3) FailbackCluster: 失败自动恢复, 对于 invoker 调用失败, 后台记录失败
请求,任务定时重发, 通常用于通知
4) FailfastCluster: 快速失败,只发起一次调用,失败立即保错,通常用于非
幂等性操作
5) FailoverCluster: 失败转移,当出现失败,重试其它服务器,通常用于读操
作,但重试会带来更长延迟
( 1) 目录服务 directory.list(invocation) 列出方法的所有可调用服务
获取重试次数,默认重试两次
( 2) 根据 LoadBalance 负载策略选择一个 Invoker
( 3) 执行 invoker.invoke(invocation)调用
( 4) 调用成功返回调用失败小于重试次数,重新执行从 3)步骤开始执行
调用次数大于等于重试次数抛出调用失败异常
6) FailsafeCluster: 失败安全,出现异常时,直接忽略,通常用于写入审计日
志等操作。
7) ForkingCluster: 并行调用,只要一个成功即返回,通常用于实时性要求较
高的操作,但需要浪费更多服务资源。
8) MergeableCluster: 分组聚合, 按组合并返回结果,比如菜单服务,接口一
样,但有多种实现,用 group 区分,现在消费方需从每种 group 中调用一次
返回结果,合并结果返回,这样就可以实现聚合菜单项。
这个还蛮有意思,我们分析下是如何实现的
( 1) 根据 MERGE_KEY 从 url 获取参数值
( 2) 为空不需要 merge, 正常调用
( 3) 按 group 分组调用,将返回接口保存到集合中
( 4) 获取 MERGE_KEY 如果是默认的话,获取默认 merge策略主要根据返
回类型判断
( 5) 如果不是,获取自定义的 merge 策略
( 6) Merge 策略合并调用结果返回
9) MockClusterWrapper: 具备调用 mock 功能是其他 Cluster 包装
获取 url 的 MOCK_KEY 属性
( 1) 不存在直接调用其他 cluster
( 2) 存在值 startsWith("force") 强制 mock调用
( 3) 存在值不是 startsWith("force") 先正常调用,出现异常在mock调用
集群模式的配置
<dubbo:service cluster="failsafe" /> 服务提供方
<dubbo:reference cluster="failsafe" /> 服务消费方

 

[code]二: 目录服务 Directory
集群目录服务 Directory, 代表多个 Invoker, 可以看成 List<Invoker>, 它的值可能
是动态变化的比如注册中心推送变更。集群选择调用服务时通过目录服务找到所有服务
1.Directory 的接口定义
public interface Directory<T> extends Node {
//服务类型
Class<T> getInterface();
//列出所有服务的可执行对象
List<Invoker<T>> list(Invocation invocation) throwsRpcException;
}

2.Directory有两个具体实现
1.StaticDirectory: 静态目录服务
它的所有Invoker通过构造函数传入,服务消费方引用服务的时候,服务对多注册中心的引用,
将Invokers集合直接传入StaticDirectory构造器,再由Cluster伪装成一个InvokerStaticDirectory
的list方法直接返回所有invoker集合
2.RegistryDirectory: 注册目录服务
它的Invoker集合是从注册中心获取的,
它实现了NotifyListener接口实现了回调接口notify(List<Url>)。
比如消费方要调用某远程服务,会向注册中心订阅这个服务的所有服务提供
方,订阅时和服务提供方数据有变动时回调消费方的NotifyListener服务的
notify方法NotifyListener.notify(List<Url>) 回调接口传入所有服务的提供方的
url地址然后将urls转化为invokers, 也就是refer应用远程服务
到此时引用某个远程服务的RegistryDirectory中有对这个远程服务调用的所
有invokers。
3.registryDirectory.list(invocation)就是根据服务调用方法获取所有的远程服务引用
的invoker 执行对象

 

[code]三:路由
Router服务路由, 根据路由规则从多个Invoker中选出一个子集AbstractDirectory
是所有目录服务实现的上层抽象, 它在list列举出所有invokers后,会在通过
Router服务进行路由过滤。
Router接口定义
public interface Router extends Comparable<Router> {
URL getUrl();<T> List<Invoker<T>> route(List<Invoker<T>>
invokers, URL url, Invocation invocation)throws RpcException;
}
1.ConditionRouter: 条件路由
我们这里简单分析下代码实现具体功能参考官方文档
条件表达式以 => 分割为whenRule和thenRule
ConditionRouter创建,构造器初始
1) 从url根据RULE_KEY获取路由条件路由内容
2) rule.indexOf("=>") 分割路由内容
3) 分别调用parseRule(rule) 解析路由为whenRule和thenRules
ConditionRouter执行route方法
1) 如果url不满足when条件即过来条件, 不过滤返回所有invokers
2) 遍历所有invokers判断是否满足then条件, 将满足条件的加入集合result
3) Result不为空,有满足条件的invokers返回
4) Result为空, 没有满足条件的invokers, 判断参数FORCE_KEY是否强制过
来,如果强制过滤返回空, 不是返回所有即不过滤

2.ScriptRouter: 脚本路由,
通过url的RULE_KEY参数获取脚本内容,然后通过java的脚本引擎执行脚本代
码, dubbo的测试用例都是通过javascript作为脚本但是理论上也支持groovy,
jruby脚本,大家可以参考下测试用例ScriptRouterTest。ScriptRouter创建,构造器初始化
1) 从url获取脚本类型javascript, groovy等等
2) 从url根据RULE_KEY获取路由规则内容
3) 根据脚本类型获取java支持的脚本执行引擎

3.ScriptRouter执行route方法
1) 执行引擎创建参数绑定
2) 绑定执行的参数
3) 执行引擎编译路由规则得到执行函数CompiledScript
4) CompiledScript.eval(binds) 根据参数执行路由规则

4.Dubbo提供了ConditionRouterFactory, ScriptRouterFactory来创建对应的路由,
路由的规则从url的RULE_KEY参数来获取,路由规则可以通过监控中心或者治
理中心写入注册中心
Dubbo 也支持通过 FileRouterFactory 从文件读取路由规则,将读取的规则设置到
url 的 RULE_KEY 参数上, 文件的后缀代表了路由的类型,选择具体的路由工厂
ConditionRouterFactory, ScriptRouterFactory 来创建路由规则
[code]四:负载均衡
LoadBalance负载均衡, 负责从多个 Invokers中选出具体的一个Invoker用于本次
调用,调用过程中包含了负载均衡的算法,调用失败后需要重新选择
1.LoadBalance接口定义
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {
@Adaptive("loadbalance")
<T> Invoker<T> select(List<Invoker<T>>invokers, URL url, Invocation invocation)
throws RpcException;
}
类注解@SPI说明可以基于Dubbo的扩展机制进行自定义的负责均衡算法实
现,默认是随机算法
方法注解@Adaptive说明能够生成设配方法
Select方法设配类通过url的参数选择具体的算法, 在从invokers集合
中根据具体的算法选择一个invoker
2.RandomLoadBalance: 随机访问策略,按权重设置随机概率,是默认策略
1) 获取所有invokers的个数
2) 遍历所有Invokers, 获取计算每个invokers的权重,并把权重累计加起来
每相邻的两个invoker比较他们的权重是否一样,有一个不一样说明权重
不均等
3) 总权重大于零且权重不均等的情况下
按总权重获取随机数offset = random.netx(totalWeight);
遍历invokers确定随机数offset落在哪个片段(invoker上)4) 权重相同或者总权重为0,根据
invokers个数均等选择 invokers.get(random.nextInt(length))

3.RoundRobinLoadBalance: 轮询,按公约后的权重设置轮询比率
1) 获取轮询key 服务名+方法名
获取可供调用的invokers个数length
设置最大权重的默认值maxWeight=0
设置最小权重的默认值minWeight=Integer.MAX_VALUE
2) 遍历所有Inokers,比较出得出maxWeight和minWeight
3) 如果权重是不一样的根据key获取自增序列
自增序列加一与最大权重取模默认得到currentWeigth
遍历所有invokers筛选出大于currentWeight的invokers
设置可供调用的invokers的个数length
4) 自增序列加一并与length取模,从invokers获取invoker

4.LeastActiveLoadBalance: 最少活跃调用数, 相同的活跃的随机选择,
活跃数是指调用前后的计数差, 使慢的提供者收到更少的请求,因为越慢
的提供者前后的计数差越大。
活跃计数的功能消费者是在ActiveLimitFilter中设置的
最少活跃的选择过程如下:
1) 获取可调用invoker的总个数初始化最小活跃数,相同最小活跃的个数
相同最小活跃数的下标数组等等
2) 遍历所有invokers, 获取每个invoker的获取数active和权重
找出最小权重的invoker
如果有相同最小权重的inovkers, 将下标记录到数组leastIndexs[]数组中
累计所有的权重到totalWeight变量
3) 如果invokers的权重不相等且totalWeight大于0
按总权重随机offsetWeight = random.nextInt(totalWeight)
计算随机值在哪个片段上并返回invoker
4) 如果invokers的权重相等或者totalWeight等于0,均等随机

5.ConsistentHashLoadBalance:一致性hash, 相同参数的请求总是发到同一个提供者,
当某一台提供者挂时,原本发往该提供者的请求,基于虚拟节点,平摊到其它提
供者,不会引起剧烈变动。ConsistentashLoadBalance中对一致性哈希算法的实现,
还是比较通俗易懂的这里

 

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: