目录
1、背景
2、集群容错
3、源码分析
3.1、FailoverClusterInvoker
3.2、 FailbackClusterInvoker
3.3、 FailfastClusterInvoker
3.4、 FailsafeClusterInvoker
3.5、 ForkingClusterInvoker
3.6、 BroadcastClusterInvoker
一、背景
为了增强系统的高可用性,现在的应用通常至少会部署在两台服务器以上。甚至对于一些负载比较高的服务,会部署更多的服务器。这样对于消费端来说,发起客户端请求之后,即使调用的服务某一个服务结点宕机了,但是仍然能够从可用的结点中选取一个节点继续提供服务。但是这时会出现一个问题,服务消费者需要决定选择哪个服务提供者进行调用。另外服务调用失败之后是重试呢,还是抛出异常,或者是只打印异常等。
为此,Dubbo设计了集群模块,提供了各种Cluster Invoker处理集群调用及异常处理等场景。集群模块形象的可以理解为服务提供者和服务消费者的中间管理层,为服务消费者屏蔽了服务提供者的具体实现细节,这样服务消费者就可以专心处理远程调用相关事宜。比如发起请求,接受服务提供者返回的数据等。这就是集群要做的事情。
Dubbo 提供了多种集群实现,包含但不限于
-
Failover Cluster
Failfast Cluster
Failsafe Cluster
Failback Cluster
Forking Cluster
Broadcast Cluster
接下来会对其实现细节逐一分析。
二、集群容错
在分析集群容错源代码之前,有必要首先搞清楚集群容错相关的组件及集群容错大致流程,对集群容错有个大致的框架性的认识,再去看具体的实现细节才能更加游刃有余。
集群容错的所有组件,包含 Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等。示意图如下:
集群发挥作用大致在两个时机,第一个是在 消费者引用服务的时候 ,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。这部分内容在服务引用文章中已经介绍过,代码部分如下:private Invoker doRefer(Cluster cluster, Registry registry, Classtype, URL url) { //省略部分代码 // 一个注册中心可能有多个服务提供者,因此这里需要将多个服务提供者合并为一个 Invoker invoker = cluster.join(directory); ProviderConsumerRegTable.registerConsuemr(invoker, url, subscribeUrl, directory); return invoker; }
public class FailoverCluster implements Cluster { public final static String NAME = "failover"; public Invokerjoin(Directory directory) throws RpcException { return new FailoverClusterInvoker(directory); } }
第二个阶段是在服务消费者进行远程调用时。以默认实现FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用服务目录Directory 的 list 方法列举 Invoker 列表。Directory 的实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 LoadBalance从Invoker 列表中选择一个Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。
以上就是集群工作的整个流程,下面开始分析源码。
三、源码分析
客户端在发起请求进行服务调用的时候各种 Cluster Invoker 的父类是AbstractClusterInvoker,首先会调用AbstractClusterInvoker的invoke方法,获取 Invoker列表,负载均衡等操作均会在此阶段被执行。因此下面先来看一下 invoke 方法的逻辑。public Result invoke(final Invocation invocation) throws RpcException { checkWhetherDestroyed(); LoadBalance loadbalance = null; // 列举 Invoker List> invokers = list(invocation); if (invokers != null && invokers.size() > 0) { loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(invokers.get(0).getUrl().getMethodParameter(invocation.getMethodName(), Constants.LOADBALANCE_KEY, Constants.DEFAULT_LOADBALANCE)); } else { loadbalance = ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(Constants.DEFAULT_LOADBALANCE); } RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation); // 调用 doInvoke 进行后续操作 return doInvoke(invocation, invokers, loadbalance); }
AbstractClusterInvoker主要获取到invoker列表,如果invoker列表不为空,则从invoker的url中根据配置取负载均衡参数进行动态生成负载均衡实现类,否则就取默认的负载均衡实现类。然后最后调用doInvoke进行后续操作。
protected abstract Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException;
doInvoke是父类提供的模板方法,供子类进行实现,接下来我们就分析子类是如何实现doInvoke进行后续操作的。
①、FailoverClusterInvoker
规则及使用场景:
FailoverClusterInvoker 在调用失败时,会自动切换可用的Invoker 进行重试。
默认配置下,Dubbo 会使用这个类作为缺省 Cluster Invoker。下面来看一下该类的逻辑。
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class); public FailoverClusterInvoker(Directory directory) { super(directory); } @SuppressWarnings({"unchecked", "rawtypes"}) public Result doInvoke(Invocation invocation, final List> invokers, LoadBalance loadbalance) throws RpcException { List> copyinvokers = invokers; checkInvokers(copyinvokers, invocation); // 获取重试次数 int len = getUrl().getMethodParameter(invocation.getMethodName(), Constants.RETRIES_KEY, Constants.DEFAULT_RETRIES) + 1; if (len <= 0) { len = 1; } // 循环调用,失败重试 // 成功调用之后的最后一次失败调用的异常 RpcException le = null; // 被调用的调用者 List> invoked = new ArrayList>(copyinvokers.size()); Set providers = new HashSet(len); for (int i = 0; i < len; i++) { //重试时,进行重新选择,避免重试时invoker列表已发生变化. //注意:如果列表发生了变化,那么invoked判断会失效,因为invoker实例已经改变 if (i > 0) { checkWhetherDestroyed(); // 在进行重试前重新列举 Invoker,这样做的好处是,如果某个服务挂了, // 通过调用 list 可得到最新可用的 Invoker 列表 copyinvokers = list(invocation); checkInvokers(copyinvokers, invocation); } // TODO 开始执行负载均衡逻辑 Invoker invoker = select(loadbalance, invocation, copyinvokers, invoked); // 添加到 invoker 到 invoked 列表中 invoked.add(invoker); // 设置 invoked 到 RPC 上下文中 RpcContext.getContext().setInvokers((List) invoked); try { // 调用目标 Invoker 的 invoke 方法 Result result = invoker.invoke(invocation); if (le != null && logger.isWarnEnabled()) { logger.warn("Although retry the method " + invocation.getMethodName() + " in the service " + getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyinvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le); } return result; } catch (RpcException e) { // TODO 如果是业务逻辑层异常,捕获到即退出重试 if (e.isBiz()) { // biz exception. throw e; } le = e; } catch (Throwable e) { le = new RpcException(e.getMessage(), e); } finally { providers.add(invoker.getUrl().getAddress()); } } // 若重试失败,则抛出异常 throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method " + invocation.getMethodName() + " in the service " + getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyinvokers.size() + ") from the registry " + directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + (le != null ? le.getMessage() : ""), le != null && le.getCause() != null ? le.getCause() : le); }}
在FailoverClusterInvoker的doInvoke方法中,首先会checkInvokers进行检查服务是否可用情况,然后获取重试次数,如果重试次数配置不合理就给个默认值1,然后RpcException le为成功调用服务后的最后一次调用失败的异常对象,如果没有调用失败的情况,则该对象一直为null,然后for循环遍历len次进行重试调用,在调用的过程中,重试时,进行重新选择,避免重试时invoker列表已发生变化,如果列表发生了变化,那么invoked判断会失效,因为invoker实例已经改变。
然后select执行的是负载均衡的逻辑,选出一个invoker,调用目标 Invoker 的 invoke 方法,如果调用成功,直接返回result,反之,如果捕获到异常,如果是业务逻辑层异常,捕获到即退出重试,向上抛出返回给客户端,如果不是业务逻辑层异常,就赋值给len。最终invoker放进providers中,开始下一次重试。
如果最终len次重试后均调用失败,则直接抛出异常信息。
FailoverClusterInvoker的执行逻辑大致就完成了,下面我们看下负载均衡是如何进行select的。
protected Invoker select(LoadBalance loadbalance, Invocation invocation, List> invokers, List> selected) throws RpcException { if (invokers == null || invokers.size() == 0) return null; // 获取调用方法名 String methodName = invocation == null ? "" : invocation.getMethodName(); // =========================== 粘滞连接特性 =========================== // 获取 sticky 配置,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的 // 调用同一个服务提供者,除非该提供者挂了再进行切换 boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, Constants.CLUSTER_STICKY_KEY, Constants.DEFAULT_CLUSTER_STICKY); { // 检测 invokers 列表是否包含 stickyInvoker,如果不包含, // 说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空 if (stickyInvoker != null && !invokers.contains(stickyInvoker)) { stickyInvoker = null; } // 在 sticky 为 true,且 stickyInvoker != null 的情况下。如果 selected 包含 // stickyInvoker,表明 stickyInvoker 对应的服务提供者可能因网络原因未能成功提供服务。 // 但是该提供者并没挂,此时 invokers 列表中仍存在该服务提供者对应的 Invoker。 if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) { // 如果此时可用性检查通过的话该Invoker可继续提供服务。 if (availablecheck && stickyInvoker.isAvailable()) { return stickyInvoker; } } } // =========================== LB选择 Invoker =========================== // 如果线程走到当前代码处,说明前面的 stickyInvoker 为空,或者不可用。 // TODO 此时继续调用 doSelect 选择 Invoker Invoker invoker = doselect(loadbalance, invocation, invokers, selected); // 如果 sticky 为 true,则将负载均衡组件选出的 Invoker 赋值给 stickyInvoker if (sticky) { stickyInvoker = invoker; } return invoker; }
代码中两行 = 注释标明了两个主要功能:粘滞连接和LB进行doselect。获取 sticky 配置,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的调用同一个服务提供者,除非该提供者挂了再进行切换。检测 invokers列表是否包含 stickyInvoker,如果不包含,说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空。 在sticky 为 true,且 stickyInvoker != null 的情况下。如果selected 包含 stickyInvoker,表明 stickyInvoker 对应的服务提供者可能因网络原因未能成功提供服务。但是该提供者并没挂,此时 invokers 列表中仍存在该服务提供者对应的 Invoker。如果此时能够通过服务检查的话说明就可以继续提供服务。然后接下来继续调用doselect进行后续操作。
private Invoker doselect(LoadBalance loadbalance, Invocation invocation, List> invokers, List> selected) throws RpcException {
if (invokers == null || invokers.size() == 0)
return null;
if (invokers.size() == 1)
return invokers.get(0);
// 如果只有两个invoker,退化成轮循【新版本中删除了这段逻辑】
if (invokers.size() == 2 && selected != null && selected.size() > 0) {
return selected.get(0) == invokers.get(0) ? invokers.get(1) : invokers.get(0);
}
// TODO 通过负载均衡组件选择 Invoker
Invoker invoker = loadbalance.select(invokers, getUrl(), invocation);
// 如果 selected 包含负载均衡选择出的 Invoker,或者该 Invoker 无法经过可用性检查,此时进行重选
if ((selected != null && selected.contains(invoker)) || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
// TODO 通过负载均衡重新选择 Invoker
Invoker rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
// 如果 rinvoker 不为空,则将其赋值给 invoker
if (rinvoker != null) {
invoker = rinvoker;
} else {
// rinvoker 为空,定位 invoker 在 invokers 中的位置
int index = invokers.indexOf(invoker);
try {
// 避免碰撞
// 获取 index + 1 位置处的 Invoker,以下代码等价于:
// invoker = invokers.get((index + 1) % invokers.size());
invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invoker;
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error("clustor relselect fail reason is :" + t.getMessage() + " if can not slove ,you can set cluster.availablecheck=false in url", t);
}
}
return invoker;
}
doselect方法首先判断如果invokers只有一个invoker,那么直接返回。
然后通过负载均衡选择 Invoker,如果 selected 包含负载均衡选择出的 Invoker,或者该 Invoker 无法经过可用性检查,此时进行重选,获得rinvoker,如果 rinvoker 不为空,则将其赋值给 invoker,否则,rinvoker 为空,定位 invoker 在 invokers 中的位置,获取 index + 1 位置处的 Invoker,最终返回invoker。
下面我们来看一下 reselect 方法的逻辑。
private Invokerreselect(LoadBalance loadbalance, Invocation invocation, List> invokers, List> selected, boolean availablecheck) throws RpcException { //如果有可能预先分配一个,肯定会使用此list List> reselectInvokers = new ArrayList>(invokers.size() > 1 ? (invokers.size() - 1) : invokers.size()); //First, try picking a invoker not in `selected`. // 根据 availablecheck 进行不同的处理 if (availablecheck) { // invoker.isAvailable() should be checked // 遍历 invokers 列表 for (Invoker invoker : invokers) { // 检测可用性 if (invoker.isAvailable()) { // 如果 selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中 if (selected == null || !selected.contains(invoker)) { reselectInvokers.add(invoker); } } } // reselectInvokers 不为空,此时通过负载均衡组件进行选择 if (reselectInvokers.size() > 0) { return loadbalance.select(reselectInvokers, getUrl(), invocation); } // 不检查 Invoker 可用性 } else { // do not check invoker.isAvailable() for (Invoker invoker : invokers) { // 如果 selected 列表不包含当前 invoker,则将其添加到 reselectInvokers 中 if (selected == null || !selected.contains(invoker)) { reselectInvokers.add(invoker); } } // 通过负载均衡组件进行选择 if (reselectInvokers.size() > 0) { return loadbalance.select(reselectInvokers, getUrl(), invocation); } } // 只需使用负载均衡策略选择一个可用的调用程序 { // 若线程走到此处,说明 reselectInvokers 集合为空,此时不会调用负载均衡组件进行筛选。 // 这里从 selected 列表中查找可用的 Invoker,并将其添加到 reselectInvokers 集合中 if (selected != null) { for (Invoker invoker : selected) { if ((invoker.isAvailable()) && !reselectInvokers.contains(invoker)) { reselectInvokers.add(invoker); } } } if (reselectInvokers.size() > 0) { return loadbalance.select(reselectInvokers, getUrl(), invocation); } } return null; }
reselect 方法总结下来其实只做了三件事情。首先根据availablecheck首先进到第一个if,这块进行available校验,查找可用的 Invoker,并将其添加到 reselectInvokers 集合中,然后根据reselectInvokers进行LB的select。第二,不进行available校验,查找可用的 Invoker,并将其添加到 reselectInvokers 集合中,然后根据reselectInvokers进行LB的select。第三,如果来到第三步,说明reselectInvokers为空,这个时候就从可用的select中选择invoker。
关于 reselect 方法就先分析到这,继续分析其他的 Cluster Invoker。
②、FailbackClusterInvoker
规则:
FailbackClusterInvoker 会在调用失败后,返回一个空结果给服务消费者。并通过定时任务对失败的调用进行重传,适合执行消息通知等操作。
下面来看一下它的实现逻辑。
public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class); private static final long RETRY_FAILED_PERIOD = 5 * 1000; private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2, new NamedThreadFactory("failback-cluster-timer", true)); private final ConcurrentMap> failed = new ConcurrentHashMap>(); private volatile ScheduledFuture> retryFuture; public FailbackClusterInvoker(Directory directory) { super(directory); } /** * @Author: wenyixicodedog * @Date: 2020-07-14 * @Param: * @return: * @Description: 创建定时任务,每隔5秒执行一次 */ private void addFailed(Invocation invocation, AbstractClusterInvoker> router) { if (retryFuture == null) { synchronized (this) { if (retryFuture == null) { retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() { public void run() { // 对失败的调用进行重试 try { retryFailed(); } catch (Throwable t) { // 如果发生异常,仅打印异常日志,不抛出 logger.error("Unexpected error occur at collect statistic", t); } } }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS); } } } // 添加 invocation 和 invoker 到 failed 中 failed.put(invocation, router); } // 对失败的调用进行重试 void retryFailed() { if (failed.size() == 0) { return; } // 遍历 failed,对失败的调用进行重试 for (Map.Entry> entry : new HashMap<>(failed).entrySet()) { Invocation invocation = entry.getKey(); Invoker> invoker = entry.getValue(); try { // 再次进行调用 invoker.invoke(invocation); // 调用成功后,从 failed 中移除 invoker failed.remove(invocation); } catch (Throwable e) { logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e); } } } protected Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { try { checkInvokers(invokers, invocation); // 选择 Invoker Invoker invoker = select(loadbalance, invocation, invokers, null); // 进行调用 return invoker.invoke(invocation); } catch (Throwable e) { // 如果调用过程中发生异常,此时仅打印错误日志,不抛出异常 logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: " + e.getMessage() + ", ", e); // 记录调用信息 addFailed(invocation, this); // 返回一个空结果给服务消费者 return new RpcResult(); // ignore } }}
FailbackClusterInvoker的执行逻辑应该不会太难理解,在doInvoke中首先checkInvokers进行检查invoker,然后利用LB进行select一个invoker,接下来就是invoker的invoke调用了,关键是如果遇到异常被捕获,调用了addFailed方法,然后返回新创建的RPCResult对象。
addFailed方法使用ExecutorService线程池进行多线程执行run任务,调用retryFailed方法。
retryFailed方法则是invoke重试调用。
③、FailfastClusterInvoker
规则:
FailfastClusterInvoker 只会进行一次调用,失败后立即抛出异常。适用于幂等操作,比如新增记录。
源码如下:
public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> { public FailfastClusterInvoker(Directory directory) { super(directory); } public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { checkInvokers(invokers, invocation); Invoker invoker = select(loadbalance, invocation, invokers, null); try { return invoker.invoke(invocation); } catch (Throwable e) { if (e instanceof RpcException && ((RpcException) e).isBiz()) { // 若是业务逻辑异常:biz exception.往上抛,返回给客户端 throw (RpcException) e; } // 若是其他异常,直接终止 throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failfast invoke providers " + invoker.getUrl() + " " + loadbalance.getClass().getSimpleName() + " select from all providers " + invokers + " for service " + getInterface().getName() + " method " + invocation.getMethodName() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } }}
逻辑比较简单,首先检查invoker然后通过LB进行select选中一个invoker,直接进行invoke调用,如果捕获到异常,判断一下异常类型,如果是业务层异常,则向上抛出,便于客户端接收,否则,直接抛出RPCException异常。
④、FailsafeClusterInvoker
规则:
FailsafeClusterInvoker 是一种失败安全的 Cluster Invoker。所谓的失败安全是指,当调用过程中出现异常时,FailsafeClusterInvoker 仅会打印异常,而不会抛出异常。适用于写入审计日志等操作。
下面分析源码。
public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class); public FailsafeClusterInvoker(Directory directory) { super(directory); } public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { try { checkInvokers(invokers, invocation); Invoker invoker = select(loadbalance, invocation, invokers, null); return invoker.invoke(invocation); } catch (Throwable e) { logger.error("Failsafe ignore exception: " + e.getMessage(), e); return new RpcResult(); // ignore } }}
代码逻辑很是简单,如果invoke调用异常,直接打印日志信息然后返回新创建的RPCResult。
⑤、ForkingClusterInvoker
规则:
ForkingClusterInvoker 会在运行时通过线程池创建多个线程,并发调用多个服务提供者。只要有一个服务提供者成功返回了结果,doInvoke 方法就会立即结束运行。ForkingClusterInvoker 的应用场景是在一些对实时性要求比较高读操作(注意是读操作,并行写操作可能不安全)下使用,但是如果并发性确实较高,这将会耗费比较多的资源。
下面来看该类的实现。
public class ForkingClusterInvoker<T> extends AbstractClusterInvoker<T> { private final ExecutorService executor = Executors.newCachedThreadPool(new NamedThreadFactory("forking-cluster-timer", true)); public ForkingClusterInvoker(Directory directory) { super(directory); } @SuppressWarnings({"unchecked", "rawtypes"}) public Result doInvoke(final Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { checkInvokers(invokers, invocation); final List> selected; // 获取 forks 配置 final int forks = getUrl().getParameter(Constants.FORKS_KEY, Constants.DEFAULT_FORKS); // 获取超时配置 final int timeout = getUrl().getParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT); // 如果 forks 配置不合理,则直接将 invokers 赋值给 selected // ======================= 第一部分 获取selected列表 ======================= if (forks <= 0 || forks >= invokers.size()) { selected = invokers; } else { selected = new ArrayList>(); // 循环选出 forks 个 Invoker,并添加到 selected 中 for (int i = 0; i < forks; i++) { // 选择 Invoker // TODO. Add some comment here, refer chinese version for more details. Invoker invoker = select(loadbalance, invocation, invokers, selected); //避免多次添加相同的invoker if (!selected.contains(invoker)) { selected.add(invoker); } } } // ======================= 第二部分 多线程执行selected列表任务 ======================= RpcContext.getContext().setInvokers((List) selected); final AtomicInteger count = new AtomicInteger(); final BlockingQueue ref = new LinkedBlockingQueue(); // 遍历 selected 列表 for (final Invoker invoker : selected) { // 为每个 Invoker 创建一个执行线程 executor.execute(new Runnable() { public void run() { try { // 进行远程调用 Result result = invoker.invoke(invocation); // 将结果存到阻塞队列中 ref.offer(result); } catch (Throwable e) { // 仅在 value 大于等于 selected.size() 时,才将异常对象 // 放入阻塞队列中 int value = count.incrementAndGet(); if (value >= selected.size()) { // 将异常对象存入到阻塞队列中 ref.offer(e); } } } }); } // ======================= 第仨部分 处理队列中执行结果 ======================= try { Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS); // 如果结果类型为 Throwable,则抛出异常 if (ret instanceof Throwable) { Throwable e = (Throwable) ret; throw new RpcException(e instanceof RpcException ? ((RpcException) e).getCode() : 0, "Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e.getCause() != null ? e.getCause() : e); } return (Result) ret; } catch (InterruptedException e) { throw new RpcException("Failed to forking invoke provider " + selected + ", but no luck to perform the invocation. Last error is: " + e.getMessage(), e); } }}
三个 = 注释将代码分为三部分:
第一部分主要是获取一些基础配置及selected列表,以供后续使用。
第二部分则是利用线程池创建多线程执行线程任务。需要注意两点,①、如果调用异常count就加一然后赋值给value,当value大于等于selected的大小的时候才往阻塞队列中放入异常对象。这是为了保证并发的多线程中只要有任意一个任务执行成功就认为操作成功。另外一点该线程池是newCachedThreadPool,他使用SynchronousQueue作为其workQueue,需要注意高并发情况下对系统性能的影响,会不断创建线程执行新来的任务。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue(), threadFactory); }
⑥、BroadcastClusterInvoker
规则:
BroadcastClusterInvoker 会逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后,BroadcastClusterInvoker 会抛出异常。该类通常用于通知所有提供者更新缓存或日志等本地资源信息。
源码如下。
public class BroadcastClusterInvoker<T> extends AbstractClusterInvoker<T> { private static final Logger logger = LoggerFactory.getLogger(BroadcastClusterInvoker.class); public BroadcastClusterInvoker(Directory directory) { super(directory); } @SuppressWarnings({"unchecked", "rawtypes"}) public Result doInvoke(final Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException { checkInvokers(invokers, invocation); RpcContext.getContext().setInvokers((List) invokers); RpcException exception = null; Result result = null; // 遍历 Invoker 列表,逐个调用 for (Invoker invoker : invokers) { try { result = invoker.invoke(invocation); } catch (RpcException e) { exception = e; logger.warn(e.getMessage(), e); } catch (Throwable e) { exception = new RpcException(e.getMessage(), e); logger.warn(e.getMessage(), e); } } // exception 不为空,则抛出异常 if (exception != null) { throw exception; } return result; }}
逻辑并不难懂,利用for循环进行遍历调用invoke,如果调用异常,就将异常封装成RPCException赋值给exception,调用完成之后exception不为空,则抛出exception。
不过这段代码中 f or循环中如果Invoke调用失败就把异常赋值给exception,但是过有多次调用失败,这个exception保存的是最后一次失败的异常,之前的不就被覆盖了。源码设计如此,不明原因。Dubbo的集群容错是Dubbo本身的高级特性,Dubbo也支持我们自定义扩展,在项目中可以根据实际需求来动态配置或者扩展他们从而满足其他特定的场景。
下面以Dubbo官网提供的一张各种集群容错策略对比作为结束。
策略名称 | 优点 | 缺点 | 主要应用场景 |
---|---|---|---|
Failover | 对调用者屏蔽调用失败的信息 | 增加RT,额外资源开销,资源浪费 | 对调用rt不敏感的场景 |
Failfast | 业务快速感知失败状态进行自主决策 | 产生较多报错的信息 | 调用非幂等性接口,需要快速感知失败的场景 |
Failsafe | 即使失败了也不会影响核心流程 | 对于失败的信息不敏感,需要额外的监控 | 旁路系统,失败不影响核心流程正确性的场景 |
Failback | 失败自动异步重试 | 重试任务可能堆积 | 对于实时性要求不高,且不需要返回值的一些异步操作 |
Forking | 并行发起多个调用,降低失败概率 | 消耗额外的机器资源,需要确保操作幂等性 | 资源充足,且对于失败的容忍度较低,实时性要求高的场景 |
Broadcast | 支持对所有的服务提供者进行操作 | 资源消耗很大 | 通知所有提供者更新缓存或日志等本地资源信息 |