文章目录
一、前言
本系列为个人Dubbo学习笔记衍生篇,是正文篇之外的衍生内容,内容来源于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章。仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。
1. 本地Mock
服务消费端本地服务mock主要用来做本地测试用,当服务提供端服务不可用时,使用本地mock服务可以模拟服务提供端来让服务消费方测试自己的功能,而不需要发起远程调用。
要实现Mock功能,首先需要消费端先实现服务端的 mock 实现类,需要注意的是Mock实现类必须要符合 接口包名.类名Mock格式
。需要注意的是,在执行Mock服务实现类 mock() 方法前,会先发起远程调用,当远程服务调用失败时,才会降级执行mock功能。
开启mock 功能需要设置 :
// 设置启动时候不检查服务是否可用
referenceConfig.setCheck(false);
// 设置启用Mock
referenceConfig.setMock(true);
2. 服务降级
基于本地Mock,Dubbo提供了一些服务降级措施,当服务提供端某一个非关键的服务出错时,可以手动对消费端的调用进行降级,这样服务消费端就避免了再去调用出错的服务,以避免加重服务提供端的负担。服务降级的本质也是通过mock配置。
Spring 中可以通过 @Reference注解的 mock属性设置
@Reference(version = "1.0.0", mock = "true")
private NewDemoService newDemoService;
这里的 mock 属性的取值可以为 :
-
true || default || fail || force : 这四种取值都会按照默认方式去执行, 即Dubbo 默认会先远程调用提供者,如果提供者调用失败,则按照调用接口的路径寻找mock实现类,Mock实现类 遵循
接口包名.类名Mock格式
的规则,如调用 com.kingfish.DemoService,则需要创建类 com.kingfish.DemoServiceMock 实现DemoService 接口。如果消费者调用 提供者 DemoService 实现类失败,则会调用 DemoServiceMock 方法并返回。 -
return xxx :当调用失败时,会返回return 指定的mock值,其中xxx 可以不填,默认返回null。其中return的合法字符串可以是:
empty: 代表空,基本类型的默认值,或者集合类的空值 null: null true: true false: false JSON 格式: 反序列化 JSON 所得到的对象
-
throw XxxException :当调用失败时,使用 throw 来返回一个 Exception 对象,作为 Mock 的返回值。其中 XxxException可以不填,默认为 RPCException
-
指定mock类路径 :即会按照默认路径寻找mock类,并执行mock方法。
-
fail: :该种方式会先去尝试调用服务提供者,若调用失败,再返回mock值。fail: 可以和 return xxx 、throw XxxException、指定mock类路径 搭配使用。如 fail: return xxx
-
force: :该种方式不会再调用服务提供者,而是直接返回客户端mock值。force:return xxx 这里返回的mock值为return 指定的 xxx,force: 可以和 return xxx 、throw XxxException、指定mock类路径 搭配使用。如 force: return xxx
更多用法参考: https://dubbo.apache.org/zh/docs/v2.7/user/examples/local-mock/
3. MockClusterInvoker 的加载
Dubbo 在加载 Cluster 接口时会使用其包装类 包装,而其中便提供了 MockClusterWrapper 来对 Invoke 进行包装。如下:
MockClusterWrapper 实现如下,借由此将 MockClusterInvoker 加入到了调用链路中。
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));
}
}
二、Mock 属性校验
消费者在创建服务提供者代理类时,第一步就是参数校验,其中就有对 mock参数的校验。其方法AbstractInterfaceConfig#checkMock
的实现如下:
void checkMock(Class<?> interfaceClass) {
// 如果 mock 没有配置,则直接跳过校验
if (ConfigUtils.isEmpty(mock)) {
return;
}
// 标准化 mock参数
String normalizedMock = MockInvoker.normalizeMock(mock);
// 对标准化后的参数校验
// 如果 是以 return 开头,则校验return 的返回值是否合法
if (normalizedMock.startsWith(Constants.RETURN_PREFIX)) {
normalizedMock = normalizedMock.substring(Constants.RETURN_PREFIX.length()).trim();
try {
//Check whether the mock value is legal, if it is illegal, throw exception
// 检测模拟返回值是否合法,不合法抛出异常
MockInvoker.parseMockValue(normalizedMock);
} catch (Exception e) {
throw new IllegalStateException("Illegal mock return in <dubbo:service/reference ... " +
"mock=\"" + mock + "\" />");
}
// 如果以 throw 开头,则校验 mock 的异常是否合法
} else if (normalizedMock.startsWith(Constants.THROW_PREFIX)) {
normalizedMock = normalizedMock.substring(Constants.THROW_PREFIX.length()).trim();
if (ConfigUtils.isNotEmpty(normalizedMock)) {
try {
//Check whether the mock value is legal
MockInvoker.getThrowable(normalizedMock);
} catch (Exception e) {
throw new IllegalStateException("Illegal mock throw in <dubbo:service/reference ... " +
"mock=\"" + mock + "\" />");
}
}
} else {
//Check whether the mock class is a implementation of the interfaceClass, and if it has a default constructor
// 检查模拟类是否是interfaceClass的实现,以及是否具有默认构造函数
// mock 为 DEFAULT 则按照 接口路径寻找 mock实现类,否则按照指定路径去寻找
MockInvoker.getMockObject(normalizedMock, interfaceClass);
}
}
我们下面主要看MockInvoker#normalizeMock
标准化参数的过程,其实现MockInvoker#normalizeMock
如下:
public static String normalizeMock(String mock) {
if (mock == null) {
return mock;
}
mock = mock.trim();
if (mock.length() == 0) {
return mock;
}
// mock = return,标准化为 return null
if (Constants.RETURN_KEY.equalsIgnoreCase(mock)) {
return Constants.RETURN_PREFIX + "null";
}
// mock = true || default || fail || force 标准化为 default
if (ConfigUtils.isDefault(mock) || "fail".equalsIgnoreCase(mock) || "force".equalsIgnoreCase(mock)) {
return "default";
}
// mock 以 fail: 开头,截取后面的数据
if (mock.startsWith(Constants.FAIL_PREFIX)) {
mock = mock.substring(Constants.FAIL_PREFIX.length()).trim();
}
// mock 以 force: 开头,截取后面的数据
if (mock.startsWith(Constants.FORCE_PREFIX)) {
mock = mock.substring(Constants.FORCE_PREFIX.length()).trim();
}
// mock 以 return 或 throw 开头,替换单引号为双引号
if (mock.startsWith(Constants.RETURN_PREFIX) || mock.startsWith(Constants.THROW_PREFIX)) {
mock = mock.replace('`', '"');
}
return mock;
}
需要注意的是,对于 fail: xxx 或 force: xxx 格式的mock配置,其返回值是 截取了 fail: 和 force: 之后的值。如 force: return 123, 返回值为 return 123
可以看到,AbstractInterfaceConfig#checkMock 对Mock 参数进行了校验,不合格的参数在消费者启动时就会抛出异常。
三、MockClusterInvoker
在Dubbo笔记⑨ : 消费者启动流程 - RegistryProtocol#refer 2 cluster.join(directory); 一文中,我们介绍过Dubbo在生成 Invoker 时 MockClusterWrapper 会将 FailoverClusterInvoker 实例包装成了MockClusterInvoker 实例。
这里简单介绍一下:
消费者端获取到的服务提供者实例本身是一个服务代理,其结构如下:
PefProxy 中存在 Invoker 实例,Invoker实例代表服务的可执行体,其中保存了当前可提供服务的服务列表(即 RegistryDirectory 中的 Invoker 列表),当消费者进行调用时,会调用其内部 Invoker#invoke 方法 。当消费者进行调用时,其调用顺序可以简化如下:
RefProxy#sayHello -> MockClusterInvoker#invoke -> FailoverClusterInvoker#invoke-> FailoverClusterInvoker#list -> RegistryDirectory#doList -> ...
其中
- MockClusterInvoker : 本文介绍的 MockClusterInvoker ,完成了 Dubbo 本地mock功能
- FailoverClusterInvoker : 完成了Dubbo集群容错功能,这里可以指定为别的ClusterInvoker。
- RegistryDirectory :服务目录,里面保存了可以提供服务的服务提供者列表,在调用时会经过负载均衡算法后挑选一个合适的 Invoker 进行服务调用。
本文关注点在 MockClusterInvoker#invoke
方法,其实现如下:
@Override
public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
// 判断是否开启mock 功能,通过设置 mock属性可以表明当前方法是否开启mock功能
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")) {
// 如果指定的是 force:return 策略
result = doMockInvoke(invocation, null);
} else {
// 否则按照指定的是 fail-mock 策略
try {
result = this.invoker.invoke(invocation);
} catch (RpcException e) {
if (e.isBiz()) {
throw e;
}
// 执行mock调用
result = doMockInvoke(invocation, e);
}
}
return result;
}
上面的逻辑比较简单我们这里可以分为三种情况:
- 没有开启mock : 没有开启mock,则直接调用下一层的 invoker方法。
- 开启 force 策略 :直接调用 mock 方法
- 开启 fail 策略 :先尝试调用服务,服务调用出了异常,再调用mock方法。
这里可以看到,force 和 fail 都是通过 doMockInvoke
方法完成的mock调用,其MockClusterInvoker#doMockInvoke
实现如下:
@SuppressWarnings({"unchecked", "rawtypes"})
private Result doMockInvoke(Invocation invocation, RpcException e) {
Result result = null;
Invoker<T> minvoker;
// 1. 从注册中心 获取 Mock Invoker
List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
if (mockInvokers == null || mockInvokers.isEmpty()) {
// 2. 创建 MockInvoker
minvoker = (Invoker<T>) new MockInvoker(directory.getUrl());
} else {
minvoker = mockInvokers.get(0);
}
try {
// 调用 invoke 方法
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;
}
这里我们看到 MockClusterInvoker#doMockInvoke
的逻辑很简单。
- selectMockInvoker(invocation) 从注册中心筛选出合适的 Invoker 集合 mockInvokers。
- 如果mockInvokers 不为空,则取第一个直接调用。否则创建一个 MockInvoker 进行调用。
下面我们来具体看一看:
1. MockClusterInvoker#selectMockInvoker
MockClusterInvoker#selectMockInvoker 从注册中心获取到协议为 mock 的服务列表。
// org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#selectMockInvoker
private List<Invoker<T>> selectMockInvoker(Invocation invocation) {
List<Invoker<T>> invokers = null;
//TODO generic invoker?
// 如果是 RPC 调用
if (invocation instanceof RpcInvocation) {
//Note the implicit contract (although the description is added to the interface declaration, but extensibility is a problem. The practice placed in the attachment needs to be improved)
// 记录当前请求是 mock 请求 (invocation.need.mock = true),这个参数在后续会使用到
((RpcInvocation) invocation).setAttachment(Constants.INVOCATION_NEED_MOCK, Boolean.TRUE.toString());
//directory will return a list of normal invokers if Constants.INVOCATION_NEED_MOCK is present in invocation, otherwise, a list of mock invokers will return.
try {
// 如果在调用中存在Constants.INVOCATION_NEED_MOCK 且为 true,则目录将返回模拟调用者的列表,否则,将返回常规调用者的列表。
invokers = directory.list(invocation);
} catch (RpcException e) {
// .... 日志打印
}
}
return invokers;
}
其中关键逻辑在于
invokers = directory.list(invocation);
在单注册中心情况下,这里的 directory 实现为 RegistryDirectory,所以这里为 RegistryDirectory#doList。关于 RegistryDirectory#doList 我们在 Dubbo笔记⑭ :Dubbo集群组件 之 Directory。这里简单提一下:
Directory#list 的调用顺序如下:
Directory#list -> RegistryDirectory#doList -> RouterChain#route
在Dubbo笔记⑨ : 消费者启动流程 - RegistryProtocol#refer 的 1. RegistryDirectory#buildRouterChain
章节中我们讲过,RegistryDirectory#RouterChain
中的 routers 实际对象为:
// 调用顺序也如下
MockInvokersSelector、TagRouter、AppRouter、ServiceRouter
RouterChain#route 的方法实现如下:
public List<Invoker<T>> route(URL url, Invocation invocation) {
List<Invoker<T>> finalInvokers = invokers;
for (Router router : routers) {
finalInvokers = router.route(finalInvokers, url, invocation);
}
return finalInvokers;
}
所以这首先会调用 MockInvokersSelector#route 方法,下面我们来看 MockInvokersSelector#router 的具体实现
1.1 MockInvokersSelector#router
MockInvokersSelector#router 方法会根据自身路由规则获取到 Invoker 集合。需要注意,如果附件 为 null (invocation.getAttachments() == null)此时方法返回的集合是正常服务的Invoker 集合。
public class MockInvokersSelector extends AbstractRouter {
public static final String NAME = "MOCK_ROUTER";
@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;
}
// 如果 附件为空就走正常的调用逻辑,因为 MockInvokersSelector 无法确定上游调用是mock还是正常调用
if (invocation.getAttachments() == null) {
return getNormalInvokers(invokers);
} else {
// 如果INVOCATION_NEED_MOCK 为 true 则执行mock流程。在 selectMockInvoker 方法中将 INVOCATION_NEED_MOCK 置为了true
String value = invocation.getAttachments().get(Constants.INVOCATION_NEED_MOCK);
if (value == null) {
return getNormalInvokers(invokers);
} else if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
return getMockedInvokers(invokers);
}
}
return invokers;
}
// 从当前注册中心的服务列表中 筛选出 mock服务并返回
private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
if (!hasMockProviders(invokers)) {
return null;
}
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
for (Invoker<T> invoker : invokers) {
if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
sInvokers.add(invoker);
}
}
return sInvokers;
}
// 从当前注册中心的服务列表中 筛选出 非mock服务并返回
private <T> List<Invoker<T>> getNormalInvokers(final List<Invoker<T>> invokers) {
if (!hasMockProviders(invokers)) {
return invokers;
} else {
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(invokers.size());
for (Invoker<T> invoker : invokers) {
if (!invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)) {
sInvokers.add(invoker);
}
}
return sInvokers;
}
}
}
上游在调用 MockInvokersSelector 时可能是 Mock调用也可能是正常调用,MockInvokersSelector 针对这两个场景做了不同的处理。这里可以看到,MockInvokersSelector#router 方法的作用是 如果附件为空,则从注册中心获取所有非 Mock 协议服务,否则根据附件中的 INVOCATION_NEED_MOCK 参数值决定是否获取 Mock 协议服务,如果为 true 则获取 mock 服务,否则获取正常服务。
这里可以看到如果开启了mock功能,会从注册中心获取到 mock协议的服务,并作为mock方法调用。但是在mock协议的服务却无法发布,因为 MockProtocol 类在export 方法中直接抛出了异常,并且定义了该类为 final,也就代表着我们无法重写MockProtocol 类
2. MockInvoker#invoke
在 MockInvokersSelector#router 后,如果有 mock 协议方法则远程调用 mock方法,这里不需要多说。如果没有 mock协议服务,则MockInvokersSelector#router返回的为 null。此时MockClusterInvoker#invoke
会创建 MockInvoker 作为本地mock调用。
MockInvoker#invoke 实现如下
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 尝试获取 methodName.mock 的配置信息
String mock = getUrl().getParameter(invocation.getMethodName() + "." + Constants.MOCK_KEY);
if (invocation instanceof RpcInvocation) {
((RpcInvocation) invocation).setInvoker(this);
}
if (StringUtils.isBlank(mock)) {
// 获取 mock 属性的信息,即我们设置 mock值
mock = getUrl().getParameter(Constants.MOCK_KEY);
}
// 为空抛出异常
if (StringUtils.isBlank(mock)) {
throw new RpcException(new IllegalAccessException("mock can not be null. url :" + url));
}
// mock 参数标准化 这里返回值会把 force: 和 fail: 截取
// 因为 关于 fail: 和 force: 策略的判断在 MockClusterInvoker#invoke 中已经完成,在这里这两个策略已经没有意义。
mock = normalizeMock(URL.decode(mock));
//处理 return mock 场景
if (mock.startsWith(Constants.RETURN_PREFIX)) {
// 截取 return 指定返回值并返回,默认为 null
mock = mock.substring(Constants.RETURN_PREFIX.length()).trim();
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 场景
} else if (mock.startsWith(Constants.THROW_PREFIX)) {
// 抛出 throw 指定异常,默认 RpcException
mock = mock.substring(Constants.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);
}
} else { //impl mock
try {
// 否则则认为是指定了 mock实现类,则进行调用
Invoker<T> invoker = getInvoker(mock);
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implementation class " + mock, t);
}
}
}
private Invoker<T> getInvoker(String mockService) {
// 从缓存中获取
Invoker<T> invoker = (Invoker<T>) mocks.get(mockService);
if (invoker != null) {
return invoker;
}
// 缓存未命中则通过反射创建
Class<T> serviceType = (Class<T>) ReflectUtils.forName(url.getServiceInterface());
T mockObject = (T) getMockObject(mockService, serviceType);
invoker = proxyFactory.getInvoker(mockObject, serviceType, url);
// 这里可以看到, Dubbo限制了每个接口 mock 类最大 10000 个
if (mocks.size() < 10000) {
mocks.put(mockService, invoker);
}
return invoker;
}
四、总结
1. 流程图
综上,整个过程的流程图大致如下:
2. 一些疑问
下面是本篇中的一些疑问以及自身理解。才疏学浅,难免有误,感谢指正。
2.1. 远程mock
在上面代码中, MockInvokersSelector#router 中筛选了注册中心上 mock协议的服务。这意味着其实可以调用远程mock 服务。(MockInvokersSelector#router 返回的 Invoker 如果不为空会被用于mock 调用)。但是在服务提供者端缺无法发布 mock协议的服务。原因在于 MockProtocol#export 方法会直接抛出异常,并且MockProtocol 被定义为了 final,这意味着我们没办法重写该方法。
final public class MockProtocol extends AbstractProtocol {
@Override
public int getDefaultPort() {
return 0;
}
@Override
public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
throw new UnsupportedOperationException();
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
return new MockInvoker<T>(url);
}
}
2.2. 消费者多分组情况
如果消费者多分组调用时,并且存在至少一个服务提供者的情况下。force 策略不起作用。即还是会进行远程调用。确切的说,应该是在这种情况下,服务路由不起作用
原因在于 RegistryDirectory#doList 中针对多分组情况直接返回了注册中心所有 Invokers。
首先需要明确下面的调用链路
MockClusterInvoker#doMockInvoke =》 MockClusterInvoker#selectMockInvoker =》 RegistryDirectory#doList => RouterChain#route(在此方法中进行路由)
RegistryDirectory#doList 方法简化如下:
@Override
public List<Invoker<T>> doList(Invocation invocation) {
if (multiGroup) {
return this.invokers == null ? Collections.emptyList() : this.invokers;
}
List<Invoker<T>> invokers = null;
invokers = routerChain.route(getConsumerUrl(), invocation);
return invokers == null ? Collections.emptyList() : invokers;
}
正常情况下(单分组情况):在 RegistryDirectory#doList 中我们会通过 invokers = routerChain.route(getConsumerUrl(), invocation);
来调用路由链,从而调用MockInvokersSelector#router方法将注册中心所有mock协议的服务筛选出来,但是由于mock协议的服务无法注册,所以这里从注册中心无法获取到 invoker,所以导致 MockClusterInvoker#selectMockInvoker 方法返回的 Invoker集合为空。而MockClusterInvoker#doMockInvoke 判断 MockClusterInvoker#selectMockInvoker 方法返回为空后会创建 MockInvoker 来进行服务调用,完成本地mock功能。也就是说,是否本地调用取决于 MockClusterInvoker#selectMockInvoker 返回的 Invoker 集合是否为空。
而多分组情况下: RegistryDirectory#doList 直接判断 multiGroup = true,所以直接将注册中心所有的 Invoker 集合返回。此时 MockClusterInvoker#selectMockInvoker 返回一个的Invoker集合不为空。而在MockClusterInvoker#doMockInvoke 中,如果 MockClusterInvoker#selectMockInvoker 返回不为空,则会直接挑选第一个 invoker 进行服务调用。由于没有使用 MockInvoker调用,所以无法完成本地 mock。
以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://dubbo.apache.org/zh/docs/v2.7/dev/source/
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正