dubbo集群之Cluster模块

Cluster 模块的目标是暴露 Invoker 对象,实现统一的调用入口

@SPI(FailoverCluster.NAME) //默认扩展点
public interface Cluster {
    @Adaptive//基于 Directory ,创建 Invoker 对象
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;
}
复制代码

我们看下类图

从上图可以看到,每一个Cluster实现都对应一个Invoker实现。

那么我们从哪里入手分析呢?

当服务消费者启动时,在获取代理时,会加入集群路由。这里出现 FailoverCluster 。

FailoverCluster

//它实现Cluster。调用失败时自动切换。会切换到其他服务器。但重试会带来延迟,要设定重试次数。通常用于读操作
public class FailoverCluster implements Cluster {

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);//这里出现了FailoverClusterInvoker
    }

}
复制代码

//AbstractClusterInvoker

我们先看FailoverClusterInvoker的基类。

public abstract class AbstractClusterInvoker<T> implements Invoker<T> {
    protected final Directory<T> directory;

    protected final boolean availablecheck;

    private AtomicBoolean destroyed = new AtomicBoolean(false);

    private volatile Invoker<T> stickyInvoker = null;

    public AbstractClusterInvoker(Directory<T> directory) {
        this(directory, directory.getUrl());
    }

    public AbstractClusterInvoker(Directory<T> directory, URL url) {
        this.directory = directory;
        this.availablecheck = url.getParameter(Constants.CLUSTER_AVAILABLE_CHECK_KEY, Constants.DEFAULT_CLUSTER_AVAILABLE_CHECK);
    }
}

//list获得所有服务提供者 Invoker 集合
protected List<Invoker<T>> list(Invocation invocation) throws RpcException {
    return directory.list(invocation);
}
 /**
     使用 LoadBalance 选择 invoker.
     LoadBalance 提供负责均衡策略
     invokers   候选的 Invoker 集合
     selected    已选过的 Invoker 集合. 注意:输入保证不重复

     */
    //从候选的 Invoker 集合,选择一个最终调用的 Invoker 对象
    protected Invoker<T> select(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())
            return null;
        String methodName = invocation == null ? "" : invocation.getMethodName();
        // 获得 sticky 配置项,方法级
        boolean sticky = invokers.get(0).getUrl().getMethodParameter(methodName, "sticky", false);
        {
            // 若 stickyInvoker 不存在于 invokers 中,说明不在候选中,需要置空,重新选择
            if (stickyInvoker != null && !invokers.contains(stickyInvoker)) {
                stickyInvoker = null;
            }
            // 若开启粘滞连接的特性,且 stickyInvoker 不存在于 selected 中,则返回 stickyInvoker 这个 Invoker 对象
            if (sticky && stickyInvoker != null && (selected == null || !selected.contains(stickyInvoker))) {
                if (availablecheck && stickyInvoker.isAvailable()) {
                    return stickyInvoker;
                }
            }
        }//该方法主要处理粘滞特性,具体选择逻辑在doSelect
        // 执行选择
        Invoker<T> invoker = doSelect(loadbalance, invocation, invokers, selected);
        // 若开启粘滞连接的特性,记录最终选择的 Invoker 到 stickyInvoker
        if (sticky) {
            stickyInvoker = invoker;
        }
        return invoker;
    }
 //从候选的 Invoker 集合,选择一个最终调用的 Invoker 对象
    private Invoker<T> doSelect(LoadBalance loadbalance, Invocation invocation, List<Invoker<T>> invokers, List<Invoker<T>> selected) throws RpcException {
        if (invokers == null || invokers.isEmpty())
            return null;
        // 1.如果只有一个 Invoker ,直接选择
        if (invokers.size() == 1)
            return invokers.get(0);
        //2.使用 Loadbalance ,选择一个 Invoker 对象
        Invoker<T> invoker = loadbalance.select(invokers, getUrl(), invocation);//10

        // 如果 selected中包含 或者 不可用&&availablecheck=true 则重新选择
        if ((selected != null && selected.contains(invoker))
                || (!invoker.isAvailable() && getUrl() != null && availablecheck)) {
            try {//3.重新选一个 Invoker 对象
                Invoker<T> rinvoker = reselect(loadbalance, invocation, invokers, selected, availablecheck);
                if (rinvoker != null) {
                    invoker = rinvoker;
                } else {
                    //看下第一次选的位置,如果不是最后,选+1位置.
                    int index = invokers.indexOf(invoker);
                    try {
                        //最后在避免碰撞
                        invoker = index < invokers.size() - 1 ? invokers.get(index + 1) : invokers.get(0);
                    } 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;
    }

  @Override//调用服务提供者的逻辑
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();// 校验是否销毁

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        // 获得所有服务提供者 Invoker 集合
        List<Invoker<T>> invokers = list(invocation);//-->进入6 7
        //获得 LoadBalance 对象
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);//8
        // 设置调用编号,若是异步调用
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 执行调用(子Cluster的Invoker实现类的服务调用的差异逻辑。)
        return doInvoke(invocation, invokers, loadbalance);
    }

  @Override//调用服务提供者的逻辑
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();// 校验是否销毁

        // binding attachments into invocation.
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
        // 获得所有服务提供者 Invoker 集合
        List<Invoker<T>> invokers = list(invocation);//-->进入6 7
        //获得 LoadBalance 对象
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);//8
        // 设置调用编号,若是异步调用
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 执行调用(子Cluster的Invoker实现类的服务调用的差异逻辑。)
        return doInvoke(invocation, invokers, loadbalance);
    }
