Spring Cloud Alibaba(七)——Sentinel流量控制框架

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. Sentinel简介

Sentinel被称为分布式系统的流量防卫兵,是阿里开源流控框架,从服务限流、降级、熔断等多个维度保护服务,Sentinel提供了简洁易用的控制台,可以看到接入应用的秒级数据,并且可以在控制台设置一些规则保护应用,它比Hystrix支持的范围广,如Spring Cloud、Dubbo、gRPC都可以整合。

资源是Sentinel最关键的概念,遵循Sentinel API的开发规范定义资源,就能将应用保护起来。

而规则可以通过控制面板配置,也可以和资源联合起来,规则可以在控制台修改并且即时生效。

名词解释

  • 限流:不能让流量一窝蜂的进来,否则可能会冲垮系统,需要限载流量,一般采用排队的方式有序进行

    • 对应生活中的小例子:比如在一个景区,一般是不会让所有人在同一时间进去的,会限制人数,排队进入,让景区内人数在一个可控范围,因为景区的基础设施服务不了那么多人。
  • 降级:即使在系统出故障的情况下,也要尽可能的提供服务,在可用和不可用之间找一个平衡点,比如返回友好提示等。

    • 例如现在稍有规模的电商系统,为了给用户提供个性化服务,一般都有推荐系统,假设现在推荐系统宕机了,不应该在推荐商品一栏不给用户展示商品,反而可以降低一点要求,保证给用户看到的是友好界面,给用户返回一些准备好的静态数据。
  • 熔断:直接拒绝访问,然后返回友好提示,一般是根据请求失败率或请求响应时间做熔断。

    • 熔断好比家里的保险盒,当线路过热时,就会跳闸,以免烧坏电路。

2. Sentinel和同类产品对比

Sentinel、Hystrix、Resilience4j的异同

基础特性 Sentinel Hystrix Resilience4j
限流 QPS、线程数、调用关系 有限的支持 Rate LImiter
注解的支持 支持 支持 支持
动态规则配置 支持多种数据源 支持多种数据源 有限支持
实时统计信息 滑动窗口 滑动窗口 Ring Bit Buffer
熔断降级策略 平均响应时间、异常比例、异常数 异常比例 平均响应时间、异常比例
控制台 可配置各种规则,接口调用的秒级信息,机器发现等 简单监控 不提供控制台,可对接其它监控平台
流量整形 支持预热、排队模式 不支持 简单的Rate Limiter模式
系统自适应限流 支持 不支持 不支持
扩展性 多个扩展点 插件的形式 接口的形式
常用适配框架 Servlet、Spring Cloud、Dubbo、gRPC等 Servlet、Spring Cloud、Netflix Spring Boot、Spring Cloud

3. 下载和运行

github地址

github.com/alibaba/Sen…

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

可以直接下载jar包运行jar包,也可以下载源码编译运行

因为它是springboot项目,下载jar包后直接运行jar包即可

java -jar sentinel-dashboard-1.8.3.jar

默认端口8080,如果需要修改,可以增加-Dserver.port参数,启动命令修改为java -jar -Dserver.port=9000 sentinel-dashboard-1.8.3.jar ,即可将程序端口改为9000

默认账号sentinel,默认密码sentinel,登录后页面是空白的,是因为sentinel采用懒加载的方式,只有证正使用它,功能才会展示出来

4. 项目集成Sentinel

4.1 创建提供者服务

pom.xml

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

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

        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- 健康监控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 服务注册/服务发现需要引入的 -->
        <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>
    </dependencies>

bootstrap.yml

server:
  port: 8082 #程序端口号
spring:
  application:
    name: provider # 应用名称
  cloud:
    sentinel:
      transport:
        port: 8719 # 启动HTTP Server,并且该服务与Sentinel仪表进行交互,是Sentinel仪表盘可以控制应用,如被占用,则从8719依次+1扫描
        dashboard: 127.0.0.1:8080 # 指定仪表盘地址
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # nacos服务注册、发现地址
      config:
        server-addr: 127.0.0.1:8848 # nacos配置中心地址
        file-extension: yml # 指定配置内容的数据格式
management:
  endpoints:
    web:
      exposure:
        include: '*' # 公开所有端点

写一个简单的Controller给消费者调用

@RestController
public class TestController {
    
    @GetMapping("/test")
    public String test(){
        return "provider test方法" + RandomUtils.nextInt(0,100);
    }
}

4.2 创建消费端服务

