(万字好文)Dubbo自带的服务降级机制&整合Sentinel做熔断降级

本文介绍了Dubbo服务降级机制及其实现方式,分析了Dubbo服务降级机制的缺陷,并探讨了熔断机制的概念。通过三种不同的方式接入Sentinel,实现了Dubbo接口的熔断和流控。

一、Dubbo服务降级实战

1 mock 机制

谈到服务降级,Dubbo 本身就提供了服务降级的机制;而 Dubbo 的服务降级机制主要是利用服务消费者的 mock 属性。

服务消费者的 mock 属性有以下三种使用方式,下面将带着例子简单介绍一下。

1.1 服务消费者注册url的mock属性

例子:
mock=return+null,即当服务提供者出现异常(宕机或者业务异常),则返回null给服务消费者。

2021-01-26 09:39:54.631 [main] [INFO ] [o.a.d.r.z.ZookeeperRegistry] [] [] -  [DUBBO] Notify urls for subscribe url consumer://127.0.0.1/com.winfun.service.DubboServiceOne?application=dubbo-service&category=providers,configurators,routers&check=false&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&lazy=true&methods=sayHello&mock=return+null&pid=36270&qos.enable=false&reference.filter=default,dubboLogFilter&release=2.7.7&retries=1&side=consumer&sticky=false&timestamp=1611625194544

1.2 @DubboReference注解或者dubbo:reference标签的mock属性

例子:
mock=“return null”,即当服务提供者出现异常(宕机或者业务异常),则返回null给服务消费者。

public class HelloController{
   
   
    
    @DubboReference(check = false,lazy = true,retries = 1,mock = "return null")
    private DubboServiceOne dubboServiceOne;
    
    //.....
}

1.3 服务消费者mock属性设置为true+Mock实现类

例子:
Mock实现类 为 Dubbo接口 的实现类,并且 Mock实现类 与 Dubbo接口 放同一个路径下(可不同项目,但是保证包路径是一致的)。

/**
 *
 * DubboServiceTwo
 * @author winfun
 * @date 2020/10/29 5:00 下午
 **/
public interface DubboServiceTwo {
   
   

    /***
     *  say hi
     * @author winfun
     * @param name name
     * @return {@link ApiResult<String> }
     **/
    ApiResult<String> sayHi(String name);
}

/**
 * DubboServiceTwo 降级实现类
 * @author winfun
 * @date 2021/1/26 9:21 上午
 **/
public class DubboServiceTwoMock implements DubboServiceTwo{
   
   


    /***
     *  say hi
     * @author winfun
     * @param name name
     * @return {@link ApiResult <String> }
     **/
    @Override
    public ApiResult<String> sayHi(String name) {
   
   
        return ApiResult.fail("Mock实现类-服务降级了");
    }
}

2 实战例子:

下面将使用第二种方式来展示 Duboo 自身提供的服务降级机制。

2.1 consumer 代码:

/**
 * Say Hello
 * @author winfun
 * @date 2020/10/29 5:12 下午
 **/
@RestController
public class HelloController {
   
   

    @DubboReference(check = false,lazy = true,retries = 1,mock="return {\"code\":1,\"message\":\"熔断限流了\"}")
    private DubboServiceOne dubboServiceOne;

    @GetMapping("/hello/{name}")
    public ApiResult sayHello(@PathVariable("name") String name){
   
   
        return this.dubboServiceOne.sayHello(name);
    }
}

2.2 provider 代码:

/**
 * DubboServiceOneImpl
 * @author winfun
 * @date 2020/10/29 5:04 下午
 **/
@DubboService(interfaceClass = DubboServiceOne.class)
public class DubboServiceOneImpl implements DubboServiceOne {
   
   
    /***
     *  say hello
     * @author winfun
     * @param name name
     * @return {@link ApiResult<String> }
     **/
    @SneakyThrows
    @Override
    public ApiResult<String> sayHello(String name) {
   
   
        // dubbo 接口默认超时时间为1s,我们这里直接休眠5s
        Thread.sleep(5000);
        return ApiResult.success("hello "+name);
    }

2.3 结果分析:

情况一:服务提供者没起来

这个时候服务消费者不会进行重试,只会直接进行服务降级,返回我们设定的 Mock 值。

2021-01-27 18:02:12.288 [http-nio-8081-exec-1] [WARN ] [o.a.d.r.c.s.w.MockClusterInvoker] [/hello/name] [16117417328056102141] -  [DUBBO] fail-mock: sayHello fail-mock enabled , url : zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-service&check=false&dubbo=2.0.2&init=false&interface=com.winfun.service.DubboServiceOne&lazy=true&methods=sayHello&mock=return+%7B%22code%22%3A1%2C%22message%22%3A%22%E7%86%94%E6%96%AD%E9%99%90%E6%B5%81%E4%BA%86%22%7D&pid=51874&qos.enable=false&reference.filter=default,dubboLogFilter,-sentinel.dubbo.consumer.filter&register.ip=172.26.144.16&release=2.7.7&retries=0&side=consumer&sticky=false&timestamp=1611741716665, dubbo version: 2.7.7, current host: 172.26.144.16
org.apache.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 172.26.144.16 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist).
	//.....省略
