Dubbo 系列(07-5)集群容错 - Mock
Dubbo 系列(07-5)集群容错 - Mock
Spring Cloud Alibaba 系列目录 - Dubbo 篇
1. 背景介绍
相关文档推荐:
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原理图
MockClusterWrapper -- join --> MockClusterInvoker
MockClusterInvoker -- selectMockInvoker --> MockInvokersSelector
MockInvokersSelector -- route --> MockProtocol
MockProtocol -- refer --> MockInvoker
总结: Dubbo Mock 主要流程如下:
MockClusterWrapper:由于这个类是 Cluster 的包装类,所以 Dubbo 默认装配 MockClusterWrapper,对 ClusterInvoker 进行包装。MockClusterInvoker:核心类,对 ClusterInvoker 进行包装,主要功能:一是判断是否需要开启 Mock 机制;二是根据 MockInvokersSelector 过滤出对应的 Mock Invoker;三是执行 MockInvoker。MockInvokersSelector:Mock 路由策略,由于是 @Activate 修辞,因此会自动装配。当不开启 Mock 时返回正常的 Invoker,当开启了 Mock 后返回 Mock Invoker。MockProtocol:创建 MockInvoker。这个 MockProtocol 只能引用,不能暴露。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 的核心类,主要功能有三个:
- 判断是否需要开启 Mock 机制,由 invoke 方法完成。
 - 根据 MockInvokersSelector 过滤出对应的 Mock Invoker,由 selectMockInvoker 完成,实际是委托给 MockInvokersSelector 完成路由。
 - 执行 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 时,有三种情况:
- attachments 为 null 或 invocation.need.mock 为 null,则返回 
非MockedInvoker。 invocation.need.mock=true则返回MockedInvoker。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,最后执行服务降级。
- 根据服务降级配置,执行对应的服务降级,如 
return、throw、xxxServiceMock - 直接 return 需要解析返回参数:parseMockValue
 - 执行 
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 服务降级有三种处理情况:
return:直接返回,可以是 empty、null 、true 、false 、json 格式,由方式 parseMockValue 进行解析。throw:直接抛出异常。如果没有指定异常,抛出 RpcException,否则抛出指定的 Exception。xxxServiceMock:执行 xxxServiceMock 方法。如果mock=true或mock=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 后返回。
每天用心记录一点点。内容也许不重要,但习惯很重要!
Dubbo 系列(07-5)集群容错 - Mock的更多相关文章
- Dubbo 源码分析 - 集群容错之 LoadBalance
		
1.简介 LoadBalance 中文意思为负载均衡,它的职责是将网络请求,或者其他形式的负载"均摊"到不同的机器上.避免集群中部分服务器压力过大,而另一些服务器比较空闲的情况.通 ...
 - Dubbo 源码分析 - 集群容错之 Cluster
		
1.简介 为了避免单点故障,现在的应用至少会部署在两台服务器上.对于一些负载比较高的服务,会部署更多台服务器.这样,同一环境下的服务提供者数量会大于1.对于服务消费者来说,同一环境下出现了多个服务提供 ...
 - Dubbo 源码分析 - 集群容错之 Router
		
1. 简介 上一篇文章分析了集群容错的第一部分 -- 服务目录 Directory.服务目录在刷新 Invoker 列表的过程中,会通过 Router 进行服务路由.上一篇文章关于服务路由相关逻辑没有 ...
 - dubbo服务引用与集群容错
		
服务引用无非就是做了两件事 将spring的schemas标签信息转换bean,然后通过这个bean的信息,连接.订阅zookeeper节点信息创建一个invoker 将invoker的信息创建一个动 ...
 - Dubbo工作原理,集群容错,负载均衡
		
Remoting:网络通信框架,实现了sync-over-async和request-response消息机制. RPC:一个远程过程调用的抽象,支持负载均衡.容灾和集群功能. Registry:服务 ...
 - Dubbo 源码分析 - 集群容错之 Directory
		
1. 简介 前面文章分析了服务的导出与引用过程,从本篇文章开始,我将开始分析 Dubbo 集群容错方面的源码.这部分源码包含四个部分,分别是服务目录 Directory.服务路由 Router.集群 ...
 - Dubbo负载均衡与集群容错机制
		
1 Dubbo简介 Dubbo是一款高性能.轻量级的开源Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现. 作为一个轻量级RPC框架,D ...
 - dubbo源码分析- 集群容错之Cluster(一)
		
1.集群容错的配置项 failover - 失败自动切换,当出现失败,重试其他服务器(缺省),通常用于读操作,但重试会带来更长的延时. failfast - 快速失效,只发起一次调用,失败立即报错.通 ...
 - Dubbo的10种集群容错模式
		
学习Dubbo源码的过程中,首先看到的是dubbo的集群容错模式,以下简单介绍10种集群容错模式 1.AvailableCluster 顾名思义,就是可用性优先,遍历所有的invokers,选择可用的 ...
 
随机推荐
- 2019牛客暑期多校训练营(第一场) - H - XOR - 线性基
			
https://ac.nowcoder.com/acm/contest/881/H 题意: 给定n个整数,求其中异或和为 \(0\) 的子集的大小的和. 题解思路: 首先转化为每个可以通过异或表示 \ ...
 - 实例之跑马灯,函数创建、通过ID获取标签及内部的值,字符串的获取与拼接、定时器的使用
			
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
 - C# 下载PDF文件(http与ftp)
			
1.下载http模式的pdf文件(以ASP.NET为例,将PDF存在项目的目录下,可以通过http直接打开项目下的pdf文件) #region 调用本地文件使用返回pdfbyte数组 /// < ...
 - C# 引用类型的深度拷贝帮助类
			
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Lin ...
 - YARN的job提交流程
			
1.客户端向ResourceManagement 提交 运行的请求 (hadoop jar xxxx.jar) 2.ResourceManager进行检查,没有问题的时候,向客户端返回一个共享资源的路 ...
 - myeclipse2014删除antlr-2.7.2.jar--解决struts和hibernate包冲突
			
方式一: 要求眼疾手快,在workspace下的D:\myeclipse2014workspace\.metadata\.me_tcat7\webapps\工程名\WEB-INF\lib中将antlr ...
 - 第十五章 Kubernetes调度器
			
一.简介 Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上.听起来非常简单,但有很多要考虑的问题: ① 公平:如何保证每个节点都能被分配资源 ② ...
 - 简单说下cookie,LocalStorage与SessionStorage.md
			
最近在网上看到有人讨论这三个的一些概念与区别,发现自己也一直没有较好的总结,所以查阅了一些资料来阐述一下. 基本概念 cookie cookie英文意思是小甜饼,是原来的网景公司创造,目前是在客户端存 ...
 - Sass--混合宏--声明宏
			
如果你的整个网站中有几处小样式类似,比如颜色,字体等,在 Sass 可以使用变量来统一处理,那么这种选择还是不错的.但当你的样式变得越来越复杂,需要重复使用大段的样式时,使用变量就无法达到我们目了.这 ...
 - pandas模块之读取文件
			
首先我们来看一个文件 1 男 北京 刘一 我笑 #跳过此行,序号1 2 女 上海 刘珊 你笑 3 男 杭州 刘五 他笑 #跳过此行,序号四 4 女 重庆 刘六 不笑了 下面来分析内容,并使用参数 1 ...