美文网首页
Dubbo集群容错——Mock

Dubbo集群容错——Mock

作者: 就这些吗 | 来源:发表于2020-07-25 22:53 被阅读0次

本系列主要参考官网文档、芋道源码的源码解读和《深入理解Apache Dubbo与实战》一书。Dubbo版本为2.6.1。

文章内容顺序:

  1. 什么是Mock

  2. Mock的UML图

  3. Mock的代码逻辑

  • 3.1MockClusterWrapper
  • 3.2MockClusterInvoker
  • 3.3MockClusterInvoker#doMockInvoke
  • 3.4MockClusterInvoker#selectMockInvoker
  1. MockClusterInvoker#selectMockInvoker是怎么通过setAttachment来获得MockInvoker的?
  • 4.1 当Directory创建时,会注入Router,此时就会添加一个MockInvokerSelector,每当调用AbstractDirector#list方法时,就会调用这个MockInvokerSelector
  • 4.2MockInvokersSelector#route
  • 4.3 MockInvokersSelector#getMockedInvokers
  • 4.4 MockInvokersSelector#hasMockProviders
  • 4.5 那如果没有protocol为mock,又要进行本地伪装怎么办呢?
  • 4.6 再介绍下如果真的有配置protocol为mock的方法,会怎么样。
  1. MockInvoker

1. 什么是Mock

本地伪装:通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
服务降级:可以通过服务降级功能,临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
推荐先阅读官网的这两个概念。
《Dubbo 用户指南 —— 本地伪装》
《Dubbo 用户指南 —— 服务降级》

2. Mock的UML图

image.png

以上类图分成两个部分:不要在意马赛克。
MockClusterWrapper + MockClusterInvoker+ MockClusterSelector
MockProtocol + MockInvoker

3. Mock的代码逻辑

3.1MockClusterWrapper

public class MockClusterWrapper implements Cluster {

    /**
     * 真正的 Cluster 对象
     */
    private Cluster cluster;

    public MockClusterWrapper(Cluster cluster) {
        this.cluster = cluster;
    }

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }

}

注意:cluster 字段才是真正的 Cluster 对象(在Cluster章节介绍的那些)。因为 MockClusterWrapperDubbo SPI Wrapper类,所以对应的 Cluster 对象,都会被它所包装。
这边直接返回了一个MockClusterInvoker,那就进去看看吧。

3.2MockClusterInvoker

