编码技巧——Sentinel的blockHandler与fallback

本文介绍Sentinel的blockHandler与fallback的区别,背景是:发生限流时,配置的sentinel的blockhandler没有生效而fallback生效了;排查原因,从而给出Sentinel配置异常降级和限流降级的代码写法;

在查看源码前,查阅了相关的技术帖子(1. Sentinel的blockHandler与fallback的区别 2.Sentinel服务熔断[fallBack/blockHandler]),针对同时配置fallback和blockHandler的场景,其中的结论存在不一致,所以决定自己亲手实践下;

1. 未配置fallback和blockHandler

代码如下,仅配置SentinelResource的value,并去sentinel控制台配置单机限流为1;

限流方法:

    /**
     * 测试sentinel的降级方法
     */
    @SentinelResource(value = "testSentinelFallback")
    public String testSentinelFallback() {
        return "返回成功ok";
    }

测试代码:

    @Override
    public String mock() {
        // 异步调用 模拟并发情况
        for (int i = 0; i < 5; i++) {
            CompletableFuture.runAsync(() -> {
                try {
                    final String result = testSentinelService.testSentinelFallback();
                    log.info("调用返回结果 [result={}]", result);
                } catch (Throwable e) {
                    log.warn("调用抛出异常", e);
                }
            });
        }
        return null;
    }

sentinel后台配置:

执行结果:

调用返回结果 [result=返回成功ok]

调用抛出异常
java.lang.reflect.UndeclaredThrowableException: null
Caused by: com.alibaba.csp.sentinel.slots.block.flow.FlowException: null

调用抛出异常
java.lang.reflect.UndeclaredThrowableException: null
Caused by: com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
...

2. 仅配置blockHandler

2.1 配置blockHandler时未带上额外参数BlockException
    @SentinelResource(value = "testSentinelFallback", blockHandler = "myBlockHandler")
    public String testSentinelFallback() {
        return "返回成功ok";
    }

    public String myBlockHandler() {
        return "进入myBlockHandler逻辑";
    }

执行结果:未进入限流降级方法

调用返回结果 [result=返回成功ok]

调用抛出异常
java.lang.reflect.UndeclaredThrowableException: null
Caused by: com.alibaba.csp.sentinel.slots.block.flow.FlowException: null

调用抛出异常
java.lang.reflect.UndeclaredThrowableException: null
Caused by: com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
...
2.2 正确的配置blockHandler
    @SentinelResource(value = "testSentinelFallback", blockHandler = "myBlockHandler")
    public String testSentinelFallback() {
        return "返回成功ok";
    }

    public String myBlockHandler(BlockException blockException) {
        return "进入myBlockHandler逻辑";
    }

执行结果:成功进入限流降级方法

调用返回结果 [result=返回成功ok]

调用返回结果 [result=进入myBlockHandler逻辑]

调用返回结果 [result=进入myBlockHandler逻辑]
...
2.3 blockHandler能捕获业务异常
    @SentinelResource(value = "testSentinelFallback", blockHandler = "myBlockHandler")
    public String testSentinelFallback() {
        if (Boolean.TRUE) {
            throw new BusinessException(FacadeResultCodeEnum.BAD_PARAMS);
        }
        return "返回成功ok";
    }

    public String myBlockHandler(BlockException blockException) {
        return "进入myBlockHandler逻辑";
    }

执行结果:接口被限流时,成功进入限流降级方法;接口出现业务异常时,会抛到外层

扫描二维码关注公众号,回复: 16115040 查看本文章

调用抛出异常
BusinessException

调用返回结果 [result=进入myBlockHandler逻辑]

调用返回结果 [result=进入myBlockHandler逻辑]
...

3. 仅配置fallback

3.1 配置fallback时未带上额外参数Throwable
    @SentinelResource(value = "testSentinelFallback", fallback = "myFallback")
    public String testSentinelFallback() {
        if (Boolean.TRUE) {
            throw new BusinessException(FacadeResultCodeEnum.BAD_PARAMS);
        }
        return "返回成功ok";
    }

    public String myFallback() {
        return "进入myFallback逻辑";
    }

执行结果:接口被限流时,或者接口出现业务异常时,都会进入fallback降级方法

调用返回结果 [result=进入myFallback逻辑]

调用返回结果 [result=进入myFallback逻辑]

调用返回结果 [result=进入myFallback逻辑]
...

3.2 配置fallback时带上额外参数Throwable

    @SentinelResource(value = "testSentinelFallback", fallback = "myFallback")
    public String testSentinelFallback() {
        if (Boolean.TRUE) {
            throw new BusinessException(FacadeResultCodeEnum.BAD_PARAMS);
        }
        return "返回成功ok";
    }

    public String myFallback(Throwable throwable) {
        if (throwable instanceof BlockException) {
            return "进入myFallback逻辑 限流异常";
        }
        return "进入myFallback逻辑 业务异常";
    }

执行结果:接口被限流时,或者接口出现业务异常时,都会进入fallback降级方法;且可以通过异常类型区分出限流异常和业务异常

