【Dubbo】Cluster 层集群容错

叁歲伎倆 2023-01-19 07:29 223阅读 0赞

主要内容 :

  • 普通容错策略的实现 ;
  • Merger 的实现原理 ;
  • Mock 的实现原理 。

讲解普通容错策略的实现原理 , 如 Failover、 Failfast 等策略 ; 此外 , 还会讲解特殊的集群容错策略 Merger 和 Mock 的实现原理 。

1 容错机制的实现

Cluster 接口一共有 9 种不同的实现 , 每种实现分别对应不同的 Clusterlnvoker。 本节会介绍继承了 Abstractclusterinvoker 的 7 种 Clusterinvoker 实现 , Merge 和 Mock 属于特殊机制 ,会在后面讲解 。

1.1 容错机制概述

Dubbo 容错机制能增强整个应用的鲁棒性 , 容错过程对上层用户是完全透明的 , 但用户也可以通过不同的配置项来选择不同的容错机制 。 每种容错机制又有自己个性化的配置项 。 Dubbo中现有 Failover 、Failfast 、 Failsafe 、 Fallback 、 Forking 、 Broadcast 等容错机制 , 容错机制的特性
在这里插入图片描述
Cluseter 的具体实现:用户可以在 > 、 标签上通过 cluster 属性设置 。

对于 Failover 容错模式 , 用户可以通过 retries 属性来设置最大重试次数 。 可以设置在dubbo: reference 标签上 , 也可以设置在细粒度的方法标签 dubbo:method 上 。

对于 Forking 容错模式 , 用户可通过 forks=”最大并行数 ” 属性来设置最大并行数 。 假设设置的 forks 数为 n 可用的服务数为 v, 当 n < v时 , 即可用的服务数大于配置的并行数 , 则并行请求 n 个服务 ; 当n > v 时 , 即可用的服务数小于配置的并行数 ,则请求所有可用的服务 v。