MockClusterInvokerDubbo Mock 的核心类,主要功能有三个:

  • 判断是否需要开启 Mock 机制,由invoke 方法完成。
  • 如果需要Mock,由 doMockInvoke方法完成,实际是委托给 Mock Invoker对象来执行。
  • 那么如何拿到这个Mock Invoker对象对象呢?则需要根据 MockInvokersSelector 过滤出对应的 Mock Invoker,由 selectMockInvoker方法完成,相当于交由MockInvokersSelector 完成了路由操作。
    public MockClusterInvoker(Directory<T> directory, Invoker<T> invoker) {
        this.directory = directory;
        this.invoker = invoker;
    }
    public Result invoke(Invocation invocation) throws RpcException {
        Result result;
        // 获得 "mock" 配置项,有多种配置方式
        String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
        //【第一种】无 mock
        if (value.length() == 0 || value.equalsIgnoreCase("false")) {
            // no mock
            // 调用原 Invoker ,发起 RPC 调用
            result = this.invoker.invoke(invocation);
        //【第二种】强制服务降级 https://dubbo.gitbooks.io/dubbo-user-book/demos/service-downgrade.html
        } else if (value.startsWith("force")) {
            if (logger.isWarnEnabled()) {
                logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
            }
            // force:direct mock
            // 直接调用 Mock Invoker ,执行本地 Mock 逻辑
            result = doMockInvoke(invocation, null);
        // 【第三种】失败服务降级 https://dubbo.gitbooks.io/dubbo-user-book/demos/service-downgrade.html
        } else {
            // fail-mock
            try {
                // 调用原 Invoker ,发起 RPC 调用
                result = this.invoker.invoke(invocation);
            } catch (RpcException e) {
                // 业务性异常,直接抛出
                if (e.isBiz()) {
                    throw e;
                } else {
                    if (logger.isWarnEnabled()) {
                        logger.info("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + directory.getUrl(), e);
                    }
                    // 失败后,调用 Mock Invoker ,执行本地 Mock 逻辑
                    result = doMockInvoke(invocation, e);
                }
            }
        }
        return result;
    }

可以看到,当执行invoke时,会进行一下逻辑判断
第一种:无 Mock。只调用真正的 invoker 的 #invoke(invocation)方法,发起 RPC 调用,即不进行 Mock 逻辑。
第二种:"mock" 配置项以 "force" 开头,强制服务降级。直接调用 #doMockInvoke(invocation, null) 方法,调用Mock Invoker ,执行本地 Mock 逻辑。
第三种:服务失败服务降级
先调用真正的invoker#invoke(invocation)方法,发起 RPC 调用,当发生RpcException异常且为业务性异常时,直接抛出异常。如果不是业务性异常,调用#doMockInvoke(invocation, null) 方法,调用 Mock Invoker ,执行本地 Mock 逻辑。
Mock需要的配置如下:
<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService" mock="com.alibaba.dubbo.demo.DemoServiceImplMock"/>

3.3MockClusterInvoker#doMockInvoke

    private Result doMockInvoke(Invocation invocation, RpcException e) {
        Result result;
        // 第一步,获得 Mock Invoker 对象
        Invoker<T> mInvoker;
        // 路由匹配 Mock Invoker 集合
        List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
        // 如果不存在,创建 MockInvoker 对象
        if (mockInvokers == null || mockInvokers.isEmpty()) {
            mInvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
        // 如果存在,选择第一个
        } else {
            mInvoker = mockInvokers.get(0);
        }
        // 第二步,调用,执行本地 Mock 逻辑
        try {
            result = mInvoker.invoke(invocation);
        } catch (RpcException me) {
            if (me.isBiz()) {
                result = new RpcResult(me.getCause());
            } else {
                throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause());
            }
        } catch (Throwable me) {
            throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
        }
        return result;
    }

第一步:先想办法获得 MockInvoker 对象。
调用 #selectMockInvoker(invocation) 方法,路由匹配 Mock Invoker 集合。
存在,创建 MockInvoker 对象。
不能再,选择第一个 Mock Invoker 对象。
第二步:调用 MockInvoker#invoke(invocation) 方法,执行本地 Mock 逻辑。

我们先暂且不考虑MockInvoker的实现到底是怎么样的,他里面是怎么调用invoke的,我们来看看selectMockInvoker是怎么得到mockInvokers的。

3.4MockClusterInvoker#selectMockInvoker

    private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
        List<Invoker<T>> invokers = null;
        // TODO generic invoker?
        if (invocation instanceof RpcInvocation) {
            // 存在隐含契约(虽然在接口声明中增加描述,但扩展性会存在问题.同时放在 attachment 中的做法需要改进
            ((RpcInvocation) invocation).setAttachment(Constants.INVOCATION_NEED_MOCK, Boolean.TRUE.toString());
            // directory 根据 invocation 中 attachment 是否有 Constants.INVOCATION_NEED_MOCK,来判断获取的是 normal invokers or mock invokers
            try {
                invokers = directory.list(invocation);
            } catch (RpcException e) {
                if (logger.isInfoEnabled()) {
                    logger.info("Exception when try to invoke mock. Get mock invokers error for service:" + directory.getUrl().getServiceInterface() + ", method:" + invocation.getMethodName() + ", will contruct a new mock with 'new MockInvoker()'.", e);
                }
            }
        }
        return invokers;
    }

