在上篇博客Dubbo 服务引入源码分析中详细的分析了服务引入的过程,同时分析过程中也提到了集群聚合多个提供的代码但并未深究,因此本文将对dubbo的集群源码(版本2.7.7
)进行详细分析。
为了避免单点故障,现在的应用通常至少会部署在两台服务器上即在同一环境下的服务提供者数量会大于1。对于服务消费者来说,需要决定选择哪个服务提供者进行调用以及考虑服务调用失败时的处理措施,例如重试、抛出异常或是只打印异常等。为了处理这些问题,Dubbo 定义了集群接口 Cluster 以及 Cluster Invoker。集群 Cluster将多个服务提供者合并为一个 Cluster Invoker,并将这个 Invoker 暴露给服务消费者,集群模块为服务消费者屏蔽了服务提供者的情况,这样服务消费者就可以专心处理远程调用相关事宜而不用关心提供者的选择及失败措施等问题。
Dubbo 提供了多种集群实现,包扩 Failover Cluster (失败自动切换)、Failfast Cluster (快速失败)、 Failsafe Cluster(失败安全) 、Failback Cluster(失败自动恢复)及
Forking Cluster(并行调用多个服务提供者)等。每种集群实现类的用途不同,接下来会一一进行分析。
在对集群相关代码进行分析之前,这里有必要先来介绍一下集群容错的所有组件。包含 Cluster、Cluster Invoker、Directory、Router 和 LoadBalance 等。
集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。第二个阶段是在服务消费者进行远程调用时。以 FailoverClusterInvoker 为例,该类型 Cluster Invoker 首先会调用 Directory 的 list 方法列举 Invoker 列表(可将 Invoker 简单理解为服务提供者)。Directory 的用途是保存 Invoker,可简单类比为 List。其实现类 RegistryDirectory 是一个动态服务目录,可感知注册中心配置的变化,它所持有的 Invoker 列表会随着注册中心内容的变化而变化。每次变化后,RegistryDirectory 会动态增删 Invoker,并调用 Router 的 route 方法进行路由,过滤掉不符合路由规则的 Invoker。当 FailoverClusterInvoker 拿到 Directory 返回的 Invoker 列表后,它会通过 负载均衡算法LoadBalance 从 Invoker 列表中选择一个 Invoker。最后 FailoverClusterInvoker 会将参数传给 LoadBalance 选择出的 Invoker 实例的 invoke 方法,进行真正的远程调用。
我们在上一章看到了两个概念,分别是集群接口 Cluster 和 Cluster Invoker,这两者是不同的。Cluster 是接口,而 Cluster Invoker 是一种 Invoker。服务提供者的选择逻辑,以及远程调用失败后的的处理逻辑均是封装在 Cluster Invoker 中。而 Cluster 接口和相关实现类仅用于生成 Cluster Invoker。
//默认使用FailoverCluster(失败自动切换)
@SPI(FailoverCluster.NAME)
public interface Cluster {
/**
* Merge the directory invokers to a virtual invoker.
*
* @param
* @param directory
* @return cluster invoker
* @throws RpcException
*/
@Adaptive
<T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
public abstract class AbstractCluster implements Cluster {
private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {
//赋值最终返回的clusterInvoker为dojoin方法即子类实现返回的Invoker
AbstractClusterInvoker<T> last = clusterInvoker;
//通过SPI方式获取集群拦截器列表
List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
//如果存在拦截器,则构建拦截器链,将最顶端的拦截器Invoker返回
if (!interceptors.isEmpty()) {
for (int i = interceptors.size() - 1; i >= 0; i--) {
final ClusterInterceptor interceptor = interceptors.get(i);
//将当前最次新的Invoker作为下一个
final AbstractClusterInvoker<T> next = last;
//使用当前拦截器及下一个Invoker构建新的Invoker
last = new InterceptorInvokerNode<>(clusterInvoker, interceptor, next);
}
}
return last;
}
/**
* Cluster接口方法实现,调用本类的构建拦截器方法及需要子类实现的doJoin方法
*/
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
//参数1:子类返回Invoker,参数2:Url中reference.interceptor对应的值
return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
}
protected abstract <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException;
public class FailbackCluster extends AbstractCluster {
public final static String NAME = "failback";
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailbackClusterInvoker<>(directory);
}
}
public class FailfastCluster extends AbstractCluster {
public final static String NAME = "failfast";
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailfastClusterInvoker<>(directory);
}
}
public class FailoverCluster extends AbstractCluster {
public final static String NAME = "failover";
@Override
public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker<>(directory);
}
}
上面贴出了接口源码及具体策略实现源码,可以看出Cluster实现逻辑都很简单。首先是有一个抽象试下,主要逻辑是根据拦截器和子类返回的Invoker构建拦截器调用链。其次是子类实现逻辑就是调用创建对应的ClusterInvoker实例返回,因此接下来主要分析各种ClusterInvoker实现。
前面说过,集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间会调用join方法也就是上面的Cluster接口方法,这个在服务引入源码分析文章中分析过。第二个阶段是在服务消费者进行远程调用时,此时 AbstractClusterInvoker 的 invoke 方法会被调用。列举 Invoker,负载均衡等操作均会在此阶段被执行。但是需要注意的是,根据上面源码分析结果可知,如果存在集权拦截器的时候,最终返回的是org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster.InterceptorInvokerNode,该类同样继承了AbstractClusterInvoker 并且重写了invoke方法,因此此时会先调用拦截器Invoker的Invoke方法,不过本方法的拦截默认实现还是调用的原本拦截器的invoke方法及AbstractClusterInvoker 的 invoke 方法。因此我们先简单分析下InterceptorInvokerNode的invoke方法。
org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster.InterceptorInvokerNode.invoke(Invocation)
@Override
public Result invoke(Invocation invocation) throws RpcException {
//定义Result接收异步调用结果
Result asyncResult;
try {
//拦截前执行前调用方法
interceptor.before(next, invocation);
//拦截方法,默认实现调用真正的Invoker的invoke方法(clusterInvoker.invoke(invocation);)
asyncResult = interceptor.intercept(next, invocation);
} catch (Exception e) {
// 失败如果是集群拦截监听器实现,则调用onError方法回滚等操作
if (interceptor instanceof ClusterInterceptor.Listener) {
ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
listener.onError(e, clusterInvoker, invocation);
}
throw e;
} finally {
//调用结束后的操作比如清楚上下文信息
interceptor.after(next, invocation);
}
//阻塞获取调用结果,并执行监听方法
return asyncResult.whenCompleteWithContext((r, t) -> {
// onResponse callback
if (interceptor instanceof ClusterInterceptor.Listener) {
ClusterInterceptor.Listener listener = (ClusterInterceptor.Listener) interceptor;
if (t == null) {
listener.onMessage(r, clusterInvoker, invocation);
} else {
listener.onError(t, clusterInvoker, invocation);
}
}
});
}
org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(Invocation)
public Result invoke(final Invocation invocation) throws RpcException {
// 检查是否调用过销毁方法,调用过则抛出异常
checkWhetherDestroyed();
// 绑定 attachments 到 invocation 中.
Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
if (contextAttachments != null && contextAttachments.size() != 0) {
((RpcInvocation) invocation).addAttachments(contextAttachments);
}
// 调用 Directory 的 list 方法列举 Invoker
List<Invoker<T>> invokers = list(invocation);
// 通过SPI方式获取负载均衡算法实现
LoadBalance loadbalance = initLoadBalance(invokers, invocation);
// 幂等操作:将调用id添加到异步操作中
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
// 调用子类doInvoke实现
return doInvoke(invocation, invokers, loadbalance);
}
根据AbstractClusterInvoker 的invoke方法源码分析可知,最终调用子类 doInvoke 方法逻辑,因此接下来分析具体子类试下。
FailoverClusterInvoker 在调用失败时,会自动切换 Invoker 进行重试。默认配置下,Dubbo 会使用这个类作为缺省 Cluster Invoker。
public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
// 因为invokers是final类型不能修改,因此重新创建一个零时变量
List<Invoker<T>> copyInvokers = invokers;
// 检查Invoker是不是空,是在抛出异常
checkInvokers(copyInvokers, invocation);
// 获取调用方法名称
String methodName = RpcUtils.getMethodName(invocation);
// 获取重试次数,默认1
int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
if (len <= 0) {
len = 1;
}
// 循环重试
RpcException le = null; // 记录上一个调用异常
// 记录调用过的Invoker
List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size());
// 记录提供给地址
Set<String> providers = new HashSet<String>(len);
for (int i = 0; i < len; i++) {
// 没词次循环都会重新检查及列举并选则可用Invoker,避免调用过程中提供者改变(例如服务挂了)导致异常
if (i > 0) {
// 检查是够已经销毁
checkWhetherDestroyed();
// 重新列举可用Invoker
copyInvokers = list(invocation);
// 校验Invoker是否为空
checkInvokers(copyInvokers, invocation);
}
// 从可用Invoker中选出一个
Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
// 添加到已经调用过Invoker集合中
invoked.add(invoker);
// 设置已经调用的Invoker到上下文中
RpcContext.getContext().setInvokers((List) invoked);
try {
// 执行invoke方法获取结果
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + methodName + " 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) {
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.getCode(),
"Failed to invoke the method " + methodName + " 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.getMessage(),
le.getCause() != null ? le.getCause() : le);
}
FailoverClusterInvoker 的 doInvoke 方法源码分析可知,该方法首先是获取重试次数,然后根据重试次数进行循环调用,失败后进行重试。在 for 循环内,首先是通过负载均衡组件选择一个 Invoker,然后再通过这个 Invoker 的 invoke 方法进行远程调用。如果失败了,记录下异常,并进行重试。重试时会再次调用父类的 list 方法列举 Invoker。下面我们看一下 select 方法的逻辑。
org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.select(LoadBalance, Invocation, List, List)
protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers,
List<Invoker<T>> selected) throws RpcException {
// Invoker空直接返回
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
// 调用名称,默认空字符""
String methodName = invocation == null ? StringUtils.EMPTY_STRING : invocation.getMethodName();
// 获取 sticky 配置,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的调用同一个服务提供者,除非该提供者挂了再进行切换
boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, CLUSTER_STICKY_KEY,
DEFAULT_CLUSTER_STICKY);
// 检测 invokers 列表是否包含 stickyInvoker,
if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
// 不包含,说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空
stickyInvoker = null;
}
// 在 sticky 为 true,且 stickyInvoker != null 的情况下。
// 如果 selected 包含 stickyInvoker,表明 stickyInvoker 对应的服务提供者可能因网络原因未能成功提供服务。
if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
// availablecheck 表示是否开启了可用性检查,
// 如果开启了,则调用 stickyInvoker 的 isAvailable 方法进行检查,如果检查通过,则直接返回 stickyInvoker。
if (availablecheck && stickyInvoker.isAvailable()) {
return stickyInvoker;
}
}
// 如果线程走到当前代码处,说明前面的 stickyInvoker 为空,或者不可用。此时继续调用 doSelect 选择 Invoker
Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
// 如果 sticky 为 true,则将负载均衡组件选出的 Invoker 赋值给 stickyInvoker
if (sticky) {
stickyInvoker = invoker;
}
return invoker;
}
org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.doSelect(LoadBalance, Invocation, List
>, List >)
private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers,
List<Invoker<T>> selected) throws RpcException {
// 判空
if (CollectionUtils.isEmpty(invokers)) {
return null;
}
// 只有一个直接返回
if (invokers.size() == 1) {
return invokers.get(0);
}
// 通过负载均衡组件选择 Invoker
Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);
// 如果 selected 包含负载均衡选择出的 Invoker,或者该 Invoker不可用但要求可用性检查,此时进行重选
if ((selected != null && selected.contains(invoker))
|| (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
try {
// 执行重选
Invoker<T> rInvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
if (rInvoker != null) {
// 如果 重选成功,则将其赋值给 invoker
invoker = rInvoker;
} else {
// 获取当前选出的Invoker在 invokers 中的位置
int index = invokers.indexOf(invoker);
try {
// 获取 index + 1 位置处的 Invoker,以下代码等价于:
// invoker = invokers.get((index + 1) % invokers.size());
invoker = invokers.get((index + 1) % invokers.size());
} catch (Exception e) {
logger.warn(e.getMessage() + " may because invokers list dynamic change, ignore.", e);
}
}
} catch (Throwable t) {
logger.error("cluster reselect fail reason is :" + t.getMessage()
+ " if can not solve, you can set cluster.availablecheck=false in url", t);
}
}
return invoker;
}
doSelect 主要做了两件事,第一是通过负载均衡组件选择 Invoker。第二是,如果选出来的 Invoker 不稳定,或不可用,此时需要调用 reselect 方法进行重选。若 reselect 选出来的 Invoker 为空,此时定位 invoker 在 invokers 列表中的位置 index,然后获取 index + 1 处的 invoker,这也可以看做是重选逻辑的一部分。
private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers,
List<Invoker<T>> selected, boolean availablecheck) throws RpcException {
// 记录参与重选的Invoker集合,如果Invoker数量大于1则该集合大小为Invoker数减一
List<Invoker<T>> reselectInvokers = new ArrayList<>(
invokers.size() > 1 ? (invokers.size() - 1) : invokers.size());
// First, try picking a invoker not in `selected`.
for (Invoker<T> invoker : invokers) {
// 如果需要可用性校验但是当前服务不可用则忽略
if (availablecheck && !invoker.isAvailable()) {
continue;
}
// 当前Invoker不在一选择的集合中则存入重选的集合中
if (selected == null || !selected.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
// 如果重选结合不为空则使用负载均衡从中选出一个
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
// 重选集合为空,但是已经选择过的集合不空
if (selected != null) {
for (Invoker<T> invoker : selected) {
// 校验已经选择过的服务是否可用,可用在加入重选集合
if ((invoker.isAvailable()) && !reselectInvokers.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
}
// 通过负载均衡从已选集合的可用服务集合中选出一个Invoker
if (!reselectInvokers.isEmpty()) {
return loadbalance.select(reselectInvokers, getUrl(), invocation);
}
// 没有可用的则返回空
return null;
}
BroadcastClusterInvoker 会逐个调用每个服务提供者,如果其中一台报错,在循环调用结束后,BroadcastClusterInvoker 会抛出异常。该类通常用于通知所有提供者更新缓存或日志等本地资源信息。
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
// 检查Invoker是不是空,是在抛出异常
checkInvokers(invokers, invocation);
// 设置调用的Invoker到上下文中
RpcContext.getContext().setInvokers((List) invokers);
// 记录异常
RpcException exception = null;
Result result = null;
// 遍历 Invoker 列表,逐个调用远程服务
for (Invoker<T> 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);
}
}
// 有一个失败则抛出异常
if (exception != null) {
throw exception;
}
return result;
}
}
AvailableClusterInvoker同样是循环所有Invoker,但是一旦有一个可用则直接发起调用并返回。
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
// 遍历所有Invoker
for (Invoker<T> invoker : invokers) {
// 当前提供者可用,则直接调用并返回
if (invoker.isAvailable()) {
return invoker.invoke(invocation);
}
}
throw new RpcException("No provider available in " + invokers);
}
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;
private final int retries;
private final int failbackTasks;
private volatile Timer failTimer;
public FailbackClusterInvoker(Directory<T> directory) {
super(directory);
int retriesConfig = getUrl().getParameter(RETRIES_KEY, DEFAULT_FAILBACK_TIMES);
if (retriesConfig <= 0) {
retriesConfig = DEFAULT_FAILBACK_TIMES;
}
int failbackTasksConfig = getUrl().getParameter(FAIL_BACK_TASKS_KEY, DEFAULT_FAILBACK_TASKS);
if (failbackTasksConfig <= 0) {
failbackTasksConfig = DEFAULT_FAILBACK_TASKS;
}
retries = retriesConfig;
failbackTasks = failbackTasksConfig;
}
private void addFailed(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers,
Invoker<T> lastInvoker) {
// 计数器为空,通过双重检查创建一个哈希轮计时器实例
if (failTimer == null) {
synchronized (this) {
if (failTimer == null) {
failTimer = new HashedWheelTimer(new NamedThreadFactory("failback-cluster-timer", true), 1,
TimeUnit.SECONDS, 32, failbackTasks);
}
}
}
// 新建一个重试任务
RetryTimerTask retryTimerTask = new RetryTimerTask(loadbalance, invocation, invokers, lastInvoker, retries,
RETRY_FAILED_PERIOD);
try {
// 指定的RetryTimerTask将在指定延迟(5秒)之后执行。
failTimer.newTimeout(retryTimerTask, RETRY_FAILED_PERIOD, TimeUnit.SECONDS);
} catch (Throwable e) {
logger.error(
"Failback background works error,invocation->" + invocation + ", exception: " + e.getMessage());
}
}
@Override
protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
Invoker<T> invoker = null;
try {
// 判空校验
checkInvokers(invokers, invocation);
// 选择可用服务
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(loadbalance, invocation, invokers, invoker);
// 返回一个空异步结果给服务消费者
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation);
}
}
@Override
public void destroy() {
super.destroy();
if (failTimer != null) {
failTimer.stop();
}
}
/**
* RetryTimerTask
*/
private class RetryTimerTask implements TimerTask {
private final Invocation invocation;
private final LoadBalance loadbalance;
private final List<Invoker<T>> invokers;
private final int retries;
private final long tick;
private Invoker<T> lastInvoker;
private int retryTimes = 0;
RetryTimerTask(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers,
Invoker<T> lastInvoker, int retries, long tick) {
this.loadbalance = loadbalance;
this.invocation = invocation;
this.invokers = invokers;
this.retries = retries;
this.tick = tick;
this.lastInvoker = lastInvoker;
}
@Override
public void run(Timeout timeout) {
// 重试执行是调用
try {
// 选择上次执行的Invoker,进行远程调用
Invoker<T> retryInvoker = select(loadbalance, invocation, invokers,
Collections.singletonList(lastInvoker));
lastInvoker = retryInvoker;
retryInvoker.invoke(invocation);
} catch (Throwable e) {
logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
if ((++retryTimes) >= retries) {
// 如果超过了重试次数,则答应错误日志
logger.error("Failed retry times exceed threshold (" + retries
+ "), We have to abandon, invocation->" + invocation);
} else {
// 否则重新将任务存入调度队列中
rePut(timeout);
}
}
}
private void rePut(Timeout timeout) {
if (timeout == null) {
return;
}
// 获取计时器,如果已经停止或取消则返回
Timer timer = timeout.timer();
if (timer.isStop() || timeout.isCancelled()) {
return;
}
// 将当前定时任务在放入延时调用队列中
timer.newTimeout(timeout.task(), tick, TimeUnit.SECONDS);
}
}
}
FailfastClusterInvoker 只会进行一次调用,失败后立即抛出异常。适用于幂等操作,比如新增记录。
public class FailfastClusterInvoker<T> extends AbstractClusterInvoker<T> {
public FailfastClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
// 判空校验
checkInvokers(invokers, invocation);
// 选择可用服务
Invoker<T> 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);
}
}
}
FailsafeClusterInvoker 是一种失败安全的 Cluster Invoker。所谓的失败安全是指,当调用过程中出现异常时,FailsafeClusterInvoker 仅会打印异常,而不会抛出异常。适用于写入审计日志等操作。
public class FailsafeClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailsafeClusterInvoker.class);
public FailsafeClusterInvoker(Directory<T> directory) {
super(directory);
}
@Override
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
try {
// 判空校验
checkInvokers(invokers, invocation);
// 选择可用服务
Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
// 执行远程调用
return invoker.invoke(invocation);
} catch (Throwable e) {
// 捕获异常并返回默认空异步结果
logger.error("Failsafe ignore exception: " + e.getMessage(), e);
return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
}
}
}
ForkingClusterInvoker 会在运行时通过线程池创建多个线程,并发调用多个服务提供者。只要有一个服务提供者成功返回了结果,doInvoke 方法就会立即结束运行。ForkingClusterInvoker 的应用场景是在一些对实时性要求比较高读操作(注意是读操作,并行写操作可能不安全)下使用,但这将会耗费更多的资源。
public Result doInvoke(final Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
throws RpcException {
try {
// 判空校验
checkInvokers(invokers, invocation);
// 记录已经选择的Invoker
final List<Invoker<T>> selected;
// 获取fork是配置(即调用几个Invoker),默认为2
final int forks = getUrl().getParameter(FORKS_KEY, DEFAULT_FORKS);
// 获取超时配置,默认1秒
final int timeout = getUrl().getParameter(TIMEOUT_KEY, DEFAULT_TIMEOUT);
// 如果forks<=0或者forks大于等于提供者数量,则选择所有的Invoker
if (forks <= 0 || forks >= invokers.size()) {
selected = invokers;
} else {
// 否则通过负载均衡算法选出forks个Invoker
selected = new ArrayList<>();
for (int i = 0; i < forks; i++) {
Invoker<T> invoker = select(loadbalance, invocation, invokers, selected);
if (!selected.contains(invoker)) {
// Avoid add the same invoker several times.
selected.add(invoker);
}
}
}
// 设置选出的Invoker到上下文
RpcContext.getContext().setInvokers((List) selected);
final AtomicInteger count = new AtomicInteger();
final BlockingQueue<Object> ref = new LinkedBlockingQueue<>();
// 遍历选出的Invoker
for (final Invoker<T> invoker : selected) {
// 将调用任务提交到线程池
executor.execute(() -> {
try {
Result result = invoker.invoke(invocation);
// 调用结果存入阻塞队列
ref.offer(result);
} catch (Throwable e) {
// 记录异常数量
int value = count.incrementAndGet();
// 如果异常数大于等于选择的Invoker数,则将异常存入阻塞队列;表明没有一个服务成功
if (value >= selected.size()) {
ref.offer(e);
}
}
});
}
try {
// 从阻塞队列中获取结果
Object ret = ref.poll(timeout, TimeUnit.MILLISECONDS);
// 如果是异常则抛出异常(根据上面逻辑可知,只有调用服务都失败了才会将异常传入队列中)
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);
}
} finally {
// clear attachments which is binding to current thread.
RpcContext.getContext().clearAttachments();
}
}
}
到此为止,dubbo集群的源码分析就结束了,同时也意味着dubbo相关的源码分析接近尾声,希望您能有所收获。
源码分析地址(cluster模块):https://github.com/qqxhb/dubbo