Sentinel配置信息持久化与自定义熔断降级处理逻辑

3,396 阅读11分钟

写在前面

在上一篇文章《微服务架构入门之流量控制与熔断降级解决方案----Sentinel初体验》 当中,我简单介绍了Sentinel的使用。但是,遗留下来了一个问题,那就是Sentinel的配置信息还没有做到持久化,服务重启后,配置信息将丢失。在这篇文章当中,我们主要来解决这个问题。

使用Nacos作为Sentinel配置信息数据源

在默认的sentinel当中,配置信息直接存放在本地内存当中,当服务器重启后,配置信息将丢失。并且,由于是细粒度的配置,需要针对每一个资源进行单独的配置操作,也不利于配置信息的重用。

Sentinel动态规则配置

在sentinel的数据源扩展当中,提供了如下两种模式:

  • 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
  • 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。

我们将使用上篇文章学到的Nacos作为Sentinel的配置信息的数据源(推模式),从而实现Sentinel配置信息的持久化和动态配置。

在这之前,如果你还没有学习过Nacos,那你需要先学习nacos的内容,可以阅读我的上一篇文章《微服务架构入门之服务注册发现与配置中心|Spring Cloud Alibaba Nacos》

引入依赖

首先,我们需要在原有的依赖当中,加入sentinel的nacos数据源依赖。

	<dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
        <version>1.5.2</version>
    </dependency>

添加配置

server:
  port: 9004
  tomcat:
    uri-encoding: UTF-8
spring:
  application:
    name: sentinel-demo
  cloud:
    nacos:
      server-addr: 172.17.172.49:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
      datasource:
#        名字随便起
        ds1:
          nacos:
            server-addr: ${spring.cloud.nacos.server-addr}
            dataId: ${spring.application.name}-sentinel
            groupId: DEFAULT_GROUP
#            规则类型,这里是限流规则
            #取值见: org.springframework.cloud.alibaba.sentinel.datasource.RuleType
            rule-type: flow
        ds2:
          nacos:
            server-addr: ${spring.cloud.nacos.server-addr}
            dataId: ${spring.application.name}-sentinel-degrade
            groupId: DEFAULT_GROUP
#            降级规则
            rule-type: degrade
        ds3:
          nacos:
            server-addr: ${spring.cloud.nacos.server-addr}
            dataId: ${spring.application.name}-sentinel-param-flow
            groupId: DEFAULT_GROUP
            #            热点参数限流规则
            rule-type: param-flow
        ds4:
          nacos:
            server-addr: ${spring.cloud.nacos.server-addr}
            dataId: ${spring.application.name}-sentinel-system
            groupId: DEFAULT_GROUP
            #            系统自适应限流
            rule-type: system
        ds5:
          nacos:
            server-addr: ${spring.cloud.nacos.server-addr}
            dataId: ${spring.application.name}-sentinel-authority
            groupId: DEFAULT_GROUP
            #            黑白名单配置
            rule-type: authority

在nacos中添加对应的sentinel规则配置

限流规则

[
    {
        "resource": "/test/info",
        "limitApp": "default",
        "grade": 1,
        "count": 3,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

关于配置信息的说明:

  • resource:资源名,即限流规则的作用对象
  • limitApp:流控针对的调用来源,若为 default 则不区分调用来源
  • grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
  • count:限流阈值
  • strategy:调用关系限流策略,0 表示直接,1 表示关联,2 表示链路;
  • controlBehavior:流量控制效果(0 直接拒绝、1 Warm Up、2 匀速排队)
  • clusterMode:是否为集群模式

熔断降级规则

[
    {
        "resource": "/test/info",
        "grade": 2,
        "count": 3,
        "timeWindow": 10,
        "minRequestAmount": 1,
        "statIntervalMs": 1000
    }
    
]

关于配置信息的说明:

  • resource: 资源名
  • grade: 熔断策略,支持慢调用比例(0)/异常比例(1)/异常数策略(2)
  • count: 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
  • timeWindow: 熔断时长,单位为 s
  • minRequestAmount: 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入)
  • statIntervalMs: 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入)
  • slowRatioThreshold: 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

热点参数限流配置:

