背景:
某个服务有2台机器,但是其中一台机器挂了,dubbo的重试还是会调用这台机器,经过查资料,推荐使用Failover机制;
过程:
经过跟踪源码,发现dubbo默认的容错机制就是使用了Failover,在执行调用前会直接进入FailoverClusterInvoker.class的doInvoke中,翻开源码:
public Result doInvoke(Invocation invocation, List> invokers, LoadBalance loadbalance) throws RpcException {
List> copyinvokers = invokers;
this.checkInvokers(invokers, invocation);
int len = this.getUrl().getMethodParameter(invocation.getMethodName(), "retries", 2) + 1;
if (len <= 0) {
len = 1;
}
RpcException le = null;
List> invoked = new ArrayList(invokers.size());
//1.记录执行rpc方法失败的提供者,但是实际不作为下次选取服务提供者的参考
Set providers = new HashSet(len);
for(int i = 0; i < len; ++i) {
//第一次选取提供端的时候,不进行检测
if (i > 0) {
this.checkWhetherDestroyed();
copyinvokers = this.list(invocation);
this.checkInvokers(copyinvokers, invocation);
}
//2. 根据上次访问的提供者,进行负载均衡,即:如果此时是串行调用,那么下次不会选取上次的提供者
Invoker invoker = this.select(loadbalance, invocation, copyinvokers, invoked);
invoked.add(invoker);
RpcContext.getContext().setInvokers(invoked);
try {
Result result = invoker.invoke(invocation);
if (le != null && logger.isWarnEnabled()) {
logger.warn("Although retry the method " + invocation.getMethodName() + " in the service " + this.getInterface().getName() + " was successful by the provider " + invoker.getUrl().getAddress() + ", but there have been failed providers " + providers + " (" + providers.size() + "/" + copyinvokers.size() + ") from the registry " + this.directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + le.getMessage(), le);
}
Result var12 = result;
return var12;
} catch (RpcException var17) {
if (var17.isBiz()) {
throw var17;
}
le = var17;
} catch (Throwable var18) {
le = new RpcException(var18.getMessage(), var18);
} finally {
providers.add(invoker.getUrl().getAddress());
}
}
throw new RpcException(le != null ? le.getCode() : 0, "Failed to invoke the method " + invocation.getMethodName() + " in the service " + this.getInterface().getName() + ". Tried " + len + " times of the providers " + providers + " (" + providers.size() + "/" + copyinvokers.size() + ") from the registry " + this.directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Last error is: " + (le != null ? le.getMessage() : ""), (Throwable)(le != null && le.getCause() != null ? le.getCause() : le));
}
AbstractClusterInvoker中的doselect关键源码:
//AbstractClusterInvoker
private Invoker doselect(LoadBalance loadbalance, Invocation invocation, List> invokers, List> selected) throws RpcException {
if (invokers != null && !invokers.isEmpty()) {
if (invokers.size() == 1) {
return (Invoker)invokers.get(0);
} else if (invokers.size() == 2 && selected != null && !selected.isEmpty()) {
return selected.get(0) == invokers.get(0) ? (Invoker)invokers.get(1) : (Invoker)invokers.get(0);
} else {
//loadbalance 随机函数,里面是根据一个随机数来判断此次调用选择哪个提供者
Invoker invoker = loadbalance.select(invokers, this.getUrl(), invocation);
//这个时候如果zk通知消费者,提供者挂了的话,那么此时将会重新选择一个提供者进行调用
if (selected != null && selected.contains(invoker) || !invoker.isAvailable() && this.getUrl() != null && this.availablecheck) {
try {
Invoker rinvoker = this.reselect(loadbalance, invocation, invokers, selected, this.availablecheck);
if (rinvoker != null) {
invoker = rinvoker;
} else {
int index = invokers.indexOf(invoker);
try {
invoker = index < invokers.size() - 1 ? (Invoker)invokers.get(index + 1) : invoker;
} catch (Exception var9) {
logger.warn(var9.getMessage() + " may because invokers list dynamic change, ignore.", var9);
}
}
} catch (Throwable var10) {
logger.error("clustor relselect fail reason is :" + var10.getMessage() + " if can not slove ,you can set cluster.availablecheck=false in url", var10);
}
}
return invoker;
}
} else {
return null;
}
}
结果:
根据这段源码,同时重点关注里面的注释1、注释2,可知,failover其实在一次调用过程中不会根据以往的失败记录作为参看条件,只是在负载均衡的大条件下,来选取服务提供者,所以实际failover并不一定能做到切换服务提供者。
其次,如果单次调用,服务超时了,那么此时还是继续调用该服务,不会换机器
最后,根据select中的源码可以发现,如果服务挂了,但是zk没有通知到消费者的话,那么消费者还是会继续根据的服务提供者的列表来访问。
如果有误,欢迎讨论