前言

关于服务降级,相信很多小伙伴都听说过或者操作过。比如最近我们在 ​​12306​​​ 上抢票回家,明明看到剩余的有票,可就是买不到,这就是很明显的一个(读)服务降级例子。再比如双十一时我们付款时偶尔出现付款失败,重新支付,也是(限流)服务降级的一种,也有许多其它降级的例子,大家可以自行搜索。具体来说就是:​​当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行​​​ 。服务降级的影响也很明显,用户体验不好。在这里就讲讲 ​​Dubbo​​ 的服务降级。

Dubbo 服务降级

在 ​​Dubbo​​ 中服务降级有两种选择

  • 屏蔽(​​force​​​):在可预知的情况下执行,比如上次拼多多​​100​​ 元优惠券事件,如果在发现漏洞后及时限制支付服务,就不会造成所谓被薅200亿濒临倒闭的传闻(仅仅举例子,还是风控各种没做好)
  • 容错(​​fail​​):在服务超时,网络异常时等非业务异常时使用

服务降级配置

上面我们只使用了 ​​return null​​,其实还有其它自定义配置

1、使用自定义mock类(接口名+Mock)

  • ​mock = default => DemoServiceMock​
  • ​mock = true => DemoServiceMock​
  • ​mock = fail => DemoServiceMock​
  • ​mock = force => DemoServiceMock​​ 2、先普通执行,执行失败之后再执行相应的mock逻辑
  • ​mock = fail:throw => throw new RpcException(" mocked exception for Service degradation. ");​
  • ​mock = fail:throw XxxException => throw new RpcException(RpcException.BIZ_EXCEPTION, XxxException);​
  • ​mock = fail:return => return null​
  • ​mock = fail:return xxx => return xxx​
  • ​mock = fail:return empty => return new Car()​​ 3、直接执行相应的mock逻辑
  • ​mock = force:throw => throw new RpcException(" mocked exception for Service degradation. ");​
  • ​mock = force:throw XxxException => throw new RpcException(RpcException.BIZ_EXCEPTION, XxxException);​
  • ​mock = force:return => return null​
  • ​mock = force:return xxx => return xxx​
  • ​mock = force:return empty => return new Car()​

屏蔽例子

我简单写了一个接口,共有两个方法,登录和注册

public interface UserService {

JsonResponse<String> login(User user);

JsonResponse<String> register(User user);
}

首先启动服务者,消费者我决定使用单元测试的方式。

在演示之前,为了加深大家的理解,我们连接上 ​​ZooKeeper​​ 去查看该服务的注册信息

Dubbo 源码分析之服务降级_服务降级源码


从图片可以看出在我们提供的服务 ​​/dubbo/com.dfire.service.UserService​​ 下共有四个目录

  • consumers: 消费者的​​URL​​ 信息
  • configurators: 覆盖​​URL​​ 配置信息
  • routers: 路由配置信息
  • providers: 提供者的​​URL​​ 信息

此时由于我们的提供者已经启动,所以其 ​​URL​​​ 信息已经展示,​​URL​​ 解码后内容为

dubbo://10.1.87.92:20880/com.dfire.service.UserService?anyhost=true&application=xiaosuda_provider&dispatcher=all&dubbo=2.6.5&generic=false&interface=com.dfire.service.UserService&methods=login,register&pid=3316&side=provider&threadpool=cached&threads=50&timestamp=1548320426971&uptime=1548320426991

基本都是我们配置的信息。

接下来打开配置中心(好像 ​​2.7.0+​​​ 要使用 ​​nacos​​​,配置方法肯定大同小异) ​​dubbo-admin​

Dubbo 源码分析之服务降级_dubbo服务降级源码_02

配置我们的注册方法 ​​register​​​ 的服务降级策略为 屏蔽 (​​force​​​) 返回的结果为 ​​return null​​,点击保存

下面在看一下我们的 ​​ZooKeeper​​ 信息

Dubbo 源码分析之服务降级_服务降级源码_03