2021-01-27 18:02:12.289 [http-nio-8081-exec-1] [INFO ] [o.a.d.r.c.s.w.MockClusterInvoker] [/hello/name] [16117417328056102141] -  [DUBBO] Exception when try to invoke mock. Get mock invokers error for service:com.winfun.service.DubboServiceOne, method:sayHello, will construct a new mock with 'new MockInvoker()'., dubbo version: 2.7.7, current host: 172.26.144.16
org.apache.dubbo.rpc.RpcException: No provider available from registry 127.0.0.1:2181 for service com.winfun.service.DubboServiceOne on consumer 172.26.144.16 use dubbo version 2.7.7, please check status of providers(disabled, not registered or in blacklist).
    //....省略

大家可能会存在一个质疑点:我上面说的是不会进行重试,但是控制台我们看到了两次异常;其实第二次异常,是获取 MockInvoker 导致的,所以大家可以先忽略,下面我会详细分析 MockClusterInvoker 的源码,就知道为啥会有两次异常了。

情况二:服务提供者超时

注意:Dubbo 提供的服务降级机制,不支持对服务提供者业务异常的情况。

这个时候消费者会进行 1+retries 次调用,然后再进行服务降级。

org.apache.dubbo.rpc.RpcException: Failed to invoke the method sayHello in the service com.winfun.service.DubboServiceOne. Tried 2 times of the providers [127.0.0.1:20881] (1/1) from the registry 127.0.0.1:2181 on the consumer 172.26.144.16 using the dubbo version 2.7.7. Last error is: Invoke remote method timeout. method: sayHello, provider: dubbo://127.0.0.1:20881/com.winfun.service.DubboServiceOne?anyhost=true&application=dubbo-service&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&init=false&interface=com.winfun.service.DubboServiceOne&lazy=true&methods=sayHello&mock=return+%7B%22code%22%3A1%2C%22message%22%3A%22%E7%86%94%E6%96%AD%E9%99%90%E6%B5%81%E4%BA%86%22%7D&pid=52020&qos.enable=false&reference.filter=default,dubboLogFilter,-sentinel.dubbo.consumer.filter&register.ip=172.26.144.16&release=2.7.7&remote.application=dubbo-provider-one&retries=1&service.filter=default,dubboLogFilter&side=consumer&sticky=false&timestamp=1611742232968, cause: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2021-01-27 18:10:42.189, end time: 2021-01-27 18:10:43.204, client elapsed: 1 ms, server elapsed: 1014 ms, timeout: 1000 ms, request: Request [id=1, version=2.0.2, twoway=true, event=false, broken=false, data=null], channel: /172.26.144.16:56663 -> /172.26.144.16:20881
	at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:113)
//....省略

3 源码分析

关于服务降级的逻辑,主要在 Dubbo 提供的 MockClusterInvoker 类中。

下面我们直接看看 MockClusterInvoker 的源码:

public class MockClusterInvoker<T> implements Invoker<T> {
   
   

    // ...省略

    @Override
    public Result invoke(Invocation invocation) throws RpcException {
   
   
        Result result = null;
        // 从 consumer 的注册url中获取方法的 mock 属性值
        String value = getUrl().getMethodParameter(invocation.getMethodName(), MOCK_KEY, Boolean.FALSE.toString()).trim();
        // mock 为空或者false,不进行服务降级处理
        if (value.length() == 0 || "false".equalsIgnoreCase(value)) {
   
   
            //no mock
            result = this.invoker.invoke(invocation);
        // 是否强行执行服务降级处理
        } else if (value.startsWith("force")) {
   
   
            if (logger.isWarnEnabled()) {
   
   
                logger.warn("force-mock: " + invocation.getMethodName() + " force-mock enabled , url : " + getUrl());
            }
            //force:direct mock
            result = doMockInvoke(invocation, null);
        } else {
   
   
            //fail-mock
            try {
   
   
                result = this.invoker.invoke(invocation);

                //fix:#4585
                if(result.getException() != null && result.getException() instanceof RpcException){
   
   
                    RpcException rpcException= (RpcException)result.getException();
                    // 如果是业务异常,直接抛出
                    if(rpcException.isBiz()){
   
   
                        throw  rpcException;
                    }else {
   
   
                        // 服务降级处理
                        result = doMockInvoke(invocation, rpcException);
                    }
                }

            } catch (RpcException e) {
   
   
                // 如果是业务异常,直接抛出
                if (e.isBiz()) {
   
   
                    throw e;
                }

                if (logger.isWarnEnabled()) {
   
   
                    logger.warn("fail-mock: " + invocation.getMethodName() + " fail-mock enabled , url : " + getUrl(), e);
                }
                // 服务降级处理
                result = doMockInvoke(invocation, e);
            }
        }
        return result;
    }

    @SuppressWarnings({
   
   "unchecked", "rawtypes"})
    private Result doMockInvoke(Invocation invocation, RpcException e) {
   
   
        Result result = null;
        Invoker<T> minvoker;

        // 获取服务降级 Invoker 列表
        List<Invoker<T>> mockInvokers = selectMockInvoker(invocation);
        if (CollectionUtils.isEmpty(mockInvokers)) {
   
   
            // 服务降级 Invoker 列表为空,默认使用 Dubbo 提供的 MockInvoker 类
            minvoker = (Invoker<T>) new MockInvoker(getUrl(), directory.getInterface());
        } else {
   
   
            minvoker = mockInvokers.get(0);
        }
        try {
   
   
            // 服务降级处理
            result = minvoker.invoke(invocation);
        } catch (RpcException me) {
   
   
            if (me.isBiz()) {
   
   
                result = AsyncRpcResult.newDefaultAsyncResult(me.getCause(), invocation);
            } else {
   
   
                throw new RpcException
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值