Luckylau's Blog

微服务架构之Dubbo服务降级(1)

dubbo提供了服务降级的策略,我们首先看一下mock机制的降级原理,下面我们从源码的角度分析其调用过程。

如何使用mock服务降级

mock支持的配置大体分为2类,一类用于屏蔽,一类用于容错。可以通过dubbo-admin控制台下发配置。

这里我们只关注配置文件的使用。

屏蔽指的是消费方对该服务的方法调用都直接返回 null 值,不发起远程调用。mock=

force:return+null ,这个不能在配置文件使用,通过控制台直接往注册中心写入该mock规则。

容错指的是消费方对该服务的方法调用在失败后,根据配置做不同的返回。

我这里就强调2种,一种是boolean值,默认的为false。如果配置为true,则缺省使用mock类名,即类名+Mock后缀,但该类要和暴露的服务路径相同,没有这个类,项目启动时会报Class Not Found 错误。

1
<dubbo:reference id = "test" interface="com.luckylau.api.service.PushRpcService" timeout="3000" version="1.0.0" mock="true"/>

如上所示,我们配置了mock = true , 这时我们需要定义一个PushRpcServiceMock的类,它实现了PushRpcService接口,同时有一个默认的无参数构造函数(也可以不写,java会默认),注意这个类必须放在com.luckylau.api.service路径下,所以在本地代码要有这样的路径存在,专门为降级使用。

另外mock = “default”效果和mock=”true”一样。

这时候如果远程调用失败,就会访问PushRpcServiceMock类的返回。

另外一种是return null。

1
<dubbo:reference id = "test" interface="com.luckylau.api.service.PushRpcService" timeout="3000" version="1.0.0" mock="return null"/>

这时候如果远程调用失败,就会直接返回null,不抛出异常。

源码解析

首先是服务的引用过程,解决如何知道这个服务是mock的方式,ReferenceConfig的init()在执行ref = createProxy(map)之前有一步骤是checkStubAndMock,做了相关处理,包括如果配置mock = true但没有找到类名+Mock后缀,报Class Not Found 错误等。

然后是实际调用过程,我们跟踪一下源码(2.6版本),首先进入的是

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
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
//checkStubAndMock的内部逻辑来决定value的值
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.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")) {
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;
}

当我们配置mock = “default” ,mock = “true” ,mock = “return null” 时

1
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();

value的值为default, true, return+null。它们会进入fail-mock的逻辑,如果调用失败之后,首先判断异常的类型,包括业务错误类型:接口实现类中的方法抛出的错误 ,和非业务错误类型:网络错误、超时错误、禁止访问错误、序列化错误及其他未知的错误。业务错误类型直接抛出,非业务错误类型进入关键的处理:

1
result = doMockInvoke(invocation, e);
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
private Result doMockInvoke(Invocation invocation,RpcException e){
Result result = null;
Invoker<T> minvoker ;
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 {
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;
}
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
/**
* 返回MockInvoker
* 契约:
* directory根据invocation中是否有Constants.INVOCATION_NEED_MOCK,来判断获取的是一个normal invoker 还是一个 mock invoker
* 如果directorylist 返回多个mock invoker,只使用第一个invoker.
* @param invocation
* @return
*/
private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
List<Invoker<T>> invokers = null;
//TODO generic invoker?
if (invocation instanceof RpcInvocation) {
//存在隐含契约(虽然在接口声明中增加描述,但扩展性会存在问题.同时放在attachement中的做法需要改进
((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;
}

我们单独分析配置mock = “true” 和 mock = “return null”的情况。此时用源码的demo。

首先看mock = “true” 的情况

1
<dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService" mock="true"/>

1
2
3
4
5
6
7
8
9
10
11
12
package com.alibaba.dubbo.demo;
/**
* @author luckylau
* @date 2018/1/24/024 13:41
*/
public class DemoServiceMock implements DemoService {
@Override
public String sayHello(String name) {
System.out.println("我是Demo Service 的 服务降级 ");
return null;
}
}

在doMockInvoke中result = minvoker.invoke(invocation)非常重要,它的实现在com.alibaba.dubbo.rpc.support的MockInvoker类中,实现如下

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
50
51
52
53
54
55
56
57
58
59
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));//关键点 处理 mock="true" "return null"
if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())) {
RpcResult result = new RpcResult();
result.setValue(null);
return result;
} 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);
}
} 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);
throw new RpcException(RpcException.BIZ_EXCEPTION, t);
}
} else { //impl mock mock ="true" 会走这里
try {
Invoker<T> invoker = getInvoker(mock);
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implemention class " + mock, t);
}
}
}
private String normallizeMock(String mock) {
if (mock == null || mock.trim().length() == 0) {
return mock;
} else if (ConfigUtils.isDefault(mock) || "fail".equalsIgnoreCase(mock.trim()) || "force".equalsIgnoreCase(mock.trim())) {
mock = url.getServiceInterface() + "Mock"; //拼接
}
if (mock.startsWith(Constants.FAIL_PREFIX)) {
mock = mock.substring(Constants.FAIL_PREFIX.length()).trim();
} else if (mock.startsWith(Constants.FORCE_PREFIX)) {
mock = mock.substring(Constants.FORCE_PREFIX.length()).trim();
}
return mock;
}

回到mock = “true”配置时候,会走到这里,我们发现这里的invoker.invoke(invocation) ,调用的是AbstractProxyInvoker,最后会调用到我们定义的这个类DemoServiceMock。

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
private Invoker<T> getInvoker(String mockService) {
// Invoker由反射生成,需要缓存生成的Invoker(否则效率低)
Invoker<T> invoker = (Invoker<T>) mocks.get(mockService);
if (invoker != null) {
return invoker;
} else {
Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
if (ConfigUtils.isDefault(mockService)) {
mockService = serviceType.getName() + "Mock";
}
// 创建mock类并判断mock类是否是原类的子类
Class<?> mockClass = ReflectUtils.forName(mockService);
if (!serviceType.isAssignableFrom(mockClass)) {
throw new IllegalArgumentException("The mock implemention class " + mockClass.getName() + " not implement interface " + serviceType.getName());
}
try {
// 创建实例,并创建对应的代理
T mockObject = (T) mockClass.newInstance();
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);
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
package com.alibaba.dubbo.rpc.proxy;
public Result invoke(Invocation invocation) throws RpcException {
try {
return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));//里面的doInvoke来自JavassistProxyFactory
} catch (InvocationTargetException e) {
return new RpcResult(e.getTargetException());
} catch (Throwable e) {
throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
}
}
protected abstract Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable;

然后mock = “return null” 的情况

在doMockInvoke中result = minvoker.invoke(invocation)非常重要,而mock = “return null” ,它执行到如下String mock = “return+null”

然后执行到下面,返回null

Luckylau wechat
如果对您有价值,看官可以打赏的!