Cluster层概述

在微服务环境中,为了保证服务的高可用,很少会有单点服务出现,服务通常都是以集群的形式出现的。然而,被调用的远程服务并不是每时每刻都保持良好状况,当某个服务调用出现异常时,如网路抖动、服务短暂不可用需要自动容错,或者只想本地测试、服务降级、需要Mock返回结果,就需要使用集群容错机制。

我们可以把Cluster看作一个集群容错层,该层中包含Cluster、Directory、Router、Loadbalance几大核心接口。注意这里要区分Cluster层和Cluster接口,Cluster层是抽象概念,表示的是对外的整个集群容错层;Cluster是容错接口,提供Failover、Failfast等容错策略。

由于Cluster层的实现众多,因此本次流程是基于AbstrctClusterInvoker的全量流程,某些实现,某些实现可能只使用了该流程的一小部分。Cluster的总体工作流程可以分为以下几步:

  1. 生成Invoker对象。不同的Cluster实现会生成不同类型的ClusterInvoker对象并返回。然后调用ClusterInvoker的Invoker方法,正式开始调用流程。
  2. 获得可调用的服务列表。首先会做前置校验,检查远程服务是否已被销毁。然后通过Directory#list方法获取所有可用的服务列表。接着使用Router接口处理该服务列表,根据路由规则过滤一部分服务,最终返回剩余的服务列表。
  3. 做负载均衡。在第2步中得到的服务列表还需要通过不同的负载均衡策略选出一个服务,用作最后的调用。首先框架会根据用户的配置,调用ExtensionLoader获取不同负载均衡的策略的扩展点实现。然后做一些后置操作,如果是异步调用则设置调用编号。接着调用子类实现的doInvoke方法(父类专门留了这个抽象方法让子类实现),子类会根据具体的负载均衡策略选出一个可以调用的服务。
  4. 做RPC调用。首先保存每次调用的Invoker到RPC上下文,并做RPC调用。然后处理调用结果,对于调用出现的异常、成功、失败等情况,每种容错策略会有不同的处理方式。

总体调用流程如下图:

dubbo reference cluster设置 dubbo的cluster_子类


上图是一个全量的通用流程,其中1~3步都是在抽象方法AbstractClusterInvoker中实现的,可以裂解为通用的模板流程,主要做了校验、参数准备等工作,最终调用子类实现的doInvoke方法。不同的ClusterInvoker子类都继承了该抽象类,子类会在上述流程中做个性化的裁剪。

容错机制的实现

Cluster接口一共有9种不同的实现,每种实现分别对应不同的ClusterInvoker。主要介绍继承了AbstractClusterInvoker的7中ClusterInvoker实现,Merge和Mock属于特殊机制。

容错机制概述

Dubbo容错机制能增强整个应用的鲁棒性,容错对于上层用户是完全透明的,但用户也可以通过不同的配置项来选择不同的容错机制。每种容错机制又有自己的个性化配置项。

容错机制的特性:

机制名

机制简介

Failover

当出现失败时,会重试其他服务器。用户可以通过retreies="2"设置重试次数。这个Dubbo的默认容错机制,会对请求做负载均衡。通常是会用在读操作或幂等的写操作上,但重试会导致接口的延迟增大,在下游机器负载已经达到极限时,重试容易家中下游服务的负载。

Failfast

快速失败,当请求失败后,快速返回异常结果,不做任何重试。该容错机制会对请求做负载均衡,通常使用在非幂等接口的调用上。该机制受网络抖动的影响 较大。

Failsafe

当出现异常时,直接忽略异常。会对请求做负载均衡。通常使用在"佛系"调用场景,即不关心调用是否成功,并且不想抛异常影响外层调用,如某些不重要的日志同步,及时出现异常也无所谓。

Failback

请求失败后,会自动记录在失败队列中,并由一个定时线程池定时重试,适用于一些异步或最终一致性的请求。请求会做负载均衡。

Forking

同时调用多个相同的服务,只要有其中一个返回,则立即返回结果。用户可以设置forks="最大并行调用数"参数来确定最大并行调用的服务数量。通常使用在对接口实时性要求极高的调用上,但是也会浪费更多的资源。

Broadcast

广播所有可用的服务,任意一个节点报错则报错。由于是广播,因此请求不需要做负载均衡。通常用于服务状态更新后的广播

Mock

提供调用失败时,返回伪造的响应结果。或直接强制返回伪造结果,不会发起远程调用

Available

最简单的方式,请求不会做负载均衡,遍历所有服务列表,找到第一个可用的节点,直接请求并返回结果。如果没有可用节点,则直接抛出异常。

Mergeable

Mergeable可以自动把多个节点请求得到的结果进行合并

Cluster的Juin实现:用户可以在<dubbo:service>、<dubbo:reference>、<dubbo:consumer>、<dubbo:provider>标签上通过cluster属性设置。

对于Failover容错模式,用户可以通过retries属性来设置最大重试次数。可以设置在dubbo:reference标签上,也可以设置在细粒度的方法标签ubbo: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配置示例:

<!-- 搜索所有分组,根据返回结果的类型自动查找合并器。该接口中getMenuItems方法不做合并 -->
<dubbo:reference interface="com.xxx.MenuService" group="*" merger="true">
	<dubbo:mthod name="getMenuItems" merger="false"/>
</dubbo:reference>

<dubbo:reference interface="com.xxx.MenuService" group="*">
	<!-- 指定方法合并结果 -->
	<dubbo:method name="getMenuItems" merger="mymerge" />
</dubbo:reference>

<dubbo:reference interface="com.xxx.MenuService" group="*">
	<!-- 调用返回结果的指定方法进行合并 -->
	<dubbo:method name="getMenuItems" merger=".addAll"  />
</dubbo:reference>