Dubbo 主要提供了这样几种容错方式:
Failover Cluster - 失败自动切换 Failfast Cluster - 快速失败 Failsafe Cluster - 失败安全 Failback Cluster - 失败自动恢复 Forking Cluster - 并行调用多个服务提供者
Cluster
Cluster是一个SPI扩展接口 只有一个join方法
@SPI("failover")
public interface Cluster {
@Adaptive
<T> Invoker<T> join(Directory<T> var1) throws RpcException;
}
有多种具体实现,默认是FailoverCluster
返回Invoker也有多种实现
由此可以推断FailoverCluster
必然返回FailoverClusterInvoker
public class FailoverCluster implements Cluster {
public static final String NAME = "failover";
public FailoverCluster() {
}
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
return new FailoverClusterInvoker(directory);
}
}
FailbackCluster
必然返回FailbackClusterInvoker
public class FailbackCluster implements Cluster {
public final static String NAME = "failback";
@Override
public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
// 创建并返回 FailbackClusterInvoker 对象
return new FailbackClusterInvoker<T>(directory);
}
}
在服务消费者进行远程调用时,此时 AbstractClusterInvoker 的 invoke 方法会被调用。列举 Invoker,负载均衡等操作均会在此阶段被执行。 AbstractClusterInvoker中的doInvoke是个模板方法 具体在FailbackClusterInvoker中实现
protected abstract Result doInvoke(Invocation var1, List<Invoker<T>> var2, LoadBalance var3) throws RpcException;
FailoverClusterInvoker中doInvoke方法执行具体调用。 先看下AbstractClusterInvoker 的 invoke 方法
public Result invoke(Invocation invocation) throws RpcException {
//检查Cluster是否销毁 是的话抛异常
this.checkWhetherDestroyed();
// 调用 Directory 的 list 方法列举 Invoker
List<Invoker<T>> invokers = this.list(invocation);
LoadBalance loadbalance;
if (invokers != null && invokers.size() > 0) {
//如果调用实现存在,根据服务调用方方法参数loadbalance 获取负载均衡实现 默认是随机算法
loadbalance = (LoadBalance)ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension(((Invoker)invokers.get(0)).getUrl().getMethodParameter(invocation.getMethodName(), "loadbalance", "random"));
} else {
//如果调用实现不存在,使用随机负载均衡实现
loadbalance = (LoadBalance)ExtensionLoader.getExtensionLoader(LoadBalance.class).getExtension("random");
}
RpcUtils.attachInvocationIdIfAsync(this.getUrl(), invocation);
return this.doInvoke(invocation, invokers, loadbalance);
}
protected void checkWhetherDestroyed() {
if (this.destroyed.get()) {
throw new RpcException("Rpc cluster invoker for " + this.getInterface() + " on consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + " is now destroyed! Can not invoke any more.");
}
}
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
List<Invoker<T>> invokers = this.directory.list(invocation);
return invokers;
}
![](https://oscimg.oschina.net/oscnet/753bd7980a9f3c4aab3af8502c351351891.jpg
以下再来看FailoverClusterInvoker的doInvoke
public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {
private static final Logger logger = LoggerFactory.getLogger(FailoverClusterInvoker.class);
public FailoverClusterInvoker(Directory<T> directory) {
super(directory);
}
public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
List<Invoker<T>> copyinvokers = invokers;
//检查服务providers是否存在 不存在报No provider available for the service XXX异常
this.checkInvokers(invokers, invocation);
//获取调用方法retries次数
int len = this.getUrl().getMethodParameter(invocation.getMethodName(), "retries", 2) + 1;
if (len <= 0) {
len = 1;
}
RpcException le = null;
List<Invoker<T>> invoked = new ArrayList(invokers.size());
Set<String> providers = new HashSet(len);
for(int i = 0; i < len; ++i) {
//每次重试都要检查cluster实例是否销毁 服务提供者provider是否存在
if (i > 0) {
this.checkWhetherDestroyed();
copyinvokers = this.list(invocation);
this.checkInvokers(copyinvokers, invocation);
}
// 通过负载均衡选择 Invoker
Invoker<T> invoker = this.select(loadbalance, invocation, copyinvokers, invoked);
// 添加到 invoker 到 invoked 列表中
invoked.add(invoker);
// 设置 invoked 到 RPC 上下文中
RpcContext.getContext().setInvokers(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 " + 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());
}
}
//这样写避免在多个catch里面抛异常 只要收集异常信息 统一向外抛调用失败
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));
}
}
protected void checkInvokers(List<Invoker<T>> invokers, Invocation invocation) {
if (invokers == null || invokers.size() == 0) {
throw new RpcException("Failed to invoke the method " + invocation.getMethodName() + " in the service " + this.getInterface().getName() + ". No provider available for the service " + this.directory.getUrl().getServiceKey() + " from registry " + this.directory.getUrl().getAddress() + " on the consumer " + NetUtils.getLocalHost() + " using the dubbo version " + Version.getVersion() + ". Please check if the providers have been started and registered.");
}
}
FailoverClusterInvoker 的 doInvoke 方法首先是获取重试次数,然后根据重试次数进行循环调用,失败后进行重试。在 for 循环内,首先是通过负载均衡组件选择一个 Invoker,然后再通过这个 Invoker 的 invoke 方法进行远程调用。如果失败了,记录下异常,并进行重试。重试时会再次调用父类的 list 方法列举 Invoker。注意服务调用过程使用sticky实现了粘滞连接
以下select 方法的主要逻辑集中在了对粘滞连接特性的支持上。首先是获取 sticky 配置,然后再检测 invokers 列表中是否包含 stickyInvoker,如果不包含,则认为该 stickyInvoker 不可用,此时将其置空。这里的 invokers 列表可以看做是存活着的服务提供者列表,如果这个列表不包含 stickyInvoker,那自然而然的认为 stickyInvoker 挂了,所以置空。如果 stickyInvoker 存在于 invokers 列表中,此时要进行下一项检测 — 检测 selected 中是否包含 stickyInvoker。如果包含的话,说明 stickyInvoker 在此之前没有成功提供服务(但其仍然处于存活状态)。此时我们认为这个服务不可靠,不应该在重试期间内再次被调用,因此这个时候不会返回该 stickyInvoker。如果 selected 不包含 stickyInvoker,此时还需要进行可用性检测,比如检测服务提供者网络连通性等。当可用性检测通过,才可返回 stickyInvoker,否则调用 doSelect 方法选择 Invoker。如果 sticky 为 true,此时会将 doSelect 方法选出的 Invoker 赋值给 stickyInvoker select方法如下
protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers != null && invokers.size() != 0) {
// 获取调用方法名
String methodName = invocation == null ? "" : invocation.getMethodName();
// 获取 sticky 配置,sticky 表示粘滞连接。所谓粘滞连接是指让服务消费者尽可能的
// 调用同一个服务提供者,除非该提供者挂了再进行切换
boolean sticky = ((Invoker)invokers.get(0)).getUrl().getMethodParameter(methodName, "sticky", false);
// 检测 invokers 列表是否包含 stickyInvoker,如果不包含,
// 说明 stickyInvoker 代表的服务提供者挂了,此时需要将其置空
if (this.stickyInvoker != null && !invokers.contains(this.stickyInvoker)) {
this.stickyInvoker = null;
}
// 在 sticky 为 true,且 stickyInvoker != null 的情况下。如果 selected 包含
// stickyInvoker,表明 stickyInvoker 对应的服务提供者可能因网络原因未能成功提供服务。
// 但是该提供者并没挂,此时 invokers 列表中仍存在该服务提供者对应的 Invoker。
// availablecheck 表示是否开启了可用性检查,如果开启了,则调用 stickyInvoker 的
// isAvailable 方法进行检查,如果检查通过,则直接返回 stickyInvoker。
//这种写法其实也可以改为多个if嵌套 更容易理解
if (sticky && this.stickyInvoker != null && (selected == null || !selected.contains(this.stickyInvoker)) && this.availablecheck && this.stickyInvoker.isAvailable()) {
return this.stickyInvoker;
} else {
// 如果线程走到当前代码处,说明前面的 stickyInvoker 为空,或者不可用。
// 此时继续调用 doSelect 选择 Invoker
Invoker<T> invoker = this.doselect(loadbalance, invocation, invokers, selected);
if (sticky) {
// 如果 sticky 为 true,则将负载均衡组件选出的 Invoker 赋值给 stickyInvoker
this.stickyInvoker = invoker;
}
return invoker;
}
} else {
return null;
}
}
private Invoker<T> doselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
if (invokers != null && invokers.size() != 0) {
if (invokers.size() == 1) {
return (Invoker)invokers.get(0);
//如果有2个服务提供者 且已调用过
} else if (invokers.size() == 2 && selected != null && selected.size() > 0) {
//如果已调用过的服务提供者就是调用列表中第一个 说明列表中第一个提供者调用失败 选择后一个 否则选择第一个
return selected.get(0) == invokers.get(0) ? (Invoker)invokers.get(1) : (Invoker)invokers.get(0);
} else {
//执行负载均衡实现类的doSelect方法
Invoker<T> invoker = loadbalance.select(invokers, this.getUrl(), invocation);
if (selected != null && selected.contains(invoker) || !invoker.isAvailable() && this.getUrl() != null && this.availablecheck) {
try {
Invoker<T> 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;
}
}
doSelect方法如下
protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
int length = invokers.size();
int totalWeight = 0;
boolean sameWeight = true;
int offset;
int i;
for(offset = 0; offset < length; ++offset) {
i = this.getWeight((Invoker)invokers.get(offset), invocation);
totalWeight += i;
if (sameWeight && offset > 0 && i != this.getWeight((Invoker)invokers.get(offset - 1), invocation)) {
sameWeight = false;
}
}
if (totalWeight > 0 && !sameWeight) {
offset = this.random.nextInt(totalWeight);
for(i = 0; i < length; ++i) {
offset -= this.getWeight((Invoker)invokers.get(i), invocation);
if (offset < 0) {
return (Invoker)invokers.get(i);
}
}
}
return (Invoker)invokers.get(this.random.nextInt(length));
}
reselect方法如下
private Invoker<T> reselect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected, boolean availablecheck) throws RpcException {
List<Invoker<T>> reselectInvokers = new ArrayList(invokers.size() > 1 ? invokers.size() - 1 : invokers.size());
Iterator i$;
Invoker invoker;
//如果设置了availablecheck 收集可用的且未调用失败过的服务 使用负载均衡算法拿到服务提供者
if (availablecheck) {
i$ = invokers.iterator();
label62:
while(true) {
do {
do {
//此内循环遍历invokers,找到可用的服务提供者 如果
if (!i$.hasNext()) {
if (reselectInvokers.size() > 0) {
return loadbalance.select(reselectInvokers, this.getUrl(), invocation);
}
//此处应该是跳出while(true) ??
break label62;
}
invoker = (Invoker)i$.next();
} while(!invoker.isAvailable());
} while(selected != null && selected.contains(invoker));
//如果服务可用 且未调用失败 则放入reselectInvokers 使用负载均衡算法 得到Invoker
reselectInvokers.add(invoker);
}
//如果没设置availablecheck 收集未调用失败过的服务 使用负载均衡算法拿到服务提供者
} else {
i$ = invokers.iterator();
label74:
while(true) {
do {
if (!i$.hasNext()) {
if (reselectInvokers.size() > 0) {
return loadbalance.select(reselectInvokers, this.getUrl(), invocation);
}
break label74;
}
invoker = (Invoker)i$.next();
} while(selected != null && selected.contains(invoker));
reselectInvokers.add(invoker);
}
}
//走到这里说明 仍没拿到服务提供方 这时候重试之前已调用失败的服务提供方(放入reselectInvokers)
if (selected != null) {
i$ = selected.iterator();
while(i$.hasNext()) {
invoker = (Invoker)i$.next();
if (invoker.isAvailable() && !reselectInvokers.contains(invoker)) {
reselectInvokers.add(invoker);
}
}
}
return reselectInvokers.size() > 0 ? loadbalance.select(reselectInvokers, this.getUrl(), invocation) : null;
}