Dubbo集群的容错机制

102 阅读3分钟

在我们日常微服务使用中,调用的服务一般都会有多个节点,那么这个时候就会涉及到多个服务可能其中有一或者多个服务不可用,我们要保证我们的服务尽可能调用返回一个正确的响应结果。再或者说我们的多个服务节点在被调用时应该可以被轮询,避免单个节点承受过大的请求压力。

其实在Dubbo中也是存在这样的机制的,也就是我们本文讨论的容错机制。话不多说,直接从源码开始分析。当Dubbo中的服务提供者将服务注册到注册中心后,在我们服务消费者启动的时候,这时候会初始化我们的每个服务的容错机制。

从源码层面分析的话,机制的入口是从ReferenceConfig类的

public synchronized T get() {
    checkAndUpdateSubConfigs();

    if (destroyed) {
        throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
    }
    if (ref == null) {
        //消费服务代理初始化,这里会涉及到我们的容错Invoker的创建
        init();
    }
    return ref;
}

然后根据断点的跟进,会来到RegistryProtocol类的订阅服务方法doRefer,此调用过程涉及到dubbo的SPI机制,不懂的话可以往前看看本专栏的SPI机制。

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    directory.setRegistry(registry);
    directory.setProtocol(protocol);

    directory.buildRouterChain(subscribeUrl);
    directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
            PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
    //这里就是我们的重点:集群容错机制的创建
    Invoker invoker = cluster.join(directory);
    ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
    return invoker;
}

在cluster.join这里又是使用的Dubbo的SPI机制,默认加载的是Cluster的实现FailoverCluster,理解不了的同学可以自己好好看看SPI机制以及自适应扩展机制,这个不是本篇的重点。

public class FailoverCluster implements Cluster {

    public final static String NAME = "failover";

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        //创建一个失败重试的Invoker
        return new FailoverClusterInvoker<T>(directory);
    }

}

接下来我们看一下FailoverClusterInvoker中到底是如何实现该机制的

public Result doInvoke(Invocation invocation, final List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
    List<Invoker<T>> copyInvokers = invokers;
    //检查invoker是否为空
    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.
    //保存已经请求过的invoker
    List<Invoker<T>> invoked = new ArrayList<Invoker<T>>(copyInvokers.size()); // invoked invokers.
    Set<String> providers = new HashSet<String>(len);
    for (int i = 0; i < len; i++) {
        //第一次不会执行这里,涉及到重试的选择其他服务机制
        if (i > 0) {
            checkWhetherDestroyed();
            copyInvokers = list(invocation);
            // 检查服务是否为空,为空抛出异常
            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("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);
}

以上就是dubbo中默认的容错机制,当然,在Dubbo中还为我们提供了多样化的容错机制,比如下面的FailsafeClusterInvoker(安全失败),意思就是失败后返回一个null的结果,并不会报错。

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

    @Override
    public Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            logger.error("Failsafe ignore exception: " + e.getMessage(), e);
            return AsyncRpcResult.newDefaultAsyncResult(null, null, invocation); // ignore
        }
    }
}

如下图,Dubbo一共提供了9种容错机制供我们选择,可以说非常的丰富了。大家可以自己去深入了解一下。

image.png