对于 Mergeable 容错模式 , 用可以在 dubbo:reference 标签中通过 merger=“true” 开启 , 合并时可以通过 group=”*“ 属性指定需要合并哪些分组的结果 。 默认会根据方法的返回值自动匹配合并器 , 如果同一个类型有两个不同的合并器实现 , 则需要在参数中指定合并器的名字( merger= “ 合并器名 “ ) 。 例如:用户根据某 List 类型的返回结果实现了多个合并器 , 则需要手动指定合并器名称 , 否则框架不知道要用哪个 。 如果想调用返回结果的指定方法进行合并 ( 如返回了一个 Set, 想调用 Set#addAll 方法 ) , 则可以通过 merger=” .addAll’ 配置来实现 。

官方 Mergeable 配置示例
在这里插入图片描述

1.2 Cluster 接口关系

在微服务环境中 , 可能多个节点同时都提供同一个服务 。 当上层调用 Invoker 时 , 无论实际存在多少个 Invoker, 只需要通过 Cluster 层 , 即可完成整个调用的容错逻辑 , 包括获取服务列表 、 路由 、 负载均衡等 , 整个过程对上层都是透明的 。 当然 , Cluster 接口只是串联起整个逻辑 ,其中 Clusterlnvoker 只实现了容错策略部分 , 其他逻辑则是调用了 Directory 、 Router 、LoadBalance 等接口实现 。

容错的接口主要分为两大类 , 第一类是 Cluster 类 , 第二类是 Clusterinvoker 类 。 Cluster 和Clusterinvoker 之间的关系也非常简单 : Cluster 接口下面有多种不同的实现 , 每种实现中都需要实现接口的 join 方法 , 在方法中会 “ new ” 一个对应的 Clusterinvoker 实现 。 我们以 FailoverCluster实现为例进行说明

Cluster 与 Clusterinvoker 之间的关系示例
在这里插入图片描述
FailoverCluster 是 Cluster 的其中一种实现 , FailoverCluster 中直接创建了一个新的FailoverClusterlnvoker 并返回 。

现在我们再次看一下创建 ClusterInvoker 的时机,在 referenceBean 的 afterPropertySet 里会一直调用到 createProxy,在 createProxy 中会根据 reference 的 url 去构造 invoker。
在这里插入图片描述

在理解集群容错的详细原理之前 , 我们先从 “ 上帝视角 ” 看一下整个集群容错的接口关系 。
在这里插入图片描述
Cluster 是最上层的接口 , 下面一共有 9 个实现类 。 Cluster 接口上有 SPI 注解 , 也就是说 ,实现类是通过扩展机制动态生成的 。 每个实现类里都只有一个 join 方法 , 实现也很简单 , 直接 “ new ” 一个对应的 Clusterinvoker。 其中 AvailableCluster 例外 , 直接使用匿名内部类实现了所有功能 。

接下来 , 我们来看一下 Clusterinvoker 的类结构 , 如图所示
在这里插入图片描述

Invoker 接口是最上层的接口 , 它下面分别有 AbstractClusterlnvoker 、 MockClusterlnvoker和 MergeableClusterlnvoker H 个类 。 其中 , AbstractClusterlnvoker 是一个抽象类 , 其封装了通用的模板逻辑 , 如获取服务列表 、 负载均衡 、 调用服务提供者等 , 并预留了一个 dolnvoke 方法需要子类自行实现 。 AbstractClusterlnvoker 下面有 7 个子类 , 分别实现了不同的集群容错机制 。

MockClusterlnvoker 和 MergeableClusterlnvoker 由于并不适用于正常的集群容错逻辑 , 因此没有挂在 AbstractClusterlnvoker 下面 , 而是直接继承了 Invoker 接口 。以上就是容错的接口 , Directory 、 Router 和 LoadBalance 的接口会在后面讲解 。

1.3 Failover 策略

Cluster 接口上有 SPI 注 W@SPI(FailoverCluster . NAME ), 即默认实现是 Failover 。 该策略的代码逻辑如下 :

  • (1) 校验 。 校验从 AbstractClusterlnvoker 传入的 Invoker 列表是否为空 。
  • (2) 获取配置参数 。 从调用 URL 中获取对应的 retries 重试次数 。
  • (3) 初始化一些集合和对象 。 用于保存调用过程中出现的异常 、 记录调用了哪些节点(这个会在负载均衡中使用 , 在某些配置下 , 尽量不要一直调用同一个服务) 。
  • (4) 使用 for 循环实现重试 , for 循环的次数就是重试的次数 。 成功则返回 , 否则继续循环 。如果 for 循环完 , 还没有一个成功的返回 , 则抛出异常 , 把 (3) 中记录的信息抛出去 。前 3 步都是做一些校验 、 数据准备的工作 。 第 4 步开始真正的调用逻辑 。 以下步骤是 for循环中的逻辑 :

    • 校验 。 如果 for 循环次数大于 1 , 即有过一次失败 , 则会再次校验节点是否被销毁 、 传入的 Invoker 列表是否为空 。
    • 负载均衡 。 调用 select 方法做负载均衡 , 得到要调用的节点 , 并记录这个节点到步骤 3的集合里 , 再把己经调用的节点信息放进 RPC 上下文中 。
    • 远程调用 。 调用 invoker#invoke 方法做远程调用 , 成功则返回 , 异常则记录异常信息 ,再做下次循环 。

Failover 流程如图所示
在这里插入图片描述

1.4 Failfast 策略

Failfast 会在失败后直接抛出异常并返回 , 实现非常简单 , 步骤如下 :

  • (1) 校验 。 校验从 AbstractClusterlnvoker 传入的 Invoker 列表是否为空 。
  • (2) 负载均衡 。 调用 select 方法做负载均衡 , 得到要调用的节点 。
  • (3) 进行远程调用 。 在 try 代码块中调用 invoker#invoke 方法做远程调用 。 如果捕获到异常 , 则直接封装成 RpcException 抛出 。

整个过程非常简短 , 也不会做任何中间信息的记录 。

1.5 Failsafe 策略

Failsafe 调用时如果出现异常 , 则会直接忽略 。 实现也非常简单 , 步骤如下 :

  • (1) 校验传入的参数 。 校验从 AbstractClusterlnvoker 传入的 Invoker 列表是否为空 。
  • (2) 负载均衡 。 调用 select 方法做负载均衡 , 得到要调用的节点
  • (3) 远程调用 。 在 try 代码块中调用 invoker#invoke 方法做远程调用 , “ catch ” 到任何异常都直接 “ 吞掉 ” , 返回一个空的结果集 。

Failsafe 调用源码
在这里插入图片描述

1.6 Fallback 策略

Fallback 如果调用失败 , 则会定期重试 。 FailbackClusterlnvoker 里面定义了一个ConcurrentHashMap, 专门用来保存失败的调用 。 另外定义了一个定时线程池 , 默认每 5 秒把所有失败的调用拿出来 , 重试一次 。 如果调用重试成功 , 则会从 ConcurrentHashMap 中移除 。

dolnvoke 的调用逻辑如下 :

  • (1) 校验传入的参数 。 校验从 AbstractClusterlnvoker 传入的 Invoker 列表是否为空 。
  • (2) 负载均衡 。 调用 select 方法做负载均衡 , 得到要调用的节点 。
  • (3) 远程调用 。 在 try 代码块中调用 invoker#invoke 方法做远程调用 , “ catch ” 到异常后直接把 invocation 保存到重试的 ConcurrentHashMap 中 , 并返回一个空的结果集 。
  • (4) 定时线程池会定时把 ConcurrentHashMap 中的失败请求拿出来重新请求 , 请求成功则从 ConcurrentHashMap 中移除 。 如果请求还是失败 , 则异常也会被 “ catch ” 住 , 不会影响ConcurrentHashMap 中后面的重试 。

Fallback 重试源码
在这里插入图片描述

1.7 Available 策略

Available 是找到第一个可用的服务直接调用 , 并返回结果 。 步骤如下 :

  • (1) 遍历从 AbstractClusterlnvoker 传入的 Invoker 列表 , 如果 Invoker 是可用的 , 则直接调用并返回 。
  • (2) 如果遍历整个列表还没找到可用的 Invoker, 则抛出异常 。

1.8 Broadcast 策略

Broadcast 会广播给所有可用的节点 , 如果任何一个节点报错 , 则返回异常 。 步骤如下 :

  • (1) 前置操作 。 校验从 AbstractClusterlnvoker 传入的 Invoker 列表是否为空 ; 在 RPC 上下文中设置 Invoker 列表 ; 初始化一些对象 , 用于保存调用过程中产生的异常和结果信息等 。
  • (2) 循环遍历所有 Invoker, 直接做 RPC 调用 。 任何一个节点调用出错 , 并不会中断整个广播过程 , 会先记录异常 , 在最后广播完成后再抛出 。 如果多个节点异常 , 则只有最后一个节点的异常会被抛出 , 前面的异常会被覆盖

1.9 Forking 策略

Forking 可以同时并行请求多个服务 , 有任何一个返回 , 则直接返回 。 相对于其他调用策略 ,Forking 的实现是最复杂的 。 其步骤如下 :

  • (1) 准备工作 。 校验传入的 Invoker 列表是否可用 ; 初始化一个 Invoker 集合 , 用于保存真正要调用的 Invoker 列表 ; 从 URL 中得到最大并行数 、 超时时间 。
  • (2) 获取最终要调用的 Invoker 列表 。 假设用户设置最大的并行数为 n, 实际可以调用的最大服务数为 v 。 如果 v < 0 或n < v, 则说明可用的服务数小于用户的设置 , 因此最终要调用的Invoker 只能有 v 个 ; 如果 n > v, 则会循环调用负载均衡方法 , 不断得到可调用的 Invoker, 加入步骤 1 中的 Invoker 集合里 。这里有一点需要注意 : 在 Invoker 加入集合时 , 会做去重操作 。

    因此 , 如果用户设置的负载均衡策略每次返回的都是同一个 Invoker, 那么集合中最后只会存在一个 Invoker, 也就是只会调用一个节点 。

  • ( 3) 调用前的准备工作 。 设置要调用的 Invoker 列表到 RPC 上下文 ; 初始化一个异常计数器 ; 初始化一个阻塞队列 , 用于记录并行调用的结果 。
  • (4) 执行调用 。 循环使用线程池并行调用 , 调用成功 , 则把结果加入阻塞队列 ; 调用失败 ,则失败计数 +1 。 如果所有线程的调用都失败了 , 即失败计数 >= 所有可调用的 Invoker 时 , 则把异常信息加入阻塞队列 。

    这里有一点需要注意 : 并行调用是如何保证个别调用失败不返回异常信息 , 只有全部失败才返回异常信息的呢? 因为有判断条件 , 当失败计数 N 所有可调用的 Invoker 时 , 才会把异常信息放入阻塞队列 , 所以只有当最后一个 Invoker 也调用失败时才会把异常信息保存到阻塞队列 ,从而达到全部失败才返回异常的效果 。

  • (5) 同步等待结果 。 由于步骤 4 中的步骤是在线程池中执行的 , 因此主线程还会继续往下执行 , 主线程中会使用阻塞队列的 poll(- 超时时间 “ )方法 , 同步等待阻塞队列中的第一个结果 ,如果是正常结果则返回 , 如果是异常则抛出 。

从上面步骤可以得知, Forking 的超时是通过在阻塞队列的 poll 方法中传入超时时间实现的 ;线程池中的并发调用会获取第一个正常返回结果 。 只有所有请求都失败了 , Forking 才会失败 。

Forking 调用流程如图所示
在这里插入图片描述

2 Merger 的实现

当一个接口有多种实现 , 消费者又需要同时引用不同的实现时 , 可以用 group 来区分不同的实现 , 如下所示
在这里插入图片描述
如果我们需要并行调用不同 group 的服务 , 并且要把结果集合并起来 , 贝懦要用到 Merger特性 。 Merger 实现了多个服务调用后结果合并的逻辑 。 虽然业务层可以自行实现这个能力 , 但Dubbo 直接封装到框架中 , 作为一种扩展点能力 , 简化了业务开发的复杂度 。 Merger 的工作方式如图所示
在这里插入图片描述
框架中有一些默认的合并实现 。 Merger 接口上有@SPI 注解 , 没有默认值 , 属于 SPI 扩展点 。 用户可以基于 Merger 扩展点接口实现自己的自定义类型合并器 。

2.1 总体结构

MergerCluster 也是 Cluster 接口的一种实现 , 因此也遵循 Cluster 的设计模式 , 在 invoke 方法中完成具体逻辑 。 整个过程会使用 Merger 接口的具体实现来合并结果集 。 在使用的时候 , 通过 MergerFactory 获得各种具体的 Merger 实现。 Merger 的 12 种默认实现的关系如图所示
在这里插入图片描述
如果开启了 Merger 特性 , 并且未指定合并器 ( Merger 的具体实现) , 则框架会根据接口的返回类型自动匹配合并器 。 我们可以扩展属于自己的合并器 , MergerFactory 在加载具体实现的时候 , 会用 ExtensionLoader 把所有 SPI 的实现都加载到缓存中 。 后续使用时直接从缓存中读取 , 如果读不到则会重新全量加载一次 SPI。

内置的合并我们可以分为四类 : Array 、 Set 、 List 、Map, 实现都比较简单 , 我们只列举 MapMerger 的实现

内置合并器代码示例
在这里插入图片描述
整个实现的思路就是 , 在 Merge 中新建了一个 Map, 把返回的多个 Map 合并成一个 。 其他类型的合并器实现都是类似的 , 因此不再赘述 。

2.2 MergeableClusterlnvoker 机制

MergeableClusterlnvoker 串起了整个合并器逻辑 , 在讲解 MergeableClusterlnvoker 的机制之前 , 我们先回顾一下整个调用的过程 : MergeableCluster#join 方法中直接生成并返回了MergeableClusterlnvoker, MergeableClusterInvoker#invoke 方法又通过 MergerFactory 工厂获取不同的 Merger 接口实现 , 完成了合并的具体逻辑 。

MergeableCluster 并没有继承抽象的 Cluster 实现 , 而是独立完成了自己的逻辑 。 因此 , 它的整个逻辑和之前的 Failover 等机制不同 , 其步骤如下 :

  • (1) 前置准备 。 通过 directory 获取所有 Invoker 列表 。
  • (2) 合并器检查 。 判断某个方法是否有合并器 , 如果没有 , 则不会并行调用多个 group,找到第一个可以调用的 Invoker 直接调用就返回了 。 如果有合并器 , 则进入第 3 步 。
  • (3) 获取接口的返回类型 。 通过反射获得返回类型 , 后续要根据这个返回值查找不同的合并器 。
  • (4) 并行调用 。 把 Invoker 的调用封装成一个个 Callable 对象 , 放到线程池中执行 , 保存线程池返回的 future 对象到 HashMap 中 , 用于等待后续结果返回 。
  • (5) 等待 fixture 对象的返回结果 。 获取配置的超时参数 , 遍历 (4) 中得到的 fixture 对象 ,设置 Future#get 的超时时间 , 同步等待得到并行调用的结果 。 异常的结果会被忽略 , 正常的结果会被保存到 list 中 。 如果最终没有返回结果 , 则直接返回一个空的 RpcResult ; 如果只有一个结果 ,那么也直接返回 , 不需要再做合并 ; 如果返回类型是 void, 则说明没有返回值 , 也直接返回 。
  • (6) 合并结果集 。 如果配置的是 merger^ 1 .addAll”, 则直接通过反射调用返回类型中的 .addAll 方法合并结果集 。 例如 : 返回类型是 Set, 则调用 Set.addAll 来合并结果

调用返回类型的方法合并结果集
在这里插入图片描述
对于要调用合并器来合并的结果集 , 则使用以下逻辑 ,

调用合并器源码
在这里插入图片描述

3 Mock

在 Cluster 中 , 还有最后一个 MockClusterWrapper, 由它实现了 Dubbo 的本地伪装 。 这个功能的使用场景较多 , 通常会应用在以下场景中 : 服务降级 ; 部分非关键服务全部不可用 , 希望主流程继续进行 ; 在下游某些节点调用异常时 , 可以以 Mock 的结果返回 。

3.1 Mock 常见的使用方式

Mock 只有在拦截到 RpcException 的时候会启用 , 属于异常容错方式的一种 。 业务层面其实也可以用 try-catch 来实现这种功能 , 如果使用下沉到框架中的 Mock 机制 , 则可以让业务的实现更优雅 。 常见配置如下
在这里插入图片描述
当接口配置了 Mock, 在 RPC 调用抛出 RpcException 时就会执行 Mock 方法 。 最后一种 return null 的配置方式通常会在想直接忽略异常的时候使用

服务的降级是在 dubbo-admin 中通过 override 协议更新 Invoker 的 Mock 参数实现的 。 如果Mock 参数设置为 mock=force: return+null, 则表明是强制 Mock, 强制 Mock 会让消费者对该服务的调用直接返回 null, 不再发起远程调用 。 通常使用在非重要服务己经不可用的时候 , 可以屏蔽下游对上游系统造成的影响 。 此外 , 还能把参数设置为印 mock=fail:return+null, 这样消费者还是会发起远程调用 , 不过失败后会返回 null, 但是不抛出异常 。最后 , 如果配置的参数是以 throw 开头的 , 即 mock= throw,则直接抛出 RpcException, 不会发起远程调用 。

3.2 Mock 的总体结构

Mock 涉及的接口比较多,整个流程贯穿 Cluster 和 Protocol 层 , 接口之间的逻辑关系如图所示
在这里插入图片描述
从图我们可以得知 , 主要流程分为 Cluster 层和 Protocol 层 。

  • MockClusterWrapper 是一个包装类 , 包装类会被自动注入合适的扩展点实现 , 它的逻辑很简单 , 只是把被包装扩展类作为初始化参数来创建并返回一个 MockClusterlnvoker,因此本节就不再详细讲解 。
  • MockClusterlnvoker 和其他的 Clusterinvoker 一样 , 在 Invoker 方法中完成了主要逻辑 。
  • MocklnvokersSelector 是 Router 接口 的一种实现 , 用于过滤出 Mock 的 Invoker 。
  • MockProtocol 根据用户传入的 URL 和类型生成一个 MockInvoker
  • MockInvoker 实现最终的 Invoker 逻辑 。

Mockinvoker 与 MockClusterlnvoker 看起来都是 Invoker, 它们之间有什么区别呢 ?首先 , 强制 Mock 、 失败后返回 Mock 结果等逻辑是在 MockClusterlnvoker 里处理的 ; 其次 ,MockClusterlnvoker 在某些逻辑下 , 会生成 Mockinvoker 并进行调用 ; 然后 , 在 Mockinvoker里会处理 mock= “return null”、 mock=“throw xxx” 或 mock=com.xxService 这些配置逻辑 。 最后 ,Mockinvoker 还会被 MockProtocol 在引用远程服务的时候创建 。 我们可以认为 ,MockClusterlnvoker 会处理一些 Class 级别的 Mock 逻辑 , 例如 : 选择调用哪些 Mock 类 。Mockinvoker 处理的是方法级别的 Mock 逻辑 , 如返回值 。

3.3 Mock 的实现原理

1. MockClusterlnvoker 的实现原理

MockClusterWrapper 是一个包装类 , 它在创建 MockClusterlnvoker 的时候会把被包装的 Invoker 传入构造方法 , 因此 MockClusterlnvoker 内部天生就含有一个 Invoker 的引用 。
在这里插入图片描述

MockClusterlnvoker 的 invoke 方法处理了主要逻辑 , 步骤如下 :
在这里插入图片描述

  • (1) 获取 Invoker 的 Mock 参数 。 前面已经说过 , 该 Invoker 是在构造方法中传入的 。 如果该 Invoker 根本就没有配置 Mock, 则直接调用 Invoker 的 invoke 方法并把结果返回 ; 如果配置了 Mock 参数 , 则进入下一步 。
  • (2) 判断参数是否以 force 开头 , 即判断是否强制 Mock 。 如果是强制 Mock, 则进入doMocklnvoke 逻辑 , 这部分逻辑在后面统一讲解 。 如果不以 force 开头 , 则进入失败后 Mock的逻辑 。
  • (3) 失败后调用 doMocklnvoke 逻辑返回结果 。 在 try 代码块中直接调用 Invoker 的 invoke方法 , 如果抛出了异常 , 则在 catch 代码块中调用 doMocklnvoke 逻辑 。强制 Mock 和失败后 Mock 都会调用 doMocklnvoke 逻辑 , 其步骤如下 :

    • (1) 通过 selectMocklnvoker 获得所有 Mock 类型的 Invoker(即 mock 协议的 provider)。 selectMocklnvoker 在对象的 attachment 属性中偷偷放进一个 invocation.need.mock=true 的标识 。 directory 在 list 方法中列出所有 Invoker 的时候 , 如果检测到这个标识 , 则使用 MockinvokersSelector 来过滤 Invoker, 而不是使用普通 route 实现 , 最后返回 Mock 类型的 Invoker 列表 。 如果一个 Mock 类型的 Invoker都没有返回 , 则通过 directory 的 URL 新创建一个 Mockinvoker ; 如果有 Mock 类型的 Invoker,则使用第一个 。
    • (2) 调用 Mockinvoker 的 invoke 方法 。 在 try-catch 中调用 invoke 方法并返回结果 。 如果出现了异常 , 并且是业务异常 , 则包装成一个 RpcResult 返回 , 否则返回 RpcException 异常

2. MocklnvokersSelector 的实现原理

在 doMocklnvoke 的第 1 步中 , directory 会使用 MocklnvokersSelector 来过滤出 Mock 类型的 Invoker 。 MocklnvokersSelector 是 Router 接口的其中一种实现 。 它路由时的具体逻辑如下 :

  • (1) 判断是否需要做 Mock 过滤 。 如果 attachment 为空 , 或者没有 invocation.need.mock=true的标识 , ‘则认为不需要做 Mock 过滤 , 进入步骤 2 ; 如果找到这个标识 , 则进入步骤 3 。
  • (2) 获取非 Mock 类型的 Invoker 。 遍历所有的 Invoker, 如果它们的 protocol 中都没有 Mock参数 , 则整个列表直接返回 。 否则 , 把 protocol 中所有没有 Mock 标识的取出来并返回 。
  • (3) 获取 Mock 类型的 Invoker。 遍历所有的 Invoker, 如果它们的 protocol 中都没有 Mock参数 , 则直接返回 null 。 否则 , 把 protocol 中所有含有 Mock 标识的取出来并返回 。

3. MockProtocol 与 Mockinvoker 的实现原理

MockProtocol 也是协议的一种 , 主要是把注册中心的 Mock URL 转换为 Mockinvoker 对象 。URL 可以通过 dubbo.admin 或其他方式写入注册中心 , 它被定义为只能引用 , 不能暴露

MockProtocol 源码
在这里插入图片描述
例如 , 我们在注册中心 /dubbo/com.test . xxxService/providers 这个服务提供者的目录下 ,写入一个 Mock 的 URL : mock:// 192.168.0. 123/com.test.xxxService 。在 Mockinvoker 的 invoke 方法中 , 主要处理逻辑如下 :

  • (1) 获取 Mock 参数值 。 通过 URL 获取 Mock 配置的参数 , 如果为空则抛出异常 。 优先会获取方法级的 Mock 参数 , 例如 : 以 methodName.mock 为 key 去获取参数值 ; 如果取不到 ,则尝试以 mock 为 key 获取对应的参数值 。
  • (2) 处理参数值是 return 的配置 。 如果只配置了一个 return, 即 mock=return, 则返回一个空的 RpcResult ; 如果 return 后面还跟了别的参数 , 则首先解析返回类型 , 然后结合 Mock 参数和返回类型 , 返回 Mock 值 。 现支持以下类型的参数 : Mock 参数值等于 empty, 根据返回类型返回 new xxx() 空对象 ; 如果参数值是 null> true> false, 则直接返回这些值 ; 如果是其他字符串 , 则返回字符串 ; 如果是数字 、 List 、 Map 类型 , 则返回对应的 JSON 串 ; 如果都没匹配上 ,则直接返回 Mock 的参数值 。
  • (3) 处理参数值是 throw 的配置 。 如果 throw 后面没有字符串 , 则包装成一个 RpcException异常 , 直接抛出 ; 如果 throw 后面有自定义的异常类 , 则使用自定义的异常类 , 并包装成一个RpcException 异常抛出 。
  • (4) 处理 Mock 实现类 。 先从缓存中取 , 如果有则直接返回 。 如果缓存中没有 , 则先获取接口的类型 , 如果 Mock 的参数配置的是 true 或 default, 则尝试通过 “ 接口名 +Mock ” 查找Mock 实现类 , 例如 : TestService 会查找 Mock 实现 TestServiceMock 如果是其他配置方式 , 则通过 Mock 的参数值进行查找 , 例如 : 配置了 mock=com.xxx.testservice , 则会查找 com.xxx.testservice

小结

讲解了 7 种普通集群容错策略的实现原理 — 都使用 了模板模式 , 继承了 AbstractClusterlnvoker , 在 AbstractClusterlnvoker中完成了总体的抽象逻辑 , 并留了一个抽象方法让子类实现自己的独特功能 。
接着讲解了特殊容错机制 Merger, 包含默认合并器的总体大图 , 以及具体 Merge 的实现步骤 。最后讲解了 Mock 机制的实现 , 分为 Cluster 层的逻辑线 , 以及 Protocol 层的逻辑线

发表评论

表情:
评论列表 (有 0 条评论,223人围观)

还没有评论,来说两句吧...

相关阅读

    相关 dubbo容错

    为了避免单点故障,现在的应用通常至少会部署在两台服务器上。对于一些负载比较高的服务,会部署更多的服务器。这样, 在同一环境下的服务提供者数量会大于1。对于服务消费者来说,同一环

    相关 redis 容错策略

    1. 问题 > 1) redis 集群如果一个主节点挂了,会怎么处理 > > 2) redis集群如果一个从节点挂了,会怎么处理 > > 3) 如何判断整个redis集群

    相关 谈谈dubbo容错

    从继承图可以看出有以下几种策略: Failover Cluster(失败转移) 当调用提供者的服务器发生错误时,再试下一个服务器。 用于读操作。重试有延时。设置重试次数

    相关 Dubbo容错

    Dubbo官网:[dubbo 2.7  集群容错][dubbo 2.7_] 集群容错指的是,当消费者调用提供者集群时发生异常的处理方案 一、Dubbo内置的容错策略

    相关 Dubbo-容错

    当一个项目(应用或服务)放在多台服务器上时,怎么实现高可用,这就是集群容错。 \------------------------------------------

    相关 Dubbo——容错

    Cluster层概述 在微服务环境中,为了保证服务的高可用,很少会有单点服务出现,服务通常都是以集群的形式出现的。然而,被调用的远程服务并不是每时每刻都保持良好状况,当某