dubbo服务降级

Dubbo作为一款服务治理框架,本身也包含了服务降级的功能,主要是对非重要的服务进行降级处理以免影响整体业务,本篇主要介绍一下如何做服务降级以及Dubbo是怎么实现的。
如何做服务降级
Dubbo提供了两种配置可以实现服务降级,一个是stub,一个是mock,当然为了兼容以前版本还有个local(和stub基本一样,不做具体介绍),实际上stub不是用作降级处理的,它原本的设计意图是在服务端接口层做一层代理,在服务调用前后通过自定义的扩展加入一些客户端的逻辑,所以官方称它叫本地存根,当然我们也可以用它实现一些类似降级的功能。而mock就是真的服务降级,除了用于降级,还可以作用一些接口测试。
他们的区别大概就是:
stub与 AOP 的 around advice 类似,mock等同于 AOP 中的 after-throwing advice。
从Dubbo官方提供的图,我们也能看到区别:
dubbo服务降级
文章图片

stub 我们先说说如何通过stub的方式实现服务降级,对于stub,有三种赋值方式:

含义
true 取对应Service对应类全名+Stub
default 同true
类全名(含包名) 取自己指定的类
举个例子:
现在有个service接口如下:
package com.example.dubboprovider.rpc; public interface CityService { String getCityName(); }

消费者端引入:
@Reference(stub = "true") CityService cityService;

【dubbo服务降级】消费者端还需要新建一个类:
package com.example.dubboprovider.rpc; /** * @author Don * @date 2021/11/8. */ public class CityServiceStub implements CityService { CityService cityService; public CityServiceStub(CityService cityService){ this.cityService = cityService; }@Override public String getCityName() { //before call cityService.getCityName(); //after call return "stub"; } }

这里有两点要注意:
1、包名必须和服务端提供的Service包名一致,这里是com.example.dubboprovider.rpc;
2、类要继承服务端提供的Service接口并要有一个有该Service类型参数的构造函数;
对于第一点的约束,我们可以通过第三种配置来规避。
比如包名改为com.example.dubboanalyze.stub之后,对应的消费端引入的stub改为具体的类全名
@Reference(stub = "com.example.dubboanalyze.stub.CityServiceStub") CityService cityService;

mock 再来说一下真正的降级mock,这里要注意的一点是,mock只支持客户端,不支持服务端,2.7.0之后的版本如果在定义的Service中加了mock会导致启动失败,如:@Service(mock = "true")
mock支持以下值配置,长度不能大于200
含义
true/default/fail/force Service类全名 + Mock
return 返回 null
return xxx 返回 xxx
throw xxx 抛出指定异常,xxx 必须是异常类全名,如:throw com.example.dubboanalyze.exception.DubboException
fail: 远程调用失败发起mock调用
force: 不做远程调用,直接发起mock调用,可用于测试
类全名 同fail
有上面的表格我想差不多都明白了,这里再举个例子:
service接口还是和上面一样,消费者端引入:
@Reference(mock = "force:com.example.dubboanalyze.mock.CityServiceMock") CityService cityService;

再看CityServiceMock类的定义:
package com.example.dubboanalyze.mock; public class CityServiceMock implements CityService { @Override public String getCityName() { return "mock"; } }

实现原理
看过了服务降级是怎么用的之后,也来看看服务降级是怎么做的。对于配置的值是怎么设置上去的就不讲了,在常用配置这一文中已经介绍了。
stub 先说一下stub,这里需要看到非常重要的一个类StubProxyFactoryWrapper,它继承了ProxyFactory
dubbo服务降级
文章图片

基于 spi 机制 Dubbo 内置了加载该类,可以看 org.apache.dubbo.rpc.ProxyFactory 文件,定义了所有的ProxyFactory
dubbo服务降级
文章图片

基于有spi机制的有wrapper先用wapper的原则,Reference在获取代理的时候((T) proxyFactory.getProxy(invoker))会先使用StubProxyFactoryWrapper,也就是下面这段逻辑
dubbo服务降级
文章图片

从逻辑中可以看出以下信息:
1)泛化调用是不会被 stub 的;
2)除了默认的 ServiceName + Stub 就是自定义类全名;
3)其实是对于 Local 的一种兼容替换;
4)Stub类需要有构造函数带Service类型的参数;
5)里面有一个export,但是目前看起来没什么用;
最终返回的代理就是这个stub类,所以我们在发起接口调用的时候会先执行stub。
mock 说到mock,需要看到非常重要的一个类MockClusterWrapper
dubbo服务降级
文章图片

和上面一样,也是 Wrapper 并且继承了Clusterjoin 方法就是生成一个MockClusterInvoker,同样内部也封装了一个普通的 Invoker。那在什么时候调用的呢?是在启动的时候生成接口代理类时,也就是org.apache.dubbo.config.ReferenceConfig#createProxy
dubbo服务降级
文章图片

如果有多个注册中心或者多直连url,则直接合并集群的时候生成,如果只有一个的话,则是在协议引用的时候org.apache.dubbo.registry.integration.RegistryProtocol#doRefer
dubbo服务降级
文章图片

所以我们一开始远程调用时候其实是先走的MockClusterInvoker,它也是继承了Invoker接口
dubbo服务降级
文章图片

从代码中我们可以看到除了 force 的时候调用就是在抛出RpcException的时候发起调用,继续跟进代码org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#doMockInvoke -> org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker#selectMockInvoker
dubbo服务降级
文章图片

可以看到这里去找Invoker,继续跟进到org.apache.dubbo.registry.integration.RegistryDirectory#doList
dubbo服务降级
文章图片

可以看到这里使用路由链的方式去找
dubbo服务降级
文章图片

后面的很简单了,这里要注意的是MockInvokersSelector,除非我们定义了 Mock 协议的服务,否则就会返回 null,然后新建一个MockInvoker,而新建的MockInvoker就会通过解析我们定义的各种mock值来发起调用
dubbo服务降级
文章图片

总结
这一篇大概介绍了一些stub和mock两种服务降级方式,大家应该有一些感悟了,比如可以通过stub对dubbo接口调用统一记日志,通过mock做接口屏蔽测试。另外还有两个点没有研究也没有写
  • mock的协议还没有研究
  • 服务端是否能够做stub

    推荐阅读