Dubbo源码解析——mock
使用
Dubbo提供了mock和stub两种方式进行服务的优雅的动态降级,临时屏蔽一些非关键服务,并定义返回策略。官方文档上给出了我们使用方式:
1 | RegistryFactory registryFactory = ExtensionLoader.getExtensionLoader(RegistryFactory.class).getAdaptiveExtension(); |
其中:
- 使用
mock=force:return+null
表示消费方对该服务的方法调用都直接返回null
值,不发起远程调用。 - 还可以改为
mock=fail:return+null
表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。 - 其中Ip地址配置为
0.0.0.0
表示对所有IP
地址生效,如果只想覆盖某个IP
的数据,请填入具体IP。 dynamic=false
表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心。application=foo
表示只对指定应用生效,可不填,表示对所有应用生效。category=configurators
表示该数据为动态配置类型。override://
表示数据采用覆盖方式,支持override
和absent
(缺省:如果原先存在该属性的配置,则以原先配置的属性值优先;如果原先没有配置该属性,则添加新的配置属性)。
以上的一些具体信息,可以看下dubbo的官方手册的配置规则
和服务降级
两个小节。
代码分析
来看下dubbo的实现,这里会稍微复习一下Dubbo的wrapper class
机制,顺便小提一下Dubbo的SPI。
我们先看看我们的mock
在哪里生效,我们看下MockClusterInvoker
这个类,至于为什么是在这个类中生效的,我们一会说,先看下这个类的invoker
方法:
1 | Result result = null; |
MockClusterInvoker
跟所有的ClusterInvoker
一样,会维护一个Directory
,关于Directory
这里不细说,可以翻看我的历史文章。
这里先从URL里获取键mock
的值,然后进行区分是进行强制mock
(不发起远程调用,直接返回)还是失败mock
(调用失败则返回mock
的值)。
我们看下,如果使用force mock
的情况,fail mock
非常类似就不讲了,看下force mocke
时,调用的doMockInvoke
方法:
1 | Result result = null; |
我们用覆盖配置的方式,selectMockInvoker
方法是找不到mock invoker
的,只有使用这种方式时,才可以直接找到自定义的MockInvoker
:
1 | <dubbo:reference id="demoService" mock="org.apache.dubbo.demo.DemoServiceMock" check="false" interface="org.apache.dubbo.demo.DemoService"/> |
注意mock
里的值必须以XXXMock
为名,不能乱起。我们看下覆盖配置的时候会发生什么:
1 | if (mockInvokers == null || mockInvokers.isEmpty()) { |
直接用url创建一个MockInvoker
,需要注意的是,这里的Url是已经覆盖过的Url(因为我们向注册中心注册了一个override
的url,为什么这里的URL变化了,可以看我以前Directory
的文章),我们写入了一个override://
的覆盖配置,生效之后,我们的Url应该是类似:
1 | ...&interface=org.apache.dubbo.demo.DemoService&methods=sayHello&mock=force:return+null&... |
注意这里的mock
已经被我们的配置给覆盖掉了。
继续看到MockInvoker#invoke
:
1 | // 方法上的mock,这里应该是空的 |
这里其实就几种情况:
- 如果只有一个return,一般是mock返回值为void的情况,直接返回一个Result。
- 如果是开头是return,比如return null,那么就尝试构建一个null并且返回。
- 如果是mock抛出异常,则尝试抛出指定的异常。
- 如果都不是,那么就是用户自定义了mock的类,就像之前说的在配置文件中声明
mock="XXXMock"
,这种情况就尝试去加载XXXMock
这个类,然后执行对应的方法进行mock。
MockClusterInvoker什么时候把ClusterInvoker包装的
我们可以debug一下整个过程,其实MockClusterInvoker
是在具体的ClusterInvoker
执行invoke之前就生效了的(要不然也没法做到不force mock
,因为force mock
是不发起远程请求的)。
我们可以理解为MockClusterInvoker
里维护了一个ClusterInvoker
。
可能看过我以前的文章的同学已经明白是怎么生效的了,这里重新拿出来大体说一下。
我们在使用ExtensionLoader
加载扩展点的时候,方法:
1 | private boolean isWrapperClass(Class<?> clazz) { |
判断是否是一个WrapperClass
,依据就是这个Class
是否有一个以当前类型为入参的构造方法,比如MockClusterWrapper
就是一个WrapperClass
,它会包装Cluster
的实现,因为他有个以Cluster
为入参的构造方法:
1 | public class MockClusterWrapper implements Cluster { |
这样我们执行join
时实际产生的是MockClusterInvoker
。而具体在什么时候进行包装的,这个是在ExtensionLoader#createExtension
里:
1 | Set<Class<?>> wrapperClasses = cachedWrapperClasses; |
这样我们就把ClusterInvoker
包装好了,再通过其中的Directory
动态控制URL,然后实现我们的动态mock
以及降级。
后记
- 最近被选为
Dubbo committer
了,顺便加入了Apache
基金会。我自己本人知道Dubbo,学习Dubbo,贡献代码,再到成为Committer
,经历了大概两年多,我会写一个我的学习历程和新路历程,希望帮助更多的人加入到Dubbo里,也为希望学习Dubbo的同学指一条我自己走出来的路。 - 其实Dubbo还有一种方式实现优雅降级,不过不太常用,就是本地存根——
Stub
,这个比较复杂且场景比较少,可能会有空再去研究一下了。也就是说,本篇文章不是系列文章,而是单讲mock
。