directory根据 invocation中 是否有 Constants.INVOCATION_NEED_MOCK ,来判断获取的是一个 normal invoker 还是一个 mock invoker
如果 directory#list返回多个Mock invoker ,只使用第一个 invoker .

在此之前,我想通过介绍通过调用invocation#setAttachment,怎么得到MockInvoker的。

4. MockClusterInvoker#selectMockInvoker是怎么通过setAttachment来获得MockInvoker的?

4.1 当Directory创建时,会注入Router,此时就会添加一个MockInvokerSelector,每当调用AbstractDirector#list方法时,就会调用这个MockInvokerSelector

下面的图是AbstractDirector#setRouters()调用的时机,可以看到当创建一个Directory的时候,就会进入到setRouters()方法中,这个方法每次都会添加一个MockInvokersSelector,而当我们调用AbstractDirector#list想获取Invokers时,就会轮询这个routers集合,必定会调用到MockInvokersSelector#route方法。知道了这点之后,就需要看看MockInvokersSelector#route方法的实现了。

image.png
image.png
image.png

4.2MockInvokersSelector#route

    public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers, URL url, final Invocation invocation) throws RpcException {
        // 获得普通 Invoker 集合
        if (invocation.getAttachments() == null) {
            return getNormalInvokers(invokers);
        } else {
            // 获得 "invocation.need.mock" 配置项
            String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
            // 获得普通 Invoker 集合
            if (value == null) {
                return getNormalInvokers(invokers);
            // 获得 MockInvoker 集合
            } else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
                return getMockedInvokers(invokers);
            }
        }
        // 其它,不匹配,直接返回 `invokers` 集合
        return invokers;
    }

在这个方法里,MockClusterInvoker#selectMockInvoker设置的invocation.need.mock终于用到了,
这个方法attachmentsnullinvocation.need.mocknull,则返回 普通 Invoker 集合。(会过滤掉MockInvoker集合)
invocation.need.mock=true 则返回 MockInvoker 集合。
所以当我们没有设置Mock的时候,设置的这个MockInvokersSelector就相当于不存在。

4.3 MockInvokersSelector#getMockedInvokers

这里简单介绍下MockInvokersSelector#getMockedInvokers

   private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
        // 不包含 MockInvoker 的情况下,直接返回 null
        if (!hasMockProviders(invokers)) {
            return null;
        }
        // 过滤掉普通 kInvoker ,创建 MockInvoker 集合
        List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1); // 一般情况就一个,所以设置了默认数组大小为 1 。
        for (Invoker<T> invoker : invokers) {
            if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
                sInvokers.add(invoker);
            }
        }
        return sInvokers;
    }

4.4 MockInvokersSelector#hasMockProviders

再看一下MockInvokersSelector#hasMockProviders(invokers)的判别逻辑

    private <T> boolean hasMockProviders(final List<Invoker<T>> invokers) {
        boolean hasMockProvider = false;
        for (Invoker<T> invoker : invokers) {
            if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) { // 协议为 "mock"
                hasMockProvider = true;
                break;
            }
        }
        return hasMockProvider;
    }

注意,上面方法传参中的invkers是这个接口方法不同机器的所有的invokers
上面的判别方式也很简单,判断这些invokersprotocol是否为mock

4.5 那如果没有protocolmock,又要进行本地伪装怎么办呢?

也很简单,在getMockedInvokers中会直接返回一个null,当doMockInvoke收到由selectMockInvoker(invocation)传达的mockInvoker,发现他为空,通过配置解析出来的url就直接创建一个MockInvoker对象,在里面进行本地伪装。

4.6 再介绍下如果真的有配置protocol为mock的方法,会怎么样。

final public class MockProtocol extends AbstractProtocol {

    public int getDefaultPort() {
        return 0;
    }

    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        throw new UnsupportedOperationException();
    }

    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return new MockInvoker<T>(url);
    }
}

export(Invoker<T> invoker)实现方法,不允许调用,直接抛出 UnsupportedOperationException 异常。
refer(Class<T> type, Url) 实现方法,引用创建MockInvoker对象。一般情况下,我们可以通过 dubbo-admin 运维平台或者直接向 Zookeeper写入静态 URL
顺带一提,这个protocol是在com.alibaba.dubbo.rpc.support,继承了AbstractProtocolMockProtocol,在Dubbo还有一个MockProtocol与此同名但是不在同一个包中。
从这个protocl的实现我们也可以看出来Mock是不允许provider来实现的,而且当Consumer调用refer实现时,与MockClusterInvoker#doMockInvoke一样,也是直接返回了一个MockInvoker

5. MockInvoker

最后我们终于可以来看一下这个MockInvoker的逻辑了

    public Result invoke(Invocation invocation) throws RpcException {
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(this);
        }
        // 获得 `"mock"` 配置项,方法级 > 类级
        String mock = getUrl().getParameter(invocation.getMethodName() + "." + Constants.MOCK_KEY);
        if (StringUtils.isBlank(mock)) {
            mock = getUrl().getParameter(Constants.MOCK_KEY);
        }
        if (StringUtils.isBlank(mock)) { // 不允许为空
            throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
        }
        // 标准化 `"mock"` 配置项
        mock = normalizedMock(URL.decode(mock));
        // 等于 "return " ,返回值为空的 RpcResult 对象
        if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())) {
            RpcResult result = new RpcResult();
            result.setValue(null);
            return result;
            // 以 "return " 开头,返回对应值的 RpcResult 对象
        } else if (mock.startsWith(Constants.RETURN_PREFIX)) {
            mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
            mock = mock.replace('`', '"');
            try {
                // 解析返回类型
                Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
                // 解析返回值
                Object value = parseMockValue(mock, returnTypes);
                // 创建对应值的 RpcResult 对象,并返回
                return new RpcResult(value);
            } catch (Exception ew) {
                throw new RpcException("mock return invoke error. method :" + invocation.getMethodName() + ", mock:" + mock + ", url: " + url, ew);
            }
            // 以 "throw" 开头,抛出 RpcException 异常
        } else if (mock.startsWith(Constants.THROW_PREFIX)) {
            mock = mock.substring(Constants.THROW_PREFIX.length()).trim();
            mock = mock.replace('`', '"');
            if (StringUtils.isBlank(mock)) { // 禁止为空
                throw new RpcException(" mocked exception for Service degradation. ");
            } else { // user customized class
                // 创建自定义异常
                Throwable t = getThrowable(mock);
                // 抛出业务类型的 RpcException 异常
                throw new RpcException(RpcException.BIZ_EXCEPTION, t);
            }
            // 自定义 Mock 类,执行自定义逻辑
        } else {
            try {
                // 创建 Invoker 对象
                Invoker<T> invoker = getInvoker(mock);
                // 执行 Invoker 对象的调用逻辑
                return invoker.invoke(invocation);
            } catch (Throwable t) {
                throw new RpcException("Failed to create mock implemention class " + mock, t);
            }
        }
    }

invoke 执行服务降级,首先获取 mock 参数,并对 mock 参数进行处理,如去除 force:fail:前缀。Dubbo 服务降级有三种处理情况:

  • return:直接返回,可以是 emptynulltruefalsejson格式,由方式 parseMockValue 进行解析。
  • throw:直接抛出异常。如果没有指定异常,抛出 RpcException,否则抛出指定的 Exception。
  • xxxServiceMock:执行 xxxServiceMock 方法。如果 mock=true 或 mock=defalut则查找 xxxServiceMock 方法后执行,如果 mock=com.dubbo.testxxxService 则执行指定的方法。

相关文章

网友评论

      本文标题:Dubbo集群容错——Mock

      本文链接:https://www.haomeiwen.com/subject/lrwqkktx.html