本文介绍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逻辑";
}
执行结果:接口被限流时,成功进入限流降级方法;接口出现业务异常时,会抛到外层;
调用抛出异常
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