关于dubbo的服务降级(或本地伪装),分为两种:

  1. 容错降级:当调用远程接口出现RpcException异常时,降级
  2. 屏蔽降级:直接屏蔽某个远程接口的访问,降级

1 实现方式

实现服务降级的方式分多种:

  1. XML配置
  2. dubbo-admin配置(本质是配置zookeeper configurators节点信息)
  3. 硬编码方式(本质是配置zookeeper configurators节点信息)

1.1 XML配置

1
2
3
4
5
6
7
8
// 1.容错降级
<dubbo:reference interface="com.foo.BarService" mock="return null" />
// 2.不降级,不使用mock
<dubbo:reference interface="com.foo.BarService" mock="false" />
// 3.自定义Mock类,与interface同目录,且名称为interface名称 + "Mock"
<dubbo:reference interface="com.foo.BarService" mock="true" />
// 4.自定义Mock类,mock属性指定实现类路径
<dubbo:reference interface="com.foo.BarService" mock="..." />

——————————————-自定义Mock类实验———————————————-

1
2
3
4
<!-- dubbo-demo-consumer.xml -->
<dubbo:reference id="demoService" check="false"
interface="com.alibaba.dubbo.demo.DemoService"
mock="com.alibaba.dubbo.demo.DemoServiceMock"/>
1
2
3
4
5
6
7
8
9
10
package com.alibaba.dubbo.demo;
public class DemoServiceMock implements DemoService {
@Override
public String sayHello(String name) {
return "mock mock mock mock mock....";
}
}

然后启动zookeeper,在dubbo-demo-provider项目的sayHello方法上增加断点,debug启动provider。最后启动consumer,会因为超时,调用mock类的sayHello方法,如下:
在这里插入图片描述—————————————————–实验结束——————————————————–

xml方式貌似不能配置屏蔽降级

1.2 dubbo-admin配置和硬编码方式

在这里插入图片描述
dubbo-admin即可以配置容错降级也可以配置服务降级(dubbo 2.5 此处有BUG),在此处配置的参数都会写到zookeeper的configurators节点,然后通知消费者notify,上两篇文章做了详细的分析。

硬编码方式如下:

1
2
3
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://10.20.153.10:2181"));
registry.register(URL.valueOf("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 值,不抛异常。

dubbo-admin和硬编码方式的实质是一样的,都是往zookeeper的configurators节点写入数据。

——————————————-硬编码方式降级实验———————————————-

屏蔽降级实验:执行下面的代码:

1
2
3
RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension();
Registry registry = registryFactory.getRegistry(URL.valueOf("zookeeper://127.0.0.1:2181"));
registry.register(URL.valueOf("override://0.0.0.0/com.alibaba.dubbo.demo.DemoService?category=configurators&dynamic=false&application=demo-consumer&mock=force:return+null"));

向zookeeper的configurators节点写入mock数据,通过ZooInspector查看如下:
在这里插入图片描述然后通过dubbo-demo-consumer DemoConsumer.main再调用DemoService接口,直接返回null,如下:
在这里插入图片描述

—————————————————–实验结束——————————————————–

2 源码分析

上一篇文章在分析远程服务调用的时候,有调用MockClusterInvoker的invoke方法这一步,从MockClusterInvoker名字就可以看出来,是对mock的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// MockClusterInvoker.invok
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
// 获取mock参数的值
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
// 如果没设置mock参数,或者mock="false"
if (value.length() == 0 || value.equalsIgnoreCase("false")) {
//no mock
result = this.invoker.invoke(invocation);
// 如果mock参数以force开头:屏蔽降级
} else if (value.startsWith("force")) {
if (logger.isWarnEnabled()) {
logger.info("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + directory.getUrl());
}
//force:direct mock
result = doMockInvoke(invocation, null);
// 其他情况:容错降级
} else {
//fail-mock
try {
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);
}
result = doMockInvoke(invocation, e);
}
}
}
return result;
}

MockClusterInvoker类invoke方法对以下三种情况进行处理:

  1. 参数mock没值,或mock=”false”,不进行mock处理,正常调用远程服务
  2. 如果以force开头,屏蔽降级
  3. 否则,容错降级

屏蔽降级直接调用doMockInvoke方法,不会去调用远程服务。容错降级会先去调用远程服务,如果抛出RpcException,则调用doMockInvoke方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MockClusterInvoker.doMockInvoke
private Result doMockInvoke(Invocation invocation, RpcException e) {
Result result = null;
Invoker<T> minvoker;
// 获取mock Invoker
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
if (mockInvokers == null || mockInvokers.size() == 0) {
minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
} else {
minvoker = mockInvokers.get(0);
}
try {
// 调用mock Invoker
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());
}
// 返回result
return result;
}

doMockInvoke方法首先获取一个mockInvokers,不管是容错降级还是屏蔽降级,mockInvokers都是null,会直接创建一个MockInvoker,然后调用MockInvoker.invoke方法。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// MockInvoker.invoke
public Result invoke(Invocation invocation) throws RpcException {
String mock = getUrl().getParameter(invocation.getMethodName() + "." + Constants.MOCK_KEY);
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(this);
}
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 = normallizeMock(URL.decode(mock));
// return
if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())) {
RpcResult result = new RpcResult();
result.setValue(null);
return result;
// return value(包括return null)
} 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);
return new RpcResult(value);
} catch (Exception ew) {
throw new RpcException("mock return invoke error. method :" + invocation.getMethodName() + ", mock:" + mock + ", url: " + url, ew);
}
// throw xxx
} 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 { //用户自定义类
Throwable t = getThrowable(mock);
throw new RpcException(RpcException.BIZ_EXCEPTION, t);
}
} else { //impl mock,自定义mock类
try {
Invoker<T> invoker = getInvoker(mock);
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implemention class " + mock, t);
}
}
}

MockInvoker.invoke方法的作用是把mock属性的值包装成RpcResult,分成以下几种情况:

  1. mock=”return”,RpcResult的value设置为null
  2. mock=”return value(包括return null)”
  3. mock=”throw…”
  4. mock=”自定义mock实现类”

下面着重看下自定义mock实现类的情况,MockInvoker.getInvoker内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// MockInvoker.getInvoker
private Invoker<T> getInvoker(String mockService) {
// 从缓存中取
Invoker<T> invoker = (Invoker<T>) mocks.get(mockService);
if (invoker != null) {
return invoker;
} else {// 缓存中没有
// 通过反射机制获取接口Class
Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
if (ConfigUtils.isDefault(mockService)) {
// mock实现类 = 接口名称 + "Mock"
mockService = serviceType.getName() + "Mock";
}
// 获取Mock实现类的Class
Class<?> mockClass = ReflectUtils.forName(mockService);
// 判断mock类是否实现了接口,如果没有抛出IllegalArgumentException异常
if (!serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
// 为什么写两遍呢?
if (!serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
try {
// 实例化mock类
T mockObject = (T) mockClass.newInstance();
// 将mock实例转化为Invoker实例
invoker = proxyFactory.getInvoker(mockObject, (Class<T>) serviceType, url);
if (mocks.size() < 10000) {
mocks.put(mockService, invoker);
}
return invoker;
} catch (InstantiationException e) {
throw new IllegalStateException("No such empty constructor \"public " + mockClass.getSimpleName() + "()\" in mock implemention class " + mockClass.getName(), e);
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}

MockInvoker.getInvoker方法的处理过程和我想象的差不多,获取mock实现类路径,通过反射机制实例化mock类。然后将mock实例转化为Invoker实例,返回转化后的Invoker实例。