发现 ​​/dubbo/com.dfire.service.UserService/configurators​​ 节点的数据内容从无变为

override://0.0.0.0/com.dfire.service.UserService?category=configurators&dynamic=false&enabled=true&register.mock=force:return null

是以 ​​override​​​ 开头的协议,我们需要关注的参数:​​register.mock=force:return nul​​​,解释为: ​​com.dfire.service.UserService.register()​​​方法的服务降级策略为屏蔽,直接返回 ​​null​​​。
关于配置覆盖:

  • ​override://​​ 表示数据采用覆盖方式,支持 override 和 absent,可扩展,必填。
  • ​0.0.0.0​​ 表示对所有 IP 地址生效,如果只想覆盖某个 IP 的数据,请填入具体 IP,必填。
  • ​com.alibaba.dubbo.demo.DemoService​​表示只对指定服务生效,必填。
  • ​category=configurators​​ 表示该数据为动态配置类型,必填。
  • ​dynamic=false​​ 表示该数据为持久数据,当注册方退出时,数据依然保存在注册中心,必填。
  • ​enabled=true​​ 覆盖规则是否生效,可不填,缺省生效。
  • ​application=demo-consumer​​ 表示只对指定应用生效,可不填,表示对所有应用生效。
  • ​mock=force:return+null​​​表示将满足以上条件的​​mock​​​ 参数的值覆盖为​​force:return+null​​​。如果想覆盖其它参数,直接加在​​override​​​ 的​​URL​​ 参数上。

接下来执行我们的单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerApplicationTests {

@Autowired
private UserService userService;

@Test
public void testForceNullMock() {
User user = new User();
user.setUsername("张三");
user.setPassword("张三");
JsonResponse<String> response = userService.register(user);
Assert.assertNull(response);
}
}

执行后看到绿色的 ​​Tests passed​

容错例子

容错例子也很简单,就是在一次正常的服务调用失败后执行服务降级操作,如果这一次正常的服务执行成功则不会执行服务降级操作。具体配置与屏蔽类似

Dubbo 源码分析之服务降级_ide_04

演示就不再演示了。

服务降级源码分析

服务降级的入口在 ​​MockClusterInvoker.invoke()​​方法

public Result invoke(Invocation invocation) throws RpcException {
Result result = null;
// 判断方法名.mock 的值 即上面例子的:register.mock
String value = directory.getUrl().getMethodParameter(invocation.getMethodName(), Constants.MOCK_KEY, Boolean.FALSE.toString()).trim();
// 未设置mock 走正常 invoke
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());
}
//屏蔽
result = doMockInvoke(invocation, null);
} else {
//容错
try {
//首先正常 invoke
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);
}
//异常后 走mock invoke
result = doMockInvoke(invocation, e);
}
}
}
return result;
}

需要注意的点

  • ​directory.getUrl()​​​ :​​directory​​​ 对象为​​RegistryDirectory​​​,​​URL​​ 会动态变化

​doMockInvoke​​ 方法

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;
}

代码很简单,首先获得 ​​mockInvokers​​​ 集合,如果该集合为空新建一个对象,否则以集合的第一个为准。
继续看​​​selectMockInvoker​

