本系列主要参考官网文档、芋道源码的源码解读和《深入理解Apache Dubbo与实战》一书。Dubbo版本为2.6.1。
文章内容顺序:
什么是Mock
Mock的UML图
Mock的代码逻辑
- 3.1MockClusterWrapper
- 3.2MockClusterInvoker
- 3.3MockClusterInvoker#doMockInvoke
- 3.4MockClusterInvoker#selectMockInvoker
- 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的方法,会怎么样。
- MockInvoker
1. 什么是Mock
本地伪装:通常用于服务降级,比如某验权服务,当服务提供方全部挂掉后,客户端不抛出异常,而是通过 Mock 数据返回授权失败。
服务降级:可以通过服务降级功能,临时屏蔽某个出错的非关键服务,并定义降级后的返回策略。
推荐先阅读官网的这两个概念。
《Dubbo 用户指南 —— 本地伪装》
《Dubbo 用户指南 —— 服务降级》
2. Mock的UML图
image.png以上类图分成两个部分:不要在意马赛克。
MockClusterWrappe
r +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章节介绍的那些)。因为MockClusterWrapper
是Dubbo SPI Wrapper
类,所以对应的Cluster
对象,都会被它所包装。
这边直接返回了一个MockClusterInvoker
,那就进去看看吧。
3.2MockClusterInvoker
MockClusterInvoker
是 Dubbo 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
下面的图是
image.pngAbstractDirector#setRouters()
调用的时机,可以看到当创建一个Directory
的时候,就会进入到setRouters()
方法中,这个方法每次都会添加一个MockInvokersSelector
,而当我们调用AbstractDirector#list想获取Invokers
时,就会轮询这个routers
集合,必定会调用到MockInvokersSelector#route
方法。知道了这点之后,就需要看看MockInvokersSelector#route
方法的实现了。
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
终于用到了,
这个方法attachments
为null
或invocation.need.mock
为null
,则返回 普通 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
。
上面的判别方式也很简单,判断这些invokers
的protocol
是否为mock
。
4.5 那如果没有protocol
为mock
,又要进行本地伪装怎么办呢?
也很简单,在
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
,继承了AbstractProtocol
的MockProtocol
,在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
:直接返回,可以是empty
、null
、true
、false
、json
格式,由方式 parseMockValue 进行解析。throw
:直接抛出异常。如果没有指定异常,抛出RpcException
,否则抛出指定的 Exception。xxxServiceMock
:执行xxxServiceMock 方法
。如果 mock=true 或mock=defalut
则查找xxxServiceMock
方法后执行,如果mock=com.dubbo.testxxxService
则执行指定的方法。
网友评论