dubbo源码分析-集群容错(一)

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;
    }

转载于:https://my.oschina.net/odetteisgorgeous/blog/3019393

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值