Dubbo-集群容错-解析

33 篇文章 0 订阅

Dubbo-集群容错感念

在这里插入图片描述

  1. 集群工作过程可分为两个阶段,第一个阶段是在服务消费者初始化期间,集群 Cluster 实现类为服务消费者创建 Cluster Invoker 实例,即上图中的 merge 操作。
  2. 第二个阶段是在服务消费者进行远程调用时。以 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 方法,进行真正的远程调用

Dubbo 主要提供了这样几种容错方式:
  • Failover Cluster - 失败自动切换 失败时会重试其它服务器
  • Failfast Cluster - 快速失败 请求失败后快速返回异常结果 不重试
  • Failsafe Cluster - 失败安全 出现异常 直接忽略 会对请求做负载均衡
  • Failback Cluster - 失败自动恢复 请求失败后 会自动记录请求到失败队列中
  • Forking Cluster - 并行调用多个服务提供者 其中有一个返回 则立即返回结果

信息缓存接口Directory

Directory是Dubbo中的一个接口,主要用于缓存当前可以被调用的提供者列表信息。我们在消费者进
行调用时都会通过这个接口来获取所有的提供者列表,再进行后续处理


public interface Directory<T> extends Node {

    /**
     * get service type.
     *获取服务的类型,也就是我们demo中所使用的HelloService
     * @return service type.
     */
    Class<T> getInterface();

    /**
     * list invokers.
     *
     * @return invokers
     * 根据本次调用的信息来获取所有可以被执行的提供者信息
     */
    List<Invoker<T>> list(Invocation invocation) throws RpcException;

    /**
     * 获取所有的提供者信息
     * @return
     */
    List<Invoker<T>> getAllInvokers();

    /**
     * 获取消费者URL
     * @return
     */
    URL getConsumerUrl();

}

AbstractDirectory #list方法


public abstract class AbstractDirectory<T> implements Directory<T> {

 public List<Invoker<T>> list(Invocation invocation) throws RpcException {
        if (destroyed) {
            throw new RpcException("Directory already destroyed .url: " + getUrl());
        }
		//交给子类进行处理
        return doList(invocation);
    }

    protected abstract List<Invoker<T>> doList(Invocation invocation) throws RpcException;

RegistryDirectory#doList 方法

    public List<Invoker<T>> doList(Invocation invocation) {
        //当没有提供者的时候会直接抛出异常
        if (forbidden) {
            //是否禁止的false 没有服务提供者,或者服务提供者被禁用了
            // 1. No service provider 2. Service providers are disabled
            throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "No provider available from registry " +
                    getUrl().getAddress() + " for service " + getConsumerUrl().getServiceKey() + " on consumer " +
                    NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() +
                    ", please check status of providers(disabled, not registered or in blacklist).");
        }

        if (multiGroup) {
            return this.invokers == null ? Collections.emptyList() : this.invokers;
        }

        List<Invoker<T>> invokers = null;
        try {
            //交给路由chain去处理并且获取所有的invokers
            // Get invokers from cache, only runtime routers will be executed.
            invokers = routerChain.route(getConsumerUrl(), invocation);
        } catch (Throwable t) {
            logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
        }

        return invokers == null ? Collections.emptyList() : invokers;
    }

在这里插入图片描述

需要使用到 RegistryProtocol

RegistryProtocol


    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        //获取注册中心的地址URL(主要用于转换协议),比如我们是使用的zookeeper,那么他就会转换为zookeeper://
        url = getRegistryUrl(url);
       // 获取注册中心配置信息
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }

        //适用于多个分组时使用
        // group="a,b" or group="*"
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
        String group = qs.get(GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        //真正进行构建invoker和我们上面的Directory
        return doRefer(cluster, registry, type, url);
    }
  • doRefer
    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        //创建RegistryDirectory 实例化Directory
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        //设置注册中心和所使用的协议
        directory.setRegistry(registry);
        directory.setProtocol(protocol);
        // all attributes of REFER_KEY
        Map<String, String> parameters = new HashMap<String, String>(directory.getConsumerUrl().getParameters());
        //生成监听路径URL
        URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
        if (directory.isShouldRegister()) {
            directory.setRegisteredConsumerUrl(subscribeUrl);
            //Directory中设置监听的consumerurl地址
            //在注册中心中注册消费者URL
// 也就是我们之前的Zookeeper的node中看到的consumer://
            registry.register(directory.getRegisteredConsumerUrl());
        }
        //构建路由连
        directory.buildRouterChain(subscribeUrl);
        //进行监听所有的的provider
        directory.subscribe(toSubscribeUrl(subscribeUrl));

        //加入到集群中
        Invoker<T> invoker = cluster.join(directory);
        List<RegistryProtocolListener> listeners = findRegistryProtocolListeners(url);
        if (CollectionUtils.isEmpty(listeners)) {
            return invoker;
        }
        //加入到集群中
        RegistryInvokerWrapper<T> registryInvokerWrapper = new RegistryInvokerWrapper<>(directory, cluster, invoker, subscribeUrl);
        for (RegistryProtocolListener listener : listeners) {
            listener.onRefer(this, registryInvokerWrapper);
        }
        return registryInvokerWrapper;
    }