/**
* 返回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;
}

判断 ​​invocation​​​ 是否为 ​​RpcInvocation​​​ 的实例,如果是为 ​​invocation​​​ 添加 ​​invocation.need.mock=true​

然后在 ​​RegistryDirectory​​​ 的抽象类中 ​​AbstractDirectory​​​ 进行筛选 ​​invokers​

public List<Invoker<T>> list(Invocation invocation) throws RpcException {
if (destroyed){
throw new RpcException("Directory already destroyed .url: "+ getUrl());
}
//模版方法 实例为RegistryDirectory 获得所有Invoker
List<Invoker<T>> invokers = doList(invocation);
// 开启时移除断流地址或者关闭时清空熔断信息 addCircuitBreaker
CircuitBreakerManager circuitBreakerManager = ExtensionLoader.getExtensionLoader(CircuitBreakerManager.class).getAdaptiveExtension();
invokers = circuitBreakerManager.filterCricuitBreakInvoker(invokers,invocation);
//路由策略 默认会在routers集合中添加MockInvokersSelector
List<Router> localRouters = this.routers; // local reference
if (localRouters != null && localRouters.size() > 0) {
for (Router router: localRouters){
try {
if (router.getUrl() == null || router.getUrl().getParameter(Constants.RUNTIME_KEY, true)) {
invokers = router.route(invokers, getConsumerUrl(), invocation);
}
} catch (Throwable t) {
logger.error("Failed to execute router: " + getUrl() + ", cause: " + t.getMessage(), t);
}
}
}
return invokers;
}

上面代码作用:从服务字典中获得所有的服务,然后进行路由过滤把过滤后的服务列表返回。
主要看​​​MockInvokersSelector​​​类的 ​​route​​ 方法。

public <T> List<Invoker<T>> route(final List<Invoker<T>> invokers,
URL url, final Invocation invocation) throws RpcException {
if (invocation.getAttachments() == null) {
return getNormalInvokers(invokers);
} else {
// invocation.need.mock 就是在MockClusterInvoker.selectMockInvoker 中添加的
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;
}

主要就是判断 ​​invocation.need.mock​​​ 的值是否为 ​​true​​​ 。如果为 ​​true​​​ 调用​​getMockedInvokers​​​,否则调用​​getNormalInvokers​

private <T> List<Invoker<T>> getNormalInvokers(final List<Invoker<T>> invokers){
//判断是否存在mock://协议的提供者 如果不存在 直接返回原提供者
if (! hasMockProviders(invokers)){
return invokers;
} else { //存在mock://协议的提供者 去掉mock://的提供者过滤后返回
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;
}
}
private <T> List<Invoker<T>> getMockedInvokers(final List<Invoker<T>> invokers) {
//判断是否存在mock://协议的提供者 如果不存在 直接返回null
if (! hasMockProviders(invokers)){
return null;
}
List<Invoker<T>> sInvokers = new ArrayList<Invoker<T>>(1);
for (Invoker<T> invoker : invokers){
// 过滤mock://的提供者
if (invoker.getUrl().getProtocol().equals(Constants.MOCK_PROTOCOL)){
sInvokers.add(invoker);
}
}
return sInvokers;
}

调用层次比较深,大家最好 ​​debug​​​ 查看。过滤完之后就又回到了 ​​MockClusterInvoker.doMockInvoke​​ 方法.

private Result doMockInvoke(Invocation invocation,RpcException e){
Result result = null;
Invoker<T> minvoker ;

List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
//mockInvokers 结果返回 可能为空
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;
}

下面就要进入 ​​MockInvoker.invoke​​ 方法了

public Result invoke(Invocation invocation) throws RpcException {
//该值即我们在dubbo-admin设置的 可能是 force:return null
String mock = getUrl().getParameter(invocation.getMethodName()+"."+Constants.MOCK_KEY);
if (invocation instanceof RpcInvocation) {
//设置invoke 为当前实例
((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));
}
//首先进行URL解码获得mock的具体要执行的结果 比如force:return null 执行normallizeMock后结果为return null
mock = normallizeMock(URL.decode(mock));
//配置 “return” 执行该语句
if (Constants.RETURN_PREFIX.trim().equalsIgnoreCase(mock.trim())){
RpcResult result = new RpcResult();
result.setValue(null);
return result;
} else if (mock.startsWith(Constants.RETURN_PREFIX)) { //配置 “return *” *表示任务内容执行该语句
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)) { //配置 “throw *” 执行该语句
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 { //自定以mock实现执行该语句
try {
Invoker<T> invoker = getInvoker(mock);
return invoker.invoke(invocation);
} catch (Throwable t) {
throw new RpcException("Failed to create mock implementation class " + mock , t);
}
}
}

​MockInvoker.invoke​​​ 的功能就是处理​​mock​​的结果的地方,具体注释已经很清楚了,不再详述。

寒冬来了,祝大家都能找到心满意足的工作。