【微服务专题】深入理解与实践微服务架构(十七)之自定义Sentinel全局处理异常

2,537 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第6天,点击查看活动详情

自定义Sentinel全局异常处理类

Sentinel有两种方式实现自定义异常处理逻辑:

  • 添加@ResourceSentinel注解(细粒度,每一个接口对应一个异常处理类)
  • 实现BlockRequestHandler接口(粗粒度,但简单省事,并且为全局处理)

一般对于复杂的系统要使用简单的方法实现,因此推荐次采用第二种全局异常处理类的方式。特殊的接口可以使用 @ResourceSentinel进行错误逻辑处理,但默认全局还是使用一种异常处理器比较好。

1. 添加Sentinel全局异常配置类

但每一个接口声明@ResourceSentinel 注解太麻烦了,并且Sentinel限流后返回的提示不太友好了,我们可以自定义Sentinel的全局异常处理类:

package com.deepinsea.common.handler;
​
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.alibaba.fastjson.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
/**
 * Created by deepinsea on 2022/7/7.
 * sentinel限流、降级和熔断全局异常处理类
 */
@Configuration
public class SentinelExceptionHandler implements BlockRequestHandler {
​
    @Override
    public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
        JSONObject resultObj = new JSONObject();
        if (throwable instanceof FlowException) {
            resultObj.put("code", 100);
            resultObj.put("msg", "接口限流");
        }
        if (throwable instanceof DegradeException) {
            resultObj.put("code", 101);
            resultObj.put("msg", "服务降级");
        }
        if (throwable instanceof ParamFlowException) {
            resultObj.put("code", 102);
            resultObj.put("msg", "热点参数限流");
        }
        if (throwable instanceof SystemBlockException) {
            resultObj.put("code", 103);
            resultObj.put("msg", "触发系统保护规则");
        }
        if (throwable instanceof AuthorityException) {
            resultObj.put("code", 104);
            resultObj.put("msg", "授权规则不通过");
        }
        return ServerResponse.status(HttpStatus.BAD_GATEWAY)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(resultObj));
    }
}

注意:使用BlockException统一异常处理时,不能添加@SentinelResource

在SentinelController中,我们使用之前创建过的limit限流接口,用于测试自定义异常处理限流后的逻辑:

package com.deepinsea.controller;
​
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
​
/**
 * Created by deepinsea on 2022/7/4.
 */
@RestController
@RequestMapping("/sentinel")
public class SentinelController {
​
    @GetMapping("/hello")
    public String hello() {
        return "hi, this is service-sentinel!";
    }
​
    @PostMapping("/test")
    public String test() {
        return "hi, this is service-sentinel[POST]!";
    }
​
    //测试流控规则
    @GetMapping("/limit")
    public String limit() { //路由id限流、自定义异常处理
        return "hi, this is service-sentinel-limit test!";
    }
​
    //测试降级规则
    @GetMapping("/degrade")
    public String degrade(){
        return "hi, this is service-sentinel-degrade test!";
    }
​
    //熔断测试
    @GetMapping("/fusing")
    public String fusing(){
        return "hi, this is service-sentinel-fusing test!";
    }
}

重启gateway,这时我们发现Sentinel配置会全部重置,是因为我们没有配置Sentinel持久化的问题

注意:sentinel的配置默认不会保存,重启gateway服务后sentinel规则配置会丢失,因此需要持久化保存配置。

因为我们在重启服务后配置重置了,因此需要重新添加限流规则:

image-20220707030930878

2. 测试全局异常处理

我们使用curl命令,测试sentinel限流、降级和熔断全局异常处理类的处理异常效果:

C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
hi, this is service-sentinel-limit test!
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}
C:\Users\deepinsea>curl http://localhost:9080/service-sentinel/sentinel/limit
{"msg":"热点参数限流","code":102}

可以看到,我们自定义的异常处理类生效了,限流规则触发后返回了自定义的错误信息。

并且,我们通过单位时间内正常请求和拒绝请求的比例也可以验证,是在触发Sentinel限流规则后然后自定义异常处理的。

image-20220707035155546

自定义Sentinel全局异常类功能测试成功!

3. 使用yml文件配置默认全局异常处理

在yml配置中添加如下配置

    datasource:      
      # 限流返回的响应
      scg:
        order: -2147483648 # 过滤器顺序,默认为 -2147483648 最高优先级
        # fallback模式,目前有三种:response、redirect、空(可以实现对 fallback 的自定义处理逻辑)
        fallback:
          # 第一种:response返回文字提示信息
          mode: response
          response-status: 503 #响应状态码(默认为429)
          response-body: 访问过于频繁,请稍后重试! #This request is blocked by service-sentinel.
          content-type: application/json #内容类型(默认为 application/json)
#          # 第二种:redirect重定向跳转uri
#          mode: redirect
#          redirect: http://www.baidu.com #跳转的URL(需对应redirect模式)

4. 测试全局异常处理

重启项目,然后在网页访问 http://localhost:9080/service-sentinel/sentinel/hello

image-20220713234315103

可以看到,第一次访问成功,返回信息和状态码都是正常的。

image-20220713234346714

但是后面快速连续的访问后,触发了限流规则,所以sentinel返回的信息也变成了错误的提示信息并且HTTP状态码也变为了503。

同理,redirect模式触发限流规则后,会直接重定向到配置的url地址

测试成功,基于yml的全局配置成功生效!

欢迎点赞,谢谢大佬了ヾ(◍°∇°◍)ノ゙