routerChain.route

  public List<Invoker<T>> route(URL url, Invocation invocation) {
        //获取所有Invoder
        List<Invoker<T>> finalInvokers = invokers;
        for (Router router : routers) {
            //依次交给所有的路由规则进行选取路由列表
            //获取所有的路由规则,根据不用的路由规则去获取Invoker
            finalInvokers = router.route(finalInvokers, url, invocation);
        }
        return finalInvokers;
    }

路由规则

ConditionRouter 的实现来做说明

public class ConditionRouter extends AbstractRouter {
    public static final String NAME = "condition";

    private static final Logger logger = LoggerFactory.getLogger(ConditionRouter.class);
    //正则匹配
    protected static final Pattern ROUTE_PATTERN = Pattern.compile("([&!=,]*)\\s*([^&!=,\\s]+)");
    //是否满足条件
    protected Map<String, MatchPair> whenCondition;
    //当满足条件时,判断如何选择Invoker
    protected Map<String, MatchPair> thenCondition;

    private boolean enabled;

  • init方法

  public void init(String rule) {
        try {
            // 必须包含规则配置
            if (rule == null || rule.trim().length() == 0) {
                throw new IllegalArgumentException("Illegal route rule!");
            }
            rule = rule.replace("consumer.", "").replace("provider.", "");
            // 根据"=>"来判断when或者then条件
            int i = rule.indexOf("=>");
            // 分别根据"=>"来生成前后的规则
            String whenRule = i < 0 ? null : rule.substring(0, i).trim();
            String thenRule = i < 0 ? rule.trim() : rule.substring(i + 2).trim();
            Map<String, MatchPair> when = StringUtils.isBlank(whenRule) || "true".equals(whenRule) ? new HashMap<String, MatchPair>() : parseRule(whenRule);
            Map<String, MatchPair> then = StringUtils.isBlank(thenRule) || "false".equals(thenRule) ? null : parseRule(thenRule);
            // NOTE: It should be determined on the business level whether the `When condition` can be empty or not.
            this.whenCondition = when;
            this.thenCondition = then;
        } catch (ParseException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }
  • parseRule

  private static Map<String, MatchPair> parseRule(String rule)
            throws ParseException {
        Map<String, MatchPair> condition = new HashMap<String, MatchPair>();
        if (StringUtils.isBlank(rule)) {
            return condition;
        }
        // 当前所操作的数据
        // 用于后面循环中使用,标识上一次循环中所操作的信息
        // Key-Value pair, stores both match and mismatch conditions
        MatchPair pair = null;
        // Multiple values
        Set<String> values = null;
        // 转化每一个条件
        // 这里分别会对每一次的分割做匹配
        // host = 1.1.1.* & host != 1.1.1.2 & method=sayHello
        // 1. "" host
        // 2. "=" 1.1.1.x
        // 3. "&" host
        // 4. "!=" 1.1.1.2
        final Matcher matcher = ROUTE_PATTERN.matcher(rule);
        while (matcher.find()) { // Try to match one by one
            // 分隔符
            String separator = matcher.group(1);
            //内容
            String content = matcher.group(2);
            // Start part of the condition expression.
            // 如果不存在分隔符
            // 则认为是首个判断
            if (StringUtils.isEmpty(separator)) {
                pair = new MatchPair();
                // 则直接放入当前condition
                condition.put(content, pair);
            }
            //如果是"&"则代表并且
            // The KV part of the condition expression
            else if ("&".equals(separator)) {
                // 如果当前的when或者then中不包含该判定条件则添加则放入
                // 否则当前的condition就需要拿出来
                if (condition.get(content) == null) {
                    pair = new MatchPair();
                    condition.put(content, pair);
                } else {
                    pair = condition.get(content);
                }
            }
            // The Value in the KV part.
            else if ("=".equals(separator)) {
                if (pair == null) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }
            // 如果是等于的比较,则需要将值放入matches中
                values = pair.matches;
                values.add(content);
            }
            // The Value in the KV part.
            else if ("!=".equals(separator)) {
                if (pair == null) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }
                    //如果为不等于,则需要放入到不等于中
                values = pair.mismatches;
                values.add(content);
            }

            // 如果values是多个的
            // The Value in the KV part, if Value have more than one items.
            else if (",".equals(separator)) { // Should be separated by ','
                if (values == null || values.isEmpty()) {
                    throw new ParseException("Illegal route rule \""
                            + rule + "\", The error char '" + separator
                            + "' at index " + matcher.start() + " before \""
                            + content + "\".", matcher.start());
                }
                //则分别加入到values列表中
                values.add(content);
            } else {
                throw new ParseException("Illegal route rule \"" + rule
                        + "\", The error char '" + separator + "' at index "
                        + matcher.start() + " before \"" + content + "\".", matcher.start());
            }
        }
        return condition;
    }
  • 核心方法 route

 public <T> List<Invoker<T>> route(List<Invoker<T>> invokers, URL url, Invocation invocation)
            throws RpcException {
        //不启用的时,则直接返回提供者的列表
        if (!enabled) {
            return invokers;
        }

        if (CollectionUtils.isEmpty(invokers)) {
            //空也直接返回
            return invokers;
        }
        try {
            //判断是否满足判断条件,不满足直接返回列表
            if (!matchWhen(url, invocation)) {
                return invokers;
            }
            List<Invoker<T>> result = new ArrayList<Invoker<T>>();
            if (thenCondition == null) {
                logger.warn("The current consumer in the service blacklist. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey());
                return result;
            }
            //依次判断每一个invoker的url是否满足条件
            for (Invoker<T> invoker : invokers) {
                if (matchThen(invoker.getUrl(), url)) {
                    result.add(invoker);
                }
            }
            if (!result.isEmpty()) {
                //如果不为空则直接返回
                return result;
            } else if (force) {
                //如果为空,并且必须要走这个条件时,则直接返回空
                logger.warn("The route result is empty and force execute. consumer: " + NetUtils.getLocalHost() + ", service: " + url.getServiceKey() + ", router: " + url.getParameterAndDecoded(RULE_KEY));
                return result;
            }
        } catch (Throwable t) {
            logger.error("Failed to execute condition router rule: " + getUrl() + ", invokers: " + invokers + ", cause: " + t.getMessage(), t);
        }
        return invokers;
    }
Cluster组件
//默认使用failover作为实现 failover
@SPI(FailoverCluster.NAME)
public interface Cluster {

    /**
     * Merge the directory invokers to a virtual invoker.
     *
     * @param <T>
     * @param directory
     * @return cluster invoker
     * @throws RpcException
     * 生成一个新的invoker
     */
    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;

}

在这里插入图片描述
提供了Abstract实现
org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster


 @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        // 使用子类doJoin来真正生成Invoker
        // 并且使用拦截器的方式进行一层封装
        return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
    }
  //对invoker进行封装
    private <T> Invoker<T> buildClusterInterceptors(AbstractClusterInvoker<T> clusterInvoker, String key) {

        AbstractClusterInvoker<T> last = clusterInvoker;
        //获取所有的拦截器
        List<ClusterInterceptor> interceptors = ExtensionLoader.getExtensionLoader(ClusterInterceptor.class).getActivateExtension(clusterInvoker.getUrl(), key);
        //拦截器不是空就 进一步封装
        if (!interceptors.isEmpty()) {
            for (int i = interceptors.size() - 1; i >= 0; i--) {
                final ClusterInterceptor interceptor = interceptors.get(i);
                final AbstractClusterInvoker<T> next = last;
                last = new InterceptorInvokerNode<>(clusterInvoker, interceptor, next);
            }
        }
        return last;
    }

