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

·  阅读 756

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

1. Sentinel简介

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

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

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

名词解释

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

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

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

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

2. Sentinel和同类产品对比

Sentinel、Hystrix、Resilience4j的异同

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

3. 下载和运行

github地址

github.com/alibaba/Sen…

可以直接下载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的函数移到单独类中是可行的。

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改