【Dubbo】集群容错

一. 概述

版本:2.7.8

服务提供端因为网络或其它原因导致调用不成功后消费端的处理逻辑

二. Dubbo集群容错模式

Failover Cluster:失败重试

  • 当服务消费方调用服务提供者失败后,会自动切换到其他服务提供者服务器进行重试。
  • 使用<dubbo:reference retries="2"/>来进行接口级别的重试次数(一共调用三次)

Failfast Cluster:快速失败

  • 当服务消费方调用服务提供者失败后,立即报错,也就是只调用一次。

Failsafe Cluster:安全失败

  • 当服务消费者调用服务出现异常时,直接忽略异常。这种模式通常用于写入审计日志等操作。

Failback Cluster:失败自动恢复

  • 当服务消费端调用服务出现异常后,在后台记录失败的请求,并按照一定的策略后期再进行重试。这种模式通常用于消息通知操作。

Forking Cluster:并行调用

  • 当消费方调用一个接口方法后,Dubbo Client会并行调用多个服务提供者的服务,只要其中有一个成功即返回。

Broadcast Cluster:广播调用

  • 当消费者调用一个接口方法后,Dubbo Client会逐个调用所有服务提供者,任意一台服务器调用异常则这次调用就标志失败。
  • 这种模式通常用于通知所有提供者更新缓存或日志等本地资源信息。

三. Cluster接口及实现类结构图

在这里插入图片描述

四. 源码解析

1. 接口Cluster

  • 集群容错模式默认为:failover
package org.apache.dubbo.rpc.cluster;

@SPI(Cluster.DEFAULT)
public interface Cluster {

    String DEFAULT = FailoverCluster.NAME;

    @Adaptive
    <T> Invoker<T> join(Directory<T> directory) throws RpcException;

    static Cluster getCluster(String name) {
        return getCluster(name, true);
    }

    static Cluster getCluster(String name, boolean wrap) {
        if (StringUtils.isEmpty(name)) {
            name = Cluster.DEFAULT;
        }
        return ExtensionLoader.getExtensionLoader(Cluster.class).getExtension(name, wrap);
    }
}

2. AbstractCluster抽象类

  • 使用模板模式执行公共业务,创建拦截器
  • 各个实现类实现抽象方法(doJoin)
package org.apache.dubbo.rpc.cluster.support.wrapper;

public abstract class AbstractCluster implements Cluster {

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return buildClusterInterceptors(doJoin(directory), directory.getUrl().getParameter(REFERENCE_INTERCEPTOR_KEY));
    }

    protected abstract <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException;
}

3. Failover重试模式

FailoverCluster实现类

  • doJoin初始化FailoverClusterInvoker类对象
package org.apache.dubbo.rpc.cluster.support;

import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Directory;
import org.apache.dubbo.rpc.cluster.support.wrapper.AbstractCluster;

public class FailoverCluster extends AbstractCluster {

    public final static String NAME = "failover";

    @Override
    public <T> AbstractClusterInvoker<T> doJoin(Directory<T> directory) throws RpcException {
        
        return new FailoverClusterInvoker<>(directory);
    }
}

FailoverClusterInvoker

  • doInvoke执行策略
  • 从服务提供端的多个Invoker选择一个调用
package org.apache.dubbo.rpc.cluster.support;

public class FailoverClusterInvoker<T> extends AbstractClusterInvoker<T> {

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {

        // 所有服务提供者
        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.

        // invoked invokers.
        // 这里不是使用所有服务提供者初始化invoked对象,只是使用了指定长度初始化空的List数组
        List<Invoker<T>> invoked = new ArrayList<>(copyInvokers.size());

        Set<String> providers = new HashSet<>(len);

        // 容错集群,重试调用
        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) {
                checkWhetherDestroyed();
                copyInvokers = list(invocation);
                // check again
                checkInvokers(copyInvokers, invocation);
            }

            // 根据复杂均衡策略选择一个服务提供者
            Invoker<T> invoker = select(loadbalance, invocation, copyInvokers, invoked);
            invoked.add(invoker);

            RpcContext.getContext().setInvokers((List) invoked);

            try {
            
                // 调用远程服务
                Result result = invoker.invoke(invocation);
                if (le != null && logger.isWarnEnabled()) {
                
                // 简化代码
                    logger.warn("");
                }
                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();
    }

4. 其它模式(参考源码)

五. 实现自定义Cluster

1. 消费端使用

2. 功能:选取服务提供者IP等于指定IP的Invoker调用

3. 定义实现类

MyCluster实现类

package org.apache.dubbo.demo.consumer.cluster;

import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.Cluster;
import org.apache.dubbo.rpc.cluster.Directory;

public class MyCluster implements Cluster {

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {

        return new MyClusterInvoker(directory);
    }
}

MyClusterInvoker实现类

package org.apache.dubbo.demo.consumer.cluster;

protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance)
        throws RpcException {

    // 1.查看是否设置了指定ip
    String ip = (String) RpcContext.getContext().get("ip");
    if (StringUtils.isBlank(ip)) {
        throw new RuntimeException("ip is blank ");
    }
    // 2.检查是否有可用Invoker
    checkInvokers(invokers, invocation);

    // 3.根据指定ip获取对应Invoker(选取 服务提供者IP等于指定IP的Invoker)
    Invoker<T> invoked = invokers.stream().filter(invoker -> invoker.getUrl().getHost().equals(ip)).findFirst()
            .orElseThrow(() -> new RpcException());

    // 4. 使用选取的Invoker发起远程调用,失败则抛出异常
    try {

        return invoked.invoke(invocation);

    } catch (Throwable e) {

       // 简化代码
        throw new RpcException();
    }
}

引入SPI

  • resources新增文件夹META-INF.dubbo
  • 新建文件:org.apache.dubbo.rpc.cluster.Cluster
  • 文件内容:myCluster=org.apache.dubbo.demo.consumer.cluster.MyCluster

六. 使用自定义Cluster

public static void main(String[] args) {

        // 1.创建服务引用对象实例
        ReferenceConfig<GreetingService> referenceConfig = new ReferenceConfig<>();

        // 2.设置应用程序信息
        referenceConfig.setApplication(new ApplicationConfig("dubbo-consumer"));

        // 3.设置服务注册中心
        referenceConfig.setRegistry(new RegistryConfig("ZKAddress"));

        // 4.设置服务接口和超时时间
        referenceConfig.setInterface(GreetingService.class);
        referenceConfig.setTimeout(5000);

        // 5.设置自定义集群容错策略并指定需要匹配的IP
        referenceConfig.setCluster("myCluster");
        RpcContext.getContext().set("ip", ip);

        // 6.设置服务分组与版本
        referenceConfig.setVersion("1.0.0");
        referenceConfig.setGroup("dubbo");

        // 7.引用服务
        GreetingService greetingService = referenceConfig.get();
        
    }
posted @ 2021-02-01 17:57    阅读(152)  评论(0编辑  收藏  举报