FailoverCluster


public class FailoverCluster extends AbstractCluster {

    public final static String NAME = "failover";

    @Override
    public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
        //创建一个新的Invoker
        return new FailoverClusterInvoker<>(directory);
    }

}

Invoker接口得知,其中最关键的方式是 invoke 方法

org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker#invoke方法

   public Result invoke(final Invocation invocation) throws RpcException {
        //检查是否已经关闭了
        checkWhetherDestroyed();

        //拷贝当前RPCContext中的附加信息到当前的invocation中
        // binding attachments into invocation.
        Map<String, Object> contextAttachments = RpcContext.getContext().getObjectAttachments();
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addObjectAttachments(contextAttachments);
        }
        //找寻出所有支持的invoker,已经路由过的
        List<Invoker<T>> invokers = list(invocation);
         初始化负载均衡器
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        //用于适配异步请求使用
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        //交给子类进行真正处理请求
        return doInvoke(invocation, invokers, loadbalance);
    }

org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker#doInvoke


public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
       //如果没有任何的invoker则抛出异常
        List<Invoker<T>> copyInvokers = invokers;
        //检查是否有异常
        checkInvokers(copyInvokers, invocation);
        //获取要执行的方法名称
        String methodName = RpcUtils.getMethodName(invocation);
        //获取这个方法最大的重试次数
        int len = getUrl().getMethodParameter(methodName, RETRIES_KEY, DEFAULT_RETRIES) + 1;
        if (len <= 0) {
            len = 1;
        }
        // retry loop.
        RpcException le = null; // last exception.
        List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
        Set<String> providers = new HashSet<String>(len);
        //通过for循环的形式表示可以重试的次数
        for (int i = 0; i < len; i++) {
            //Reselect before retry to avoid a change of candidate `invokers`.
            //NOTE: if `invokers` changed, then `invoked` also lose accuracy.
            if (i > 0) {
                //每次都执行一次是否关闭当前consumer的判断
                checkWhetherDestroyed();
                //重新获取一遍invoker列表
                copyInvokers = list(invocation);
                // check again
                //再次进行一次存在invoker的检查
                checkInvokers(copyInvokers, invocation);
            }
            //选择具体的invoker(交给负载均衡)
            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
            //增加到已经执行过得invoker列表中
            invoked.add(invoker);
            RpcContext.getContext().setInvokers((List) invoked);
            try {
                //让其真正的去进行执行操作-执行操作
                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);
    }
