前置条件
依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置yml
server:
port: 10001
spring:
application:
name: cloud-consumer-feign
cloud:
nacos:
discovery:
namespace: public
server-addr: localhost:8848
//[0]
sentinel:
transport:
dashboard: localhost:8080
port: 8719
- [0]处 为配置sentinel,dashboard时sentinel后台管理页面地址,port为sentinel api 端口
实践—自定义错误处理
之前接口在限流或者熔断等之后都会返回一个Blocked by sentinel(flow limiting)
。下面就阐述如何自定义返回的内容以便给用户一个友好提示。
@SentinelResource 注解
注意:注解方式埋点不支持 private 方法。
@SentinelResource
用于定义资源,并提供可选的异常处理和 fallback 配置项。@SentinelResource
注解包含以下属性:
value
:资源名称,必需项(不能为空)
entryType
:entry 类型,可选项(默认为EntryType.OUT
)
blockHandler
/blockHandlerClass
:blockHandler
对应处理BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。fallback/fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个
Throwable
类型的参数用于接收对应的异常。- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定
fallbackClass
为对应的类的Class
对象,注意对应的函数必需为 static 函数,否则无法解析。
1.8.0 版本开始,defaultFallback
支持在类级别进行配置。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(
DegradeException
)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException
时只会进入 blockHandler
处理逻辑。若未配置 blockHandler
、fallback
和 defaultFallback
,则被限流降级时会将 BlockException
直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException
)。
-
blockHandler演示
- 代码部分
@GetMapping("/testB") @SentinelResource(value = "testB", blockHandler = "handler") public ResponseEntity<String> testB() { return ResponseEntity.ok().body("test B port:" + port); } public ResponseEntity<String> handler(BlockException exception) { return ResponseEntity.status(444).body(exception.getClass().getCanonicalName()+ " \t 我是一个友好的错误提示,真的 (~ ̄▽ ̄)~ "); }
-
dashboard配置
配置了QPS限流,注意资源名不是 /testB 而是testB,和我们
@SentinelResource
注解中的value值一样。如果此时资源名使用接口url则发生限流之后不会进入我们的blockHadnler方法 -
测试
可以看到触发流控规则之后,返回的不再是默认的错误提示了
-
blockHandlerClass演示
上面的方式会使得错误处理方法和业务代码耦合的太紧密。等日后业务代码多了之后,会变得更加难以管理。这种方式可以实现错误处理与业务分离。
-
代码部分
@GetMapping("/testB") @SentinelResource(value = "testB", blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handler") public ResponseEntity<String> testB() { return ResponseEntity.ok().body("test B port:" + port); }
public class CustomerBlockHandler { // 必须是public public static ResponseEntity<String> handler(BlockException exception) { return ResponseEntity.status(444).body(exception.getClass().getCanonicalName()+ " \t 我是一个友好的错误提示,真的 (~ ̄▽ ̄)~ "); } }
-
测试
-
-
fallback演示
-
代码
@GetMapping("/testC") @SentinelResource(value = "testC", fallback = "fallbackHandler") public ResponseEntity<String> testC(@RequestParam("id") String id) { if ("1".equals(id)) { throw new IllegalArgumentException(); } return ResponseEntity.ok().body("test C port:" + port); } public ResponseEntity<String> fallbackHandler(String id) { return ResponseEntity.status(444).body("我是自定义的异常处理 port:" + port + " id:" + id); }
-
测试
-
-
fallbackClass演示
-
代码部分
@GetMapping("/testD") @SentinelResource(value = "testD", fallbackClass = FallbackConfig.class,fallback = "fallbackHandlerInClass") public ResponseEntity<String> testD(@RequestParam("id") String id) { if ("1".equals(id)) { throw new IllegalArgumentException(); } return ResponseEntity.ok().body("test C port:" + port); }
public class FallbackConfig { public static ResponseEntity<String> fallbackHandlerInClass(String id) { return ResponseEntity.status(444).body("我是自定义的异常处理 id:" + id); } }
-
测试
-
-
fallback,blockhandler同时配置
-
代码部分
@GetMapping("/testE") @SentinelResource(value = "testE", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handler1",fallbackClass = FallbackConfig.class,fallback = "fallbackHandlerInClass") public ResponseEntity<String> testE(@RequestParam("id") String id) { if ("1".equals(id)) { throw new IllegalArgumentException(); } return ResponseEntity.ok().body("test E port:" + port); }
// 规则一场处理类 public class CustomerBlockHandler { public static ResponseEntity<String> handler1(String id, BlockException exception) { return ResponseEntity.status(444).body(exception.getClass().getCanonicalName() + " \t 我是一个友好的错误提示,真的 (~ ̄▽ ̄)~ "); } }
// 异常处理类 public class FallbackConfig { public static ResponseEntity<String> fallbackHandlerInClass(String id) { return ResponseEntity.status(444).body("我是自定义的异常处理 id:" + id); } }
-
-
全局配置处理
有没有感觉这样很烦每个都要处理,那么这就是究极偷懒的方法。
-
代码部分
实现
BlockExceptionHandler
再注入springIOC容器即可@GetMapping("/testF") // 没有@SentinelResource,所以资源名就是url public ResponseEntity<String> testF() { throw new IllegalArgumentException(); }
@Configuration public class GlobalBlockHandler implements BlockExceptionHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception { if (e instanceof FlowException) { //流控规则异常 response.setStatus(445); PrintWriter out = response.getWriter(); out.print("i am custom BlockException handler"); out.flush(); out.close(); } if (e instanceof AuthorityException) { //授权规则异常 System.out.println("i am flow AuthorityException"); } if (e instanceof DegradeException) { // 熔断规则 System.out.println("i am flow DegradeException"); } if (e instanceof ParamFlowException) { // 热点规则 System.out.println("i am flow ParamFlowException"); } if (e instanceof SystemBlockException) { // 系统规则 System.out.println("i am flow SystemBlockException"); } } }
-
dashboard配置
-
测试
-
-
总结
-
blockHandler/blockHandler负责配置违规
-
fallback/fallbackClass负责业务异常
-
如果两个都配置了,则被限流降级而抛出的BlockException时只会进入blockHandler处理逻辑
-
本质上违反了配置规则就会抛出
BlockException
,而此时若配置了blockHandler
就会调用blockhandler
指定的方法处理这个异常的。若没有配置blockhandler
则所有异常则归fallback
处理 -
实在不想写可以配置全局异常处理。
-
exceptionsToIgnore里的异常不会处理
@GetMapping("/testE") @SentinelResource(value = "testE",fallbackClass=FallbackConfig.class,fallback"fallbackHandlerInClass", exceptionsToIgnore = {IllegalArgumentException.class}) public ResponseEntity<String> testE(@RequestParam("id") String id) { if ("1".equals(id)) { throw new IllegalArgumentException(); } return ResponseEntity.ok().body("test E port:" + port); }
-