zoukankan      html  css  js  c++  java
  • Dubbo 系列(07-5)集群容错

    Dubbo 系列(07-5)集群容错 - Mock

    [toc]

    Spring Cloud Alibaba 系列目录 - Dubbo 篇

    1. 背景介绍

    相关文档推荐:

    1. Dubbo 实战 - 服务降级
    2. Dubbo 实战 - 本地伪装
    3. Dubbo 实战 - 本地存根

    Dubbo 的集群容错中默认会组装 MockClusterWrapper,它实现了 Dubbo 的服务降级和本地伪装。

    1.1 服务降级

    服务降级配置方式,更多参考官网 Dubbo 实战 - 服务降级

    <dubbo:reference interface="com.foo.BarService" mock="force:return+null"/>
    

    或向注册中心写入动态配置覆盖规则:

    "override://0.0.0.0/com.foo.BarService?category=configurators&dynamic=false&application=foo&mock=force:return+null"
    
    • mock=force:return+null 表示消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
    • mock=fail:return+null 表示消费方对该服务的方法调用在失败后,再返回 null 值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。

    1.2 本地伪装

    本地伪装配置方式,更多参考官网 Dubbo 实战 - 本地伪装

    <dubbo:reference interface="com.foo.BarService" mock="true"/>
    <dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock"/>
    <dubbo:reference interface="com.foo.BarService" mock="return null"/>
    <dubbo:reference interface="com.foo.BarService" mock="throw com.foo.MockException" />
    

    以上几种方式,和 mock=fail:return+null 一样,表示消费方对该服务的方法调用在失败后,执行 mock 配置的代码。

    2. 源码分析

    2.1 原理分析

    在上一篇讲解 Dubbo Cluster 时可以看到, Dubbo 默认会将 Cluster#join 生成的 ClusterInvoker 对象包装 MockClusterInvoker。

    图1 Dubbo Mock原理图
    graph LR MockClusterWrapper -- join --> MockClusterInvoker MockClusterInvoker -- selectMockInvoker --> MockInvokersSelector MockInvokersSelector -- route --> MockProtocol MockProtocol -- refer --> MockInvoker

    总结: Dubbo Mock 主要流程如下:

    1. MockClusterWrapper:由于这个类是 Cluster 的包装类,所以 Dubbo 默认装配 MockClusterWrapper,对 ClusterInvoker 进行包装。
    2. MockClusterInvoker:核心类,对 ClusterInvoker 进行包装,主要功能:一是判断是否需要开启 Mock 机制;二是根据 MockInvokersSelector 过滤出对应的 Mock Invoker;三是执行 MockInvoker。
    3. MockInvokersSelector:Mock 路由策略,由于是 @Activate 修辞,因此会自动装配。当不开启 Mock 时返回正常的 Invoker,当开启了 Mock 后返回 Mock Invoker。
    4. MockProtocol:创建 MockInvoker。这个 MockProtocol 只能引用,不能暴露。
    5. MockInvoker:核心类,真正执行服务降级,处理 mock="return null"、mock="throw com.foo.MockException"mock="com.foo.BarServiceMock"

    2.2 MockClusterInvoker

    MockClusterInvoker 的主要功能是判断是否需要开启 Mock 机制,如果开启 Mock 则需要过滤出 MockInvoker 后执行服务降级。MockClusterWrapper 和 MockClusterInvoker 位于 dubbo-cluster 工程下。

    2.2.1 MockClusterWrapper

    MockClusterWrapper 是包装类,按 Dubbo SPI 机制,会将默认的 Cluster 进行包装。

    public class MockClusterWrapper implements 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));
        }
    }
    

    总结: Dubbo 默认的 Cluster 是 FailoverCluster,也就是说 MockClusterWrapper 会对 FailoverCluster 进行包装。接下来看一下 Mock 的核心 MockClusterInvoker 执行过程。

    MockClusterInvoker 是 Dubbo Mock 的核心类,主要功能有三个:

    1. 判断是否需要开启 Mock 机制,由 invoke 方法完成。
    2. 根据 MockInvokersSelector 过滤出对应的 Mock Invoker,由 selectMockInvoker 完成,实际是委托给 MockInvokersSelector 完成路由。
    3. 执行 MockInvoker,由 doMockInvoke方法完成,实际是委托给 MockInvoker。

    2.2.2 invoke 执行入口

    invoke 判断是否需要开启 Mock 机制,如果需要开启,则调用 doMockInvoke 进行服务降级。

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        Result result = null;
        String value = directory.getUrl().getMethodParameter(invocation.getMethodName(),
    		MOCK_KEY, Boolean.FALSE.toString()).trim();
        if (value.length() == 0 || value.equalsIgnoreCase("false")) {
            //no mock
            result = this.invoker.invoke(invocation);
        } else if (value.startsWith("force")) {
            //force:direct mock
            result = doMockInvoke(invocation, null);
        } else {
            //fail-mock
            try {
                result = this.invoker.invoke(invocation);
            } catch (RpcException e) {
                if (e.isBiz()) {
                    throw e;
                }
                result = doMockInvoke(invocation, e);
            }
        }
        return result;
    }
    

    总结: **invoke 关注一个问题,是否需要开启 Mock,如果开启 Mock 调用 doMockInvoke 执行。**代码注释已经很清楚了,分别对 no mock:(正常流程)force:(强制mock)fail:(失败mock,默认) 分别处理。如果 mock=false 则正常处理,如果配置 mock="return null"mock="fail:return+null" 处理流程是一样的。

    2.2.3 doMockInvoke

    doMockInvoke 执行服务降级。

    private Result doMockInvoke(Invocation invocation, RpcException e) {
        Result result = null;
        Invoker<T> minvoker;
    
        // 1. 过滤可以用 mockInvokers
        List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
        // 2. 如果没有,创建 MockInvoker
        if (CollectionUtils.isEmpty(mockInvokers)) {
            minvoker = (Invoker<T>) new MockInvoker(directory.getUrl(), directory.getInterface());
        } else {
            minvoker = mockInvokers.get(0);
        }
        // 3. 执行服务降级 mockInvoker
        try {
            result = minvoker.invoke(invocation);
        } catch (RpcException me) {
            if (me.isBiz()) {
                result = AsyncRpcResult.newDefaultAsyncResult(me.getCause(), invocation);
            } else {
                throw new RpcException(me.getCode(), getMockExceptionMessage(e, me), me.getCause());
            }
        } catch (Throwable me) {
            throw new RpcException(getMockExceptionMessage(e, me), me.getCause());
        }
        return result;
    }
    

    总结: doMockInvoke 最终调用 minvoker.invoke(invocation) 进行服务降级,其中需要关注的是 selectMockInvoker(invocation) 过滤缓存中的 MockInvoker,如果没有就需要创建新的 MockInvoker。

    2.2.4 selectMockInvoker

    selectMockInvoker 方法很奇怪,没有看到真正的 MockInvoker 过滤到底是怎么完成的。实际上 Dubbo 的默认路由策略就包含了 MockInvokersSelector,由这个类完成规则路由。

    private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
       List<Invoker<T>> invokers = null;
       if (invocation instanceof RpcInvocation) {
           // 1. 设置invocation.need.mock=true
           ((RpcInvocation) invocation).setAttachment(INVOCATION_NEED_MOCK, Boolean.TRUE.toString());
           // 2. 调用 MockInvokersSelector 路由规则过滤服务列表
           invokers = directory.list(invocation);
           ...
       }
       return invokers;
    }
    

    总结: selectMockInvoker 方法偷偷在将 invocation 的 invocation.need.mock 属性设置为 false,这个参数在 MockInvokersSelector 中就很有用了。然后通过 directory.list(invocation) 方法重新获取服务列表,在 Dubbo 系列(07-1)集群容错 - 服务字典 分析 RegisterDirectory 源码时,我们知道 list 方法会调用 routeChain.route 路由规则过滤服务。 下面看一下 MockInvokersSelector 代码。

    2.3 MockInvokersSelector

    MockInvokersSelector 在未开启 Mock 时返回正常的 Invokers,开启后返回 MockInvoker。

    @Override
    public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
    		URL url, final Invocation invocation) throws RpcException {
        if (CollectionUtils.isEmpty(invokers)) {
            return invokers;
        }
    
        if (invocation.getAttachments() == null) {
            // 1. 返回 -> 非MockedInvoker
            return getNormalInvokers(invokers);
        } else {
            String value = invocation.getAttachments().get(INVOCATION_NEED_MOCK);
            if (value == null) {
                return getNormalInvokers(invokers);
            // 2. invocation.need.mock=true则返回 -> MockedInvoker(MockProtocol)
            } else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
                return getMockedInvokers(invokers);
            }
        }
        // 3. invocation.need.mock=false则返回 -> 非MockedInvoker + MockedInvoker
        // ???
        return invokers;
    }
    

    **总结:**directory.list 调用 MockInvokersSelector.route 时,有三种情况:

    1. attachments 为 null 或 invocation.need.mock 为 null,则返回 非MockedInvoker
    2. invocation.need.mock=true 则返回 MockedInvoker
    3. invocation.need.mock=false 则返回 非MockedInvoker + MockedInvoker ???

    2.4 MockInvoker

    MockProtocol 和 MockInvoker 位于 dubbo-rpc-api 工程下。

    在 MockClusterInvoker#doMockInvoke 方法中,如果 directory.list 过滤出的 MockedInvoker 为空,则会直接创建一个 MockedInvoker,代码如下:

     minvoker = (Invoker<T>) new MockInvoker(directory.getUrl(), directory.getInterface());
    

    其实 Mock 也是一种协议,可以在注册中心 /dubbo/com.foo.BarService/providers 目录下写入:

    "mock://192.168.139.101/com.foo.BarService"
    

    这样消费者订阅 com.foo.BarService 服务后会根据 MockProtocol 协议创建对应的 MockedInvoker。

    2.4.1 MockProtocol

    MockProtocol 只能通过 reference 引入,不能通过 export 暴露服务。其实也就是直接创建了一个 MockInvoker。

    final public class MockProtocol extends AbstractProtocol {
        @Override
        public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public <T> Invoker<T> protocolBindingRefer(Class<T> type, URL url) throws RpcException {
            return new MockInvoker<>(url, type);
        }
    }
    

    总结: MockProtocol 非常简单,就不多说了。下面看一下 MockInvoker 代码。

    2.4.2 MockInvoker

    MockInvoker 执行服务降级。在 MockClusterInvoker 判断是否需要开启 Mock 后,MockInvokersSelector 过滤出可用的 MockInvoker,最后执行服务降级。

    1. 根据服务降级配置,执行对应的服务降级,如 returnthrowxxxServiceMock
    2. 直接 return 需要解析返回参数:parseMockValue
    3. 执行 xxxServiceMock 需要查找对应的实现类:getInvoker。

    先看一下整体的执行流程 invoke 方法。

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
        // 1. 获取mock值,URL 中 methodname.mock 或 mock 参数
        String mock = getUrl().getParameter(invocation.getMethodName() + "." + MOCK_KEY);
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(this);
        }
        if (StringUtils.isBlank(mock)) {
            mock = getUrl().getParameter(MOCK_KEY);
        }
    
        if (StringUtils.isBlank(mock)) {
            throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
        }
        
        // 2. 对mock字符串进行处理,比如去除 `force:`、`fail:` 前缀
        mock = normalizeMock(URL.decode(mock));
        // 3. return
        if (mock.startsWith(RETURN_PREFIX)) {
            mock = mock.substring(RETURN_PREFIX.length()).trim();
            try {
                Type[] returnTypes = RpcUtils.getReturnTypes(invocation);
                Object value = parseMockValue(mock, returnTypes);
                return AsyncRpcResult.newDefaultAsyncResult(value, invocation);
            } catch (Exception ew) {
                throw new RpcException(ew);
            }
        // 3. throw
        } else if (mock.startsWith(THROW_PREFIX)) {
            mock = mock.substring(THROW_PREFIX.length()).trim();
            if (StringUtils.isBlank(mock)) {
                throw new RpcException("mocked exception for service degradation.");
            } else { // user customized class
                Throwable t = getThrowable(mock);
                throw new RpcException(RpcException.BIZ_EXCEPTION, t);
            }
        // 5. xxxServiceMock
        } else { //impl mock
            try {
                Invoker<T> invoker = getInvoker(mock);
                return invoker.invoke(invocation);
            } catch (Throwable t) {
                throw new RpcException("Failed to create mock implementation class " + mock, t);
            }
        }
    }
    

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

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

    getInvoker 方法查找指定对流的 Invoker。

    private Invoker<T> getInvoker(String mockService) {
        // 1. 缓存命中
        Invoker<T> invoker = (Invoker<T>) MOCK_MAP.get(mockService);
        if (invoker != null) {
            return invoker;
        }
    	
        // 2. 根据serviceType查找mock的实现类,默认为 xxxServiceMock
        Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
        T mockObject = (T) getMockObject(mockService, serviceType);
        // 3. 包装成Invoker
        invoker = PROXY_FACTORY.getInvoker(mockObject, serviceType, url);
        if (MOCK_MAP.size() < 10000) {
            MOCK_MAP.put(mockService, invoker);
        }
        return invoker;
    }
    
    public static Object getMockObject(String mockService, Class serviceType) {
        // mock=true或default时,查找 xxxServiceMock
        if (ConfigUtils.isDefault(mockService)) {
            mockService = serviceType.getName() + "Mock";
        }
    	// mock=testxxxService,指定Mock实现类
        Class<?> mockClass = ReflectUtils.forName(mockService);
        ...
        return mockClass.newInstance();
    }
    

    总结: Dubbo 如果不指定 Mock 实现类,默认查找 xxxServiceMock。如果存在该实现类,则将其包装成 Invoker 后返回。


    每天用心记录一点点。内容也许不重要,但习惯很重要!

  • 相关阅读:
    SQL Server2008中删除重复记录
    Php环境在Windows (server 2003) 服务器部署标准 白丁简明版
    国外服务器鸟文windows,时间12小时制,如何改成24小时呢?我来告诉你
    将Capicom调用代码封装到ActiveX——解决javascript调Capicom读取数字证书信息时,IE弹出安全提示的问题
    Linq处理List数据
    C#将窗口最小化到系统托盘,并显示图标和快捷菜单
    C# 将程序添加到启动项 (写入注册表),及从启动项中删除
    C#中string[]数组和list<string>泛型的相互转换
    IIS7.5部署ASP.NET失败
    IIS 7.5版本中一些诡异问题的解决方案
  • 原文地址:https://www.cnblogs.com/binarylei/p/11703192.html
Copyright © 2011-2022 走看看