负载均衡实现原理
//默认使用随机算法 random
@SPI(RandomLoadBalance.NAME)
public interface LoadBalance {

    /**
     * 进行选择真正的invoker
     */
    @Adaptive("loadbalance")
    <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) throws RpcException;

}

LoadBalance 依旧选择了 AbstractLoadBalance 作为基础的实现类
在这里插入图片描述

org.apache.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance

    @Override
    public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        //如果为空就直接返回
        if (CollectionUtils.isEmpty(invokers)) {
            return null;
        }
        //如果只有一个就获取第一个返回
        if (invokers.size() == 1) {
            return invokers.get(0);
        }
        //调用子类的实现
        return doSelect(invokers, url, invocation);
    }

    protected abstract <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation);
默认的随机算法是如何实现的

在这里插入图片描述

org.apache.dubbo.rpc.cluster.loadbalance.RandomLoadBalance

    protected <T> Invoker<T> doSelect(List<Invoker<T>> invokers, URL url, Invocation invocation) {
        // Number of invokers
        //获取Invoker数量
        int length = invokers.size();
        // Every invoker has the same weight?
        //相同的权重
        boolean sameWeight = true;
        // the weight of every invokers
        //声明一个权重数组大小与invokder大小一样
        int[] weights = new int[length];
        // the first invoker's weight
        //获取第一个invoker的权重 首个invoker的权重信息
        int firstWeight = getWeight(invokers.get(0), invocation);
        //赋值
        weights[0] = firstWeight;
        // The sum of weights
        //计算总共的权重,并且吧每一个invoker的权重进行设置到列表中
        int totalWeight = firstWeight;
        for (int i = 1; i < length; i++) {
            int weight = getWeight(invokers.get(i), invocation);
            // save for later use
            weights[i] = weight;
            // Sum
            totalWeight += weight;
            if (sameWeight && weight != firstWeight) {
                sameWeight = false;
            }
        }
        //如果权重不相同
        if (totalWeight > 0 && !sameWeight) {
            //通过总共的权重来随机分配
            // If (not every invoker has the same weight & at least one invoker's weight>0), select randomly based on totalWeight.
            int offset = ThreadLocalRandom.current().nextInt(totalWeight);
            // Return a invoker based on the random value.
            //看看最终落到哪一个机器上去
            for (int i = 0; i < length; i++) {
                offset -= weights[i];
                if (offset < 0) {
                    return invokers.get(i);
                }
            }
        }
        //如果权重都是相同的话,则随机选取一个即可
        // If all invokers have the same weight value or totalWeight=0, return evenly.
        return invokers.get(ThreadLocalRandom.current().nextInt(length));
    }
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值