[
    {
        "resource": "/test/getInfoById",
        "count": 3,
        "durationInSec": 1,
        "controlBehavior": 0,
        "maxQueueingTimeMs": 3000,
        "paramIdx": 0

    }
    
]

关于配置信息的说明:

  • resource: 资源名
  • count: 限流阈值
  • durationInSec: 统计窗口时间长度(单位为秒),1.6.0 版本开始支持
  • controlBehavior: 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持
  • maxQueueingTimeMs: 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持
  • paramIdx: 参数索引值,从0开始

系统自适应限流配置

[
    {
       "highestSystemLoad": 3
    },
    {
        "highestCpuUsage": 0.8
    },
    {
        "avgRt": 10
    },
    {
        "qps": 20
    },
    {
        "maxThread": 20
    }
   
]

关于配置信息的说明:

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

黑白名单配置:

[
    {
        "resource": "/test/info",
        "limitApp": "sentinel-demo-client",
        "strategy": 0
    }
    

关于配置信息的说明:

  • resource: 资源名
  • limitApp: 对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy: 名单类型,0表示白名单,1表示黑名单。默认为白名单模式。

启动并测试

  1. 启动nacos、sentinel和本模块

  2. 访问接口

  3. 检查Sentinel控制台,看是否收到nacos的推送信息

流控规则

熔断降级规则

热点参数限流规则

系统自适应限流规则

权限规则

可以看到sentinel已经收到了nacos的推送。

关于流量控制规则修改的说明

  1. Sentinel控制台中修改规则:仅存在于服务的内存中,不会修改Nacos中的配置值,重启后恢复原来的值。

  2. Nacos控制台中修改规则:服务的内存中规则会更新,Nacos中持久化规则也会更新,重启后依然保持。

自定义降级消息提示

Sentinel 提供了 @SentinelResource 注解用于定义资源,并提供了 AspectJ 的扩展用于自动定义资源、处理 BlockException

属性说明:

  • 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 函数,否则无法解析。
  • defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore

    里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注意:

  1. @SentinelResource 不支持private方法
  2. 1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理
  3. 如果 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
  4. 若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层UndeclaredThrowableException)。

使用步骤

引入依赖

如果使用sentinel的aop处理,需要引入以下依赖

	<dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-annotation-aspectj</artifactId>
    </dependency>

使用

/**测试接口
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/26 10:37
 */
@RestController
@RequestMapping("/test")
public class DemoController {

    /**
     * 原函数
     * @return map
     */
    @SentinelResource(value = "/test/info",
            blockHandler = "blockExceptionHandler",
            fallback = "infoFallback",
            exceptionsToIgnore = RuntimeException.class
            )
    @GetMapping("/info")
    public Map info() {
        HashMap<String, Object> map = new HashMap<>(3);
        map.put("code",0);
        map.put("msg","Wxb: Java开发实践");
        return map;
    }

    /**处理降级异常
     * fallback函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
     * @return
     */
    public Map infoFallback() {
        HashMap<String, Object> map = new HashMap<>(3);
        map.put("code",501);
        map.put("msg","正忙,请稍后重试");
        return map;
    }

    /**处理限流异常
     *  Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
     * @return
     */
    public Map backExceptionhandler(BlockException e){
        HashMap<String, Object> map = new HashMap<>(3);
        map.put("code",500);
        map.put("msg","系统正忙,请稍后重试");
        return map;
    }
}

启动并测试

在浏览器中快速访问接口

注意:1. 如果异常被@Exceptionhandler拦截处理了,这将使得sentinel配置的降级策略发生错误。被@Exceptionhandler处理的异常将不被统计到统计信息中

##Sentinel对RestTemplate的支持·@SentinelRestTemplate

在上面的例子当中,我们通过配置限流、降级的处理函数,从而使得发生限流、降级时能得到相应的处理。但是,可以看到,限流、降级两个方法的格式有强制的要求,因此,这两个方法很难做到复用。如果想要复用就得统一参数、统一返回结果。或者真对每个sentinel资源都编写对应的限流和降级处理函数,这显然不现实。

sentinel支持在服务上游(服务调用方)定义降级策略,并可以编写统一的降级处理方法。

实现步骤

我们新建一个模块sentinel-demo-client,演示服务调用。

1. 添加依赖

 <dependencies>
        <!--        nacos服务发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--        nacos配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!--        sentinel服务流控-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-annotation-aspectj</artifactId>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

    </dependencies>

2. 编写启动类

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/30 20:22
 */
@SpringBootApplication(scanBasePackages = "pers.lbf.spring.cloud.alibaba.demo.sentinel.demo.client")
@EnableDiscoveryClient
public class ClientApp {

  public static void main(String[] args) {
    SpringApplication.run(ClientApp.class,args);
  }

}

3. 编写配置

server:
  port: 8010
spring:
  application:
    name: sentinel-demo-client
  cloud:
    nacos:
      server-addr: 172.17.172.49:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080

4. 编写限流、降级处理逻辑

/**sentinel限流、降级处理类
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/30 20:25
 */
public class SentinelUtils {


    /**
     * 限流处理函数
     * @param request
     * @param body
     * @param execution
     * @param ex
     * @return
     */
    public static SentinelClientHttpResponse handleException(HttpRequest request,
                                                             byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
       
        System.err.println("fallback: 服务被限流" );
        return new SentinelClientHttpResponse("系统正忙,请稍后重试");
    }

    /**
     * 服务降级处理函数
     * @param request
     * @param body
     * @param execution
     * @param ex
     * @return
     */
      public static SentinelClientHttpResponse defaultFallbackMethod(HttpRequest request,
                                                        byte[] body, ClientHttpRequestExecution execution, BlockException ex) {
          System.err.println("fallback: 服务被降级" );
          return new SentinelClientHttpResponse("系统正忙,请稍后重试1");
      }

}

5 配置RestTemplate

我们需要配置sentinel支持的RestTemplate,从而使得服务提供方发生降级、限流时能得到处理。配置的方式很简单,只需要在注入RestTemplate时标记@SentinelRestTemlpate注解即可。

/**
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/30 20:22
 */
@SpringBootApplication(scanBasePackages = "pers.lbf.spring.cloud.alibaba.demo.sentinel.demo.client")
@EnableDiscoveryClient
public class ClientApp {

  public static void main(String[] args) {
    SpringApplication.run(ClientApp.class,args);
  }

  @Bean
  @SentinelRestTemplate(fallbackClass = SentinelUtils.class,
          fallback = "defaultFallbackMethod",
          blockHandlerClass = SentinelUtils.class,
          blockHandler = "handleException")
  public RestTemplate restTemplate() {
      return new RestTemplate();
  }
}
SentinelRestTemlpate参数说明:
  • fallbackClass: 降级处理函数所在类
  • fallback: 对应的降级处理函数名
  • blockHandlerClass: 限流处理函数所在类
  • blockHandler: 限流处理函数

注意:fallback和blockhandler方法的方法签名和方法返回类型是固定写法,并且这两个方法必须是静态方法

6 编写测试接口,调用服务

/**测试接口,负责演示调用服务
 * @author 赖柄沣 bingfengdev@aliyun.com
 * @version 1.0
 * @date 2020/10/30 20:52
 */
@RestController
public class TestController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/test")
    public ResultVO test() throws Exception{

        ResultVO resultVO = restTemplate.getForObject("http://127.0.0.1:9004/test/info", ResultVO.class);

        System.out.println(Objects.requireNonNull(resultVO).toString());

        return resultVO;
    }
}

7 启动nacos、sentinel和sentinel两个测试模块,添加服务降级配置

添加降级配置:

这里需要注意的是,我们在服务调用方添加对被调用服务的降级配置,这时,达到触发降级(熔断)条件时,将执行降级逻辑,不再进行远程服务调用。并且,熔断时长超过后,再次调用时,一但这次调用还是发生异常的话,将再次触发熔断机制。

测试:

可以看到第二批次访问时,由于第一个请求没有成功,所以再次发生熔断

写在最后

关于sentine入门我们就简单介绍到这里。

如果您觉得这篇文章能给您带来帮助,那么可以点赞鼓励一下。如有错误之处,还请不吝赐教。在此,谢过各位乡亲父老!

在下一篇Spring Cloud Alibaba系列的文章当中,我们将开始学习微服务之服务调用。

**本文代码链接:**github.com/code81192/a…