调用返回结果 [result=进入myFallback逻辑 业务异常]

调用返回结果 [result=进入myFallback逻辑 限流异常]
调用返回结果 [result=进入myFallback逻辑 限流异常]
...

4. 同时配置fallback和blockHandler

    @SentinelResource(value = "testSentinelFallback", fallback = "myFallback", blockHandler = "myBlockHandler")
    public String testSentinelFallback() {
        if (Boolean.TRUE) {
            throw new BusinessException(FacadeResultCodeEnum.BAD_PARAMS);
        }
        return "返回成功ok";
    }

    public String myFallback(Throwable throwable) {
        if (throwable instanceof BlockException) {
            return "进入myFallback逻辑 限流异常";
        }
        return "进入myFallback逻辑 业务异常";
    }

    public String myBlockHandler(BlockException blockException) {
        return "进入myBlockHandler逻辑";
    }

执行结果:若blockHandler和fallback都进行了配置,在未触发限流进入到方法逻辑抛出业务异常时,会进入fallback方法在触发限流时,进入不到方法逻辑,直接抛出BlockException进入blockHandler方法

5. 结论

5.1 异常捕获逻辑

1. blockHandler

  • blockHandler仅处理限流异常;
  • 使用blockHandler时,方法签名参数与原方法一致,且必须要在参数的最后位置补充BlockException参数;
  • 若未补充BlockException参数则不生效;

2. fallback

  • fallback可以处理所有类型异常,包括限流异常和业务异常;
  • 使用fallback时,方法签名参数可以与原方法完全一致,或者也接受在参数的最后位置补充Throwable参数;
  • 通过对Throwable参数的类型区分是限流异常还是其他异常;
  • 当同时生效blockHandler和fallback时,限流异常会优先被blockHandler处理而不再进入fallback逻辑;
5.2 合理的代码写法

(1)同时配置生效blockHandler和fallback分别处理限流异常和业务异常

    @SentinelResource(value = "testSentinelFallback", fallback = "myFallback", blockHandler = "myBlockHandler")
    public String testSentinelFallback() {
        // ...
        return "返回成功ok";
    }

    public String myFallback(Throwable throwable) {
        return "进入myFallback逻辑 业务异常";
    }

    public String myBlockHandler(BlockException blockException) {
        return "进入myBlockHandler逻辑";
    }

(2)仅配置fallback并通过Throwable类型区分限流异常和业务异常

    @SentinelResource(value = "testSentinelFallback", fallback = "myFallback")
    public String testSentinelFallback() {
        // ...
        return "返回成功ok";
    }

    public String myFallback(Throwable throwable) {
        if (throwable instanceof com.alibaba.csp.sentinel.slots.block.flow.FlowException) {
            final FlowRule rule = ((FlowException) throwable).getRule();
            final double count = rule.getCount();
            final String resource = rule.getResource();
            // 打印限流规则信息
            log.warn("testSentinelFallback触发限流降级 [sentinelResource={} QpsLimit={}]]", resource, count);
            return null;
        } else {
            log.warn("testSentinelFallback触发异常降级 抛出异常", throwable);
            throw new RuntimeException("testSentinelFallback业务异常");
        }
    }
5.3 注解参数释义及注意事项

1. @SentinelResource注解参数说明

属性 默认值 说明
blockHandler

用于在抛出限流/熔断/系统保护等异常的降级处理逻辑,blockHandler 针对BlockException类型的异常,优先级比fallback高

blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException;

blockHandler 函数默认需要和原方法在同一个类中;

blockHandlerClass 若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析;
defaultFallback 默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法);

默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理;

若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效

entryType EntryType.OUT 资源调用的流量类型,是入口流量(EntryType.IN)还是出口流量(EntryType.OUT),注意系统保护规则只对 IN 生效
exceptionsToIgnore 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出; 优先级高于exceptionsToTrace
exceptionsToTrace Throwable.class 用于指定哪些异常不被排除掉;如果属于该类型,则会计入异常统计中,也会进入 fallback 逻辑中,不会原样抛出;不建议修改默认值;
fallback

用于在抛出异常的时候提供 fallback 处理逻辑;fallback 针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)

方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常(注意和blockHandler添加的BlockException不一样)

fallbackClass 类似blockHandlerClass参数
resourceType 资源类型,默认0
value 资源名称,必需项

2. 区分限流异常和熔断异常

限流状态会抛异常:FlowException(继承BlockException)

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.alibaba.csp.sentinel.slots.block.flow.FlowException] with root cause
 
com.alibaba.csp.sentinel.slots.block.flow.FlowException: null

熔断状态会抛异常:DegradeException(继承BlockException)

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is com.alibaba.csp.sentinel.slots.block.degrade.DegradeException] with root cause
 
com.alibaba.csp.sentinel.slots.block.degrade.DegradeException: null

参考:annotation-support | Sentinel

猜你喜欢

转载自blog.csdn.net/minghao0508/article/details/132148320