复制代码

FailoverClusterInvoker

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        List<Invoker<T>> copyinvokers = invokers;
        checkInvokers(copyinvokers, invocation);// 检查copyinvokers即可用Invoker集合是否为空,如果为空,那么抛出异常
        String methodName = RpcUtils.getMethodName(invocation);
        // 得到最大可调用次数:最大可重试次数+1,默认最大可重试次数Constants.DEFAULT_RETRIES=2
        int len = getUrl().getMethodParameter(methodName, "retries", 2) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; //  保存最后一次调用的异常
        // 保存已经调用过的Invoker
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyinvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        // failover机制核心实现:如果出现调用失败,那么重试其他服务器
        for (int i = 0; i < len; i++) {
            if (i > 0) {
                //i > 0进行重新选择,避免重试时,候选 Invoker 集合,已发生变化。
                checkWhetherDestroyed();
                copyinvokers = list(invocation);
                // check again
                checkInvokers(copyinvokers, invocation);
            }
            // 根据负载均衡机制从copyinvokers中选择一个Invoker
            Invoker<T> invoker = select(loadbalance, invocation, copyinvokers, invoked);//9
            // 保存每次调用的Invoker
            invoked.add(invoker);
            // 设置已经调用的 Invoker 集合,到 Context 中
            RpcContext.getContext().setInvokers((List) invoked);
            try {// RPC 调用得到 Result
                Result result = invoker.invoke(invocation);//-
                // 重试过程中,将最后一次调用的异常信息以 warn 级别日志输出
                if (le != null && logger.isWarnEnabled()) {//le 非空,说明此时是重试调用成功
                    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()) { //  如果是业务性质的异常,不再重试,直接抛出.
                    throw e;
                }
                le = e;// 其他性质的异常统一封装成RpcException
            } catch (Throwable e) {
                le = new RpcException(e.getMessage(), e);
            } finally {//保存已经调用的网络地址集合。
                providers.add(invoker.getUrl().getAddress());
            }
        } // 最大可调用次数用完还得到Result的话,抛出RpcException异常:重试了N次还是失败,并输出最后一次异常信息
        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);
    }
复制代码

总结的时序图

转载于:https://juejin.im/post/5bf2cdea51882579cf011e89

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值