Mock
在Cluster中,还有最后一个MockClusterWrapper,由它实现了Dubbo的本地伪装。这个功能的使用场景较多,通常会应用在以下场景中:服务降级;部分非关键服务全部不可用,希望主流程继续进行;在下游某些节点调用异常时,可以以Mock的结果返回。
Mock常见的使用方法
Mock只有在拦截到RpcException的时候会启用,数异常容错方式的一种。业务层面其实也可以用try-catch来实现这种功能,如果使用下沉到框架中的Mock机制,则可以让业务的实现更优雅
常见配置如下:
//配置方式1:可以在配置文件中配置
<dubbo:reference interface="com.foo.BarService" mock="true" />
//配置方式2
<dubbo:reference interface="com.foo.BarService" mock="com.foo.BarServiceMock" />
//配置方式3
<dubbo:reference interface="com.foo.BarService" mock="return null" />
//提供Mock实现,如果Mock配置了true或default,则实现的类名必须是接口名+Mock,如配置方式1
//否则会直接取Mock参数值作为Mock实现类,如配置方式2
package com.foo;
public class BarServieMock implements BarService {
public String sayHello(String name) {
//可以伪造容错数据,此方法只在出现RpcException时被执行
return "容错数据";
}
}
当接口配置了Mock,在RPC调用抛出RpcException时救护执行Mock方法。最后一种return null的配置方式通常会在想直接忽略异常的时候使用。
服务的降级是在dubbo-admin中通过override协议更新Invoker的Mock参数实现的。如果Mock参数设置为mock=force:return+null
,则表明是强制Mock,强制Mock会让消费者对该服务的调用直接返回null,不再发起远程调用。通常使用在非重要服务已经不可用的时候,可以屏蔽下游对上游系统造成的影响。此外,还能把参数设置为mock=fail:return+null
,这样为消费者还会发起远程调用,不过失败后返回null,但是不抛出异常。
最后,如果配置的参数是以throe开头的,即mock=throw
,则直接抛出RpcException,不会发起远程调用。
Mock的总体结构
Mock设计的接口比较多,整个流程贯穿Cluster和Protocol层,接口之间的逻辑关系如图:
主要流程分为Cluster层和Protocol 层。
- MockClusterWrapper是一个包装类,包装类会被自动注入合适的打展点实现, 它的这辑很简单,只是把被包装扩展类作为初始化参数来创建并返回一个MockClusterInvoker。
- MockClusterlnvoker和其他的Clusterlnvoker一样, 在Invoker 方法中完成了主要逻辑。
- MockInvokersSelector是Router接口的一种实现,用于过滤出Mock的Invokero。
- MockProtocol根据用户传入的URL和类型生成一个MockInvoker。
- MockInvoker实现最终的Invoker逻辑。
MockInvoker与MockClusterInvoker看起来都是Invoker,它们之间有什么区别呢?
首先,强制Mock、失败后返回Mock结果等逻辑是在MockClusterInvoker里处理的;其次,MockClusterInvoker在某些逻辑下,会生成MockInvoker并进行调用;然后,再MockInvoker里会处理mock="return null"、mock="throw xxx"或mock=com.xxService
这些配置逻辑。最后,MockInvoker还会被MockProtocol在引用远程服务的时候创建。
可以任意,MockClusterInvoker会处理一些Class级别的Mock逻辑,例如:选择调用哪些Mock类。MockInvoker处理的是方法级别的Mock逻辑,如返回值。
Mock的实现原理
1. MockClusterInvoker的实现原理:
MockClusterWapper 是一个包装类, 它在创建MockClusterInvoker的时候会把被包装的Invoker传入构造方法,因此Mckiuserivoker 内部天生就含有一个Invoker 的引用。MockClusterlnvoker的invoke方法处理了主要逻辑,步骤如下:
-
获取Invoker 的Mock参数。前面已经说过,该Invoker 是在构造方法中传入的。如果该Invoker根本就没有配置Mock,则直接调用Invoker的invoke方法并把结果返回:如果配置了Mock 参数,则进入下一步。
-
判断参数是否以force 开头,即判断是否强制Mock。 如果是强制Mock, 则进入doMockInvoke逻辑。 如果不以force 开头,则进入失败后Mock的逻辑。
-
失败后调用doMockInvoke 逻辑返回结果。在try代码块中直接调用Invoker的invoke方法,如果抛出了异常,则在catch代码块中调用doMockInvoke逻辑。
强制Mock和失败后Mock都会调用doMockInvoke逻辑,其步骤如下:
-
通过selectMockInvoker获得所有Mock类型的Invoker。selectMockInvoker 在对象的attachment属性中偷偷放进一个
invocation.need.mock-true
的标识。directory 在list方法中列出所有Invoker的时候,如果检测到这个标识,则使用MockInvokersSelector来过滤Invoker,而不是使用普通route实现,最后返回Mock类型的Invoker 列表。如果一个Mock类型的Invoker都没有返回,则通过directory的URL新创建一个MockInvoker;如果有Mock类型的Invoker,则使用第一个。
-
调用MockInvoker的invoke方法。在try-catch中调用invoke方法并返回结果。如果出现了异常, 并且是业务异常,则包装成一个RpcResult 返回,否则返回RpcException异常。
2. MockInvokersSelector的实现原理:
在doMockInvoke中的第一步中,directory会使用MockInvokersSelector来过滤出Mock类型的Invoker。MockInvokersSelector是Router接口的其中一种实现。它的路由时的具体逻辑如下:
- 判断是否需要做Mock过滤。如果attachment为空,或者没有
invocation.need.mock=true
的标识,则认为不需要做Mock过滤,进入步骤2;如果找到这个标识,则进入步骤3; - 获取非Mock类型的Inoker。遍历所有的Invoker,如果它们的protocol中都没有Mock参数,则整个列表直接返回。否则,把protocol中所有没有Mock标识的取出来返回。
- 获取Mock类型的Invoker。遍历所有的Invoker如果它们的protocol中都没有Mock参数,则直接返回null。否则,把protocol中所有含有Mock标识的取出来并返回。
3. MockProtocol与MockInvoker的实现原理:
MockProtocol也是协议的一种,主要是把注册中心的Mock URL转换为MockInvoker对象。URL可以通过dubbo-admin或其他方式写入注册中心,它被定义为只能引用,不能暴露,如下所示:
例如:我们在注册中心/dubbo/com.test.xxxSvice/providers
这个服务提供者的目录下,写入一个Mock的URL:mock://192168.0.123/com.test.xxxService
。
在MockInvoker的invoke方法中,主要处理逻辑如下:
-
获取Mock参数值。通过URL获取Mock配置的参数,如果为空则抛出异常。优先会获取方法级的Mock参数,例如:以
methodName.mock
为key去获取参数值;如果获取不到,则尝试以mock为key获取对应的参数值。 -
处理参数值是return的配置。如果只配置了一个return,即mock=return,则返回一个空的RpcResult;如果return后面还跟了别的参数,则首先解析返回类型,然后结合Mock参数和返回类型,返回Mock值。现支持一下类型的参数:
- Mock参数值等于empty,根据返回类型返回new xxx()空对象;
- 如果参数值是null、true、false,则直接返回这些值;
- 如果是其他字符串,则返回字符串;
- 如果是数字、List、Map类型,则返回对应的JSON传;
- 如果都没有匹配上,则直接返回Mock的参数值。
-
处理参数值是throw的配置。如果throw后面没有字符串,则包装成一个RpcException异常,直接抛出;如果throw后面有自定义的异常类,则使用自定义的异常类,并包装成一个RpcException异常抛出。
-
处理Mock实现类。先从缓存中取,如果有则直接返回。如果缓存中没有,则先获取接口的类型,如果Mock的参数配置的是true或default,则尝试通过
"接口名+Mock"
查找Mock实现类,例如:TestService会查找Mock实现TestServiceMock
。如果是其他配置方法,则通过Mock的参数值进行查找,例如:配置了mock=com.xxx.testServie
,则会查找com.xxx.testService
。