pom.xml

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

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

        <!-- 热部署 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- 健康监控 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- 服务注册/服务发现需要引入的 -->
        <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>
    </dependencies>

bootstrap.yml

server:
  port: 8081 #程序端口号
spring:
  application:
    name: consumer # 应用名称
  cloud:
    sentinel:
      transport:
        port: 8719 # 启动HTTP Server,并且该服务与Sentinel仪表进行交互,是Sentinel仪表盘可以控制应用,如被占用,则从8719依次+1扫描
        dashboard: 127.0.0.1:8080 # 指定仪表盘地址
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # nacos服务注册、发现地址
      config:
        server-addr: 127.0.0.1:8848 # nacos配置中心地址
        file-extension: yml # 指定配置内容的数据格式
management:
  endpoints:
    web:
      exposure:
        include: '*' # 公开所有端点

新建一个controller去调用服务提供者

@RestController
public class TestController {
    // 这里的服务地址填写注册到Nacos的应用名称
    private final String SERVER_URL = "http://provider";

    @Resource
    private RestTemplate restTemplate;

    /**
     * 调用提供者test接口
     * @return
     */
    @GetMapping("/test")
    public String test(){
        // 调用提供者的 /test 接口
        return restTemplate.getForObject(SERVER_URL+"/test",String.class);
    }

    /**
     *  sentinel测试组件
     * @return
     */
    @GetMapping("/sentinelTest")
    public String sentinelTest(){
        return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
    }
}

4.3 使用RestTemplate+Ribbon远程调用

增加config配置类,实例化RestTemplate对象

@Configuration
public class GenericConfiguration {
    @LoadBalanced//标记此注解后,RestTemplate就具有了客户端负载均衡的能力
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

在没有加任何Sentinel规则的情况下接口正常调用

C:\Users\81950>curl http://localhost:8081/test
provider test方法13
C:\Users\81950>curl http://localhost:8081/test
provider test方法47
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 8873
C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 3357

4.4 使用Sentinel常用规则

调用过接口之后,Sentinel控制台出现了很多共功能

在使用Sentinel功能前,需要准备一个模拟发起请求的工具,测试限流这些规则效果用curl手动发起请求不太现实,这里将用到JMeter发起大规模请求。

下载地址

jmeter.apache.org/download_jm…

4.4.1 流控规则

1、流控规则主要是设置QPS或线程数等参数保护应用,针对某个资源的设置。添加规则前需要先调用接口。

一些流控关键词的含义:

  • 资源名:资源名称,唯一即可

  • 针对来源:对调用者进行限流,填写应用名称(一般是spring.application.name的值),指定对哪个服务进行限流(默认default是全部限制)

  • 阈值类型

    • QPS:每秒能接受的请求数
    • 线程数:能使用的业务线程数
  • 流控模式

    • 直接:达到条件后,直接执行某个流控效果
    • 关联:如果关联资源达到条件,就限流自身
    • 链路:记录从入口资源的流量,达到条件也只限流入口资源
  • 流控效果

    • 快速失败:达到条件后,直接返回失败的结果
    • Warm Up:预热,给一个缓冲时间,初始值是阈值/codeFactor(默认为3),慢慢达到设置的阈值
    • 排队等待:让系统匀速处理请求,而不是一次处理很多,过一会则处于空闲状态。

(1)QPS——直接——快速失败

QPS(Query Per Second)是指每秒可处理的请求数。

流控规则设置

在Sentinel控制台选择“簇点链路”,选择“列表视图”,资源名为/sentinelTest进行流控

阀值类型为QPS,单机阈值为1

即增加了一条直接-快速失败的流控规则

测试

使用JMeter设置线程数为10,发起请求

从结果可以看出,超过限制QPS超过阈值1就被接管了,直接返回失败结果Blocked by Sentinel (flow limiting)

(2)QPS——直接——Warm Up

Warm Up是预热,即初始i请求QPS等于阈值/coldFactor,cold-Factor的默认值为3,经过预热时长1秒后单机阈值为100

流控规则设置

编辑流控规则,流控效果选择Warm Up,预热时长1秒,单机阈值100

测试

因为Jmeter线程数10,只循环依次,可能执行完还不到1秒,所以把循环次数改为10,对比1秒前和1秒后的效果。

虽然预热前1秒几乎请求都是失败的,但过了1秒后大部分都是请求成功的,流量缓慢增加,给冷系统一个缓冲时间,避免一下子把系统给压垮。

(3)QPS——直接——排队等待

让请求以均匀的速度通过,如果请求超过了阈值就等待,如果等待超时就返回失败

编辑流控规则,单机阈值依旧为1,超时时间15000毫秒,JMeter循环执行次数1

QPS设置为1,在调用过程中几乎是1秒发一个请求,超时时间如果短一些,一定会有很多失败。

(4)QPS——关联——快速失败

如果访问关联接口B到达了阈值,就让接口A返回失败,这种规则适用于资源之间,具有资源争抢或者依赖关系。

增加一个接口sentinelTestB

/**
 *  sentinel测试组件B
 * @return
 */
@GetMapping("/sentinelTestB")
public String sentinelTestB(){
    // 调用提供者的 /test 接口
    return "TestController#sentinelTestB " + RandomUtils.nextInt(0,10000);
}

流控规则设置

修改流控规则,主要改的就是流控效果:关联

测试

把JMeter循环次数设置为永远,JMeter请求sentinelTestB接口,模拟一直超过阈值,然后使用curl命令请求sentinelTest接口,结果如下

C:\Users\81950>curl http://localhost:8081/sentinelTest
Blocked by Sentinel (flow limiting)

关联资源B请求达到阈值,而请求sentinelTest接口直接被限流

(5)线程数——直接

限制处理请求的业务线程数,达到阈值就会限流

流控规则设置

阈值类型选择“并发线程数”,单机阈值1,流控模式“直接”。

测试

JMeter线程数改为10,Ramp-Up时间为0.5,循环次数为10

从结果中看出很多请求被限流

因为设置的阈值很小,所以明显业务线程已经处理不过来了,业务线程正在处理任务的时候再来的请求就被限流了。

4.4.2 熔断规则

Sentinel1.8.0 及以上版本主要有三个策略:慢调用比例,异常比例,异常数。

(1)慢调用比例

选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

熔断规则设置

同样在簇点链路选择列表视图,对资源/sentinelTest,选择熔断,熔断策略为慢比例调用,最大RT为500,比例阈值1,熔断时长10秒,最小请求数5,统计时长1000ms

熔断条件
在1000毫秒,也就是1秒内,如果发送到/sentinelTest的请求数数量大于5,并且在这些请求中,所有请求的响应时长(因为比例与阈值为1,所以是所有的请求响应时长)都大于500毫秒,也就是都大于0.5秒的时候,进入熔断状态。

模拟耗时操作

修改代码,让线程睡眠1秒

/**
 *  sentinel测试组件
 * @return
 */
@GetMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
    TimeUnit.SECONDS.sleep(1);
    return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}

测试

JMeter设置线程数10,循环次数永远

10个线程,在一秒的时间内全部发送完, 又因为接口响应时长为暂停1秒,所以响应一个请求的时长都大于一秒,所以在统计时长1000毫秒也就是1秒内会进入熔断状态

使用curl测试接口,此时已经进入熔断状态

C:\Users\81950>curl http://localhost:8081/sentinelTest
Blocked by Sentinel (flow limiting)

停止JMeter测试,超过熔断时长10m后再使用curl测试,接口正常访问

C:\Users\81950>curl http://localhost:8081/sentinelTest
TestController#sentinelTest 5483

(2)异常比例

当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

熔断规则设置

在1000毫秒(1秒)中当最少接收5个请求得情况下,错误率达到20%,接下来10秒开启熔断。

为了测试出效果,将接口故意使程序报错

/**
 *  sentinel测试组件
 * @return
 */
@GetMapping("/sentinelTest")
public String sentinelTest() throws InterruptedException {
    // TimeUnit.SECONDS.sleep(1);
    int i = 1/0; // 除数不能为0,此处必然会报错
    return "TestController#sentinelTest " + RandomUtils.nextInt(0,10000);
}

测试

JMeter设置1个线程1秒内执行10次,查看结果

当异常总数比例超过设定的比例时,进入熔断状态,要等过了时间窗口期10秒才能恢复

(3)异常数

当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

熔断规则设置

熔断策略为异常数,异常数2,熔断时长5秒,最小请求数5,表示如果1秒内异常数达到2个,则该接口再接下来的5秒钟进入熔断状态

测试

JMeter使用1个线程1秒内执行10次,结果如下

异常数到达两个之后进入熔断状态,要过了熔断时长5秒后才能恢复。

4.4.3 系统规则

4.4.1流控规则和4.4.2降级规则是针对某个资源而言的,而系统规则是针对整个应用的,当前服务都会应用这个系统规则,相对来说就更加的粗粒度了,属于应用级别的入口流量控制。

(1)LOAD

负载,当系统负载超过设定值,且并发线程数超过预估系统容量就会触发保护机制。

此规则仅对Linux机器生效,因此将项目修改项目配置文件后吗,打包放到服务器上运行。

系统规则设置

在系统规则界面,新增系统保护规则,阈值类型为LOAD,阈值为1

测试

使用JMeter使用100个线程调用接口,循环次数设置为永远

(2)RT

整个应用上所有资源平均的响应时间,而不是某个固定资源

(3)线程数

设定真个应用所能使用的业务线程数阈值,而不是固定某个资源

(4)入口QPS

整个应用所使用的是每秒处理的请求数,而不是固定某个资源

(5)CPU使用率

应用占用CPU的百分比,同样仅对Linux机器生效

都是超过设定值则被阻断,不再演示

4.4.4 授权规则

授权规则是根据调用方判断资源的请求是否被允许,Sentinel控制台提供了黑白名单的授权类型,如果配置了白名单,表示只允许白名单的应用调用该资源时通过,如果配置了黑名单,表示黑名单的应用调用该资源不通过,其余的均能够通过。

授权规则的配置需要服务提供者配置授权规则

创建CustomRequestOriginParser类来实现RequestOriginParser接口,用于获取参数,然后将返回结果值交给Sentinel流控匹配处理

@Component
public class CustomRequestOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        // 区分来源:本质通过request域获取来源标识
        String origin = httpServletRequest.getParameter("origin");
        // 授权规则必须判断
        if(StringUtils.isEmpty(origin)){
            throw new RuntimeException("origin不能为空");
        }
        // 最后返回origin交给sentinel流控匹配处理
        return origin;
    }
}

新增授权规则

对/sentinelTestC资源新增授权,流控应用填写app,授权类型黑名单

分别调用接口

C:\Users\81950>curl http://127.0.0.1:8081/sentinelTestC?origin=app
Blocked by Sentinel (flow limiting)
C:\Users\81950>curl http://127.0.0.1:8081/sentinelTestC?origin=pc
TestController#sentinelTestC 9667

配置了黑名单,表示黑名单的应用调用该资源不通过,其余的均能够通过


给提供者增加CustomRequestOriginParser类,并通过远程调用访问test接口,对provider的/test资源新增授权规则

修改consumer的/test接口

/**
 * 调用提供者test接口
 * @return
 */
@GetMapping("/test")
public String test(){
    // 调用提供者的 /test 接口
    return restTemplate.getForObject(SERVER_URL+"/test?origin=consumer",String.class);
}

测试消费方的test接口,即携带origin远程调用提供方的test接口,成功

C:\Users\81950>curl http://127.0.0.1:8081/test
provider test方法11

4.5 使用@SentinelResource注解

@SentinelResource注解根据实际情况实现定制化功能,对应用的保护更加细粒度。

之前到达一定阈值时,sentinel给的提示时Blocked by Sentinel (flow limiting),这显然是不友好的,所以需要自定义错误页面,又或者只针对某个参数限流等,实现更精细化的控制。

4.5.1 blockHandler属性——负责响应控制面板配置

blockHandler主要是针对达到控制面板的限制条件做一个自定义的“兜底”操作,而不是返回默认的Blocked by Sentinel (flow limiting)。

添加一个接口/blockHandlerTest,资源名称为blockHandlerTest,如果违反Sentinel控制台的规则,就会自动进入blockHandlerTestHandler

@RestController
public class HandlerTestController {
  @GetMapping("/blockHandlerTest")
  // 资源名称为blockHandlerTest 违反规则后的兜底方法是blockHandlerTestHandler
  @SentinelResource(value = "blockHandlerTest", blockHandler = "blockHandlerTestHandler")
  public String blockHandlerTest(String params) {
    return "HandlerTestController#blockHandlerTest " + RandomUtils.nextInt(0, 1000);
  }

  /**
   * 接口blockHandlerTest的兜底方法
   *
   * @param params
   * @param blockException
   * @return
   */
  public String blockHandlerTestHandler(String params, BlockException blockException) {
    return "HandlerTestController#blockHandlerTestHandler "
        + RandomUtils.nextInt(1, 1000)
        + " "
        + blockException.getMessage();
  }
}

blockHandler指定的兜底方法的返回值类型要和原方法一致,并且该方法除了原有的参数(方法签名),还要新增BlockExceptionca参数

新增流控规则

测试

JMeter使用10个线程循环10次

从结果可与i看到,发起第二个请求时,QPS已经达到1个了,然后进入到自定义的blockHandler方法

4.5.2 热点规则

热点就是在一定时期内访问特别频繁,如果访问某个资源很频繁,有可能只是某些参数访问量很大。

Sentinel不仅支持以资源为粒度的限制,还可以更细化,针对资源下的参数进行限制,其实就是对这个接口的请求参数设置限定。

@RestController
public class HotspotTestController {
  /**
   * 热点参数测试接口
   *
   * @return
   */
  @GetMapping("/testHotKeyA")
  @SentinelResource(value = "testHotKeyA", blockHandler = "blockTestHotKeyA")
  public String testHotKeyA(
      @RequestParam(value = "orderId", required = false) String orderId,
      @RequestParam(value = "userId", required = false) String userId) {
    return "HotspotTestController#testHotKeyA " + RandomUtils.nextInt(0, 1000);
  }

  /**
   * 热点参数测试接口testHotKeyA的兜底方法
   * @param orderId
   * @param userId
   * @param blockException
   * @return
   */
  public String blockTestHotKeyA(String orderId, String userId, BlockException blockException) {
    return "HotspotTestController#blockTestHotKeyA "
        + RandomUtils.nextInt(0, 1000)
        + " "
        + blockException.getMessage();
  }
}

新增热点规则

限流模式只能是QPS,参数索引为0,代表是orderId参数,单机阈值为1,统计窗口时长为5秒,也就是在5秒内统计到QPS大于1,接口就会被阻断,进入到自定义的blockTestHotKeyA方法,

测试

JMeter使用10个线程循环10次测试testHotKeyA接口,需要加上orderId参数

从结果可以看出,发起第2个请求时,5秒内统计到的QPS已经大于1了,所以进入自定义的blockTestHotKeyA方法

另外还可以对参数的值单独设置阈值,也可以添加多个值

JMeter中orderId传值111,统计时间5秒内并不会达到限流阀值500,也就不会进入blockHandler方法

4.5.3 fallback属性——负责业务异常

fallback属性的方法是对业务异常的“兜底”,如果业务代码报了异常(除了exceptionToIgnore属性排除掉的异常类型),就会进入fallback属性配置的方法。

增加fallbackTest接口代码,定义fallback的方法fallbackHandler

@RestController
public class FallbackTestController {
  /**
   * 测试fallback的方法
   * @param params
   * @return
   */
  @GetMapping("/fallbackTest")
  // 资源名称为fallbackTest,异常后的兜底方法为fallbackHandler
  @SentinelResource(value = "fallbackTest",fallback = "fallbackHandler")
  public String fallbackTest(String params) {
    int res = 1 / 0; // 此处模拟报错
    return "FallbackTestController#fallbackTest "
            + RandomUtils.nextInt(0, 1000);
  }

  /**
   * 接口fallbackTest的兜底方法
   * @param params
   * @return
   */
  public String fallbackHandler(String params){
    return "FallbackTestController#fallbackHandler "
            + RandomUtils.nextInt(0, 1000);
  }
}

接口测试必然时报错的,因为使用了fallback属性设置了兜底方法,所以一报错就进入fallbackHandlerfa方法

C:\Users\81950>curl http://127.0.0.1:8081/fallbackTest
FallbackTestController#fallbackHandler 704

4.5.4 fallback+blockHandler

增加sentinelUnionTest接口代码,资源名为sentinelUnionTest,并且调用该接口是必然报错的

@RestController
public class UnionTestController {
  /**
   * sentinel组件测试方法fallback和blockHandler联合
   * @return
   */
  @GetMapping("/sentinelUnionTest")
  @SentinelResource(
      value = "sentinelUnionTest",
      blockHandler = "sentinelUnionTestBlockHandler",
      fallback = "sentinelUnionTestFallback")
  public String sentinelUnionTest() {
    int res = 1 / 0; // 此处必然报错
    return "UnionTestController#sentinelUnionTest " 
            + RandomUtils.nextInt(0, 1000);
  }

  /**
   * sentinelUnionTest的兜底方法
   * @return
   */
  public String sentinelUnionTestFallback() {
    return "UnionTestController#sentinelUnionTestFallback " 
            + RandomUtils.nextInt(0, 1000);
  }

  /**
   * sentinelUnionTest的兜底方法
   * @param blockException
   * @return
   */
  public String sentinelUnionTestBlockHandler(BlockException blockException) {
    return "UnionTestController#sentinelUnionTestBlockHandler " 
            + RandomUtils.nextInt(0, 1000)
            + " "
            + blockException.getMessage();
  }
}

控制台新增sentinelUnionTest资源的流控规则

JMeter使用10个线程循环10次调用/sentinelUnionTest接口

从结果可以看出,发出第一个请求时,并没有达到QPS阈值的条件,方法内部报错后,进入了fallback属性定义的兜底方法,发送第二个请求时已经达到了QPS阈值条件,进入了blockHandler属性定义的方法。

4.5.5 exceptionsToIgnore忽略异常

fallback可以针对所有的类型的异常,@SentinelResource注解的exceptionsToIgnore属性表示忽略异常,不会纳入异常统计,也就会跳过fallback属性。

@RestController
public class ExceptionsTestController {
  /**
   * 测试exceptionsToIgnore的方法
   * @return
   */
  @GetMapping("/exceptionsToIgnoreTest")
  @SentinelResource(
      value = "exceptionsToIgnoreTest",  // 资源名称为exceptionsToIgnoreTest
      fallback = "exceptionsToIgnoreTestFallback",  // 异常后的兜底方法为exceptionsToIgnoreTestFallback
      exceptionsToIgnore = {ArithmeticException.class}   // 忽略ArithmeticException异常
  )
  public String exceptionsToIgnoreTest() {
    int res = 1 / 0; // 此处模拟报错
    return "ExceptionsTestController#exceptionsToIgnoreTest "
            + RandomUtils.nextInt(0, 1000);
  }

  /**
   * 接口exceptionsToIgnoreTest的兜底方法
   * @return
   */
  public String exceptionsToIgnoreTestFallback() {
    return "ExceptionsTestController#exceptionsToIgnoreTestFallback "
            + RandomUtils.nextInt(0, 1000);
  }
}

接口测试

1/0必然会抛出ArithmeticException算数异常,方法内异常本应该进入fallback定义的方法,因为设置了exceptionsToIgnores属性,忽略了ArithmeticException异常,所以异常照常返回。

4.5.6 代码优化

4.5.4中的代码中,fallback和blockHandler的处理方法都写在同一个类里,一来这样不符合程序单一的原则,毕竟controller层有很多之外的逻辑,二来别的类也不好复用其方法。

新需求:把具体的处理函数单独放在一个类

@SentinelResource注解还有两个属性,分别是blockHandlerClassfallbackClass,通过源码查看其注释

优化代码

为fallback建立单独的类ExceptionHandler

public class ExceptionHandler {
    /**
     * 接口sentinelUnionTest的兜底方法,放到单独类后必须时static
     * @return
     */
    public static String sentinelUnionTestFallback(){
        return "单独类ExceptionHandler#sentinelUnionTestFallback "
                + RandomUtils.nextInt(0,1000);
    }
}

为blockHandler建立单独的类BlockHandler

public class BlockHandler {
    /**
     * sentinelUnionTest的兜底方法,放到单独类后必须时static
     * @param blockException
     * @return
     */
    public static String sentinelUnionTestBlockHandler(BlockException blockException) {
        return "单独类BlockHandler#sentinelUnionTestBlockHandler "
                + RandomUtils.nextInt(0, 1000)
                + " "
                + blockException.getMessage();
    }
}

修改UnionTestController中的代码,指定fallback和blockHandler的类

@RestController
public class UnionTestOptimizeController {
    /**
     * sentinel组件测试方法fallback和blockHandler联合
     * 指定fallback和blockHandler的类
     * @return
     */
    @GetMapping("/sentinelUnionTestOptimize")
    @SentinelResource(
            value = "sentinelUnionTestOptimize",
            blockHandler = "sentinelUnionTestBlockHandler",
            blockHandlerClass = BlockHandler.class,
            fallback = "sentinelUnionTestFallback",
            fallbackClass = ExceptionHandler.class
    )
    public String sentinelUnionTest() {
        int res = 1 / 0; // 此处必然报错
        return "UnionTestController#sentinelUnionTest "
                + RandomUtils.nextInt(0, 1000);
    }
}

设置sentinelUnionTestOptimize资源流控规则,QPS为1,使用JMeter使用10个线程测试接口

结果与4.5.4中是一致的,在发出第一个请求时,没有达到QPS阈值的条件,方法内部报错,进入了fallback定义的方法,发出第二个请求的时候达到了QPS的阈值条件,直接进入blockHandler定义的方法,证明把fallback和blockHandler的函数移到单独类中是可行的。

猜你喜欢

转载自juejin.im/post/7112327516819292191