美文网首页
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