Spring Cloud Alibaba 之熔断和限流 Sentinel

777 阅读12分钟

一、概述

二、安装

  • Sentinel分为两个部分前台(dashboard)、后台(在服务运行时环境中)。通过前台对sentinel进行配置,后台执行。
  • Dashboard可以直接通过下载jar包运行下载地址。下载之后,直接运行即可java -jar sentinel-dashboard-1.7.0.jar
  • 如果想更方便,也可以直接用docker:docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard:1.7.0
  • 有的前台端口是8080,也有的是8858,具体还要看实际情况。可以直接在浏览器上打开前台页面http://localhost:8080。访问账号密码均是sentinel。进入控制台表示成功了。 image.png

三、初始化工程

1. 导包

  • spring-cloud-starter-alibaba-sentinel即sentinel后台的包,还导入了一个用于持久化的sentinel-datasource-nacos
<dependencies>
    <!--SpringCloud ailibaba nacos -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
    <dependency>
        <groupId>com.alibaba.csp</groupId>
        <artifactId>sentinel-datasource-nacos</artifactId>
    </dependency>
    <!--SpringCloud ailibaba sentinel -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <!--openfeign-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件+actuator -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.6.3</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 修改配置文件

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        # 配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        # 一个用于和Sentinel DashBoard交互的HTTP Server端口号
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

3. 主启动类

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {
  public static void main(String[] args) {
    SpringApplication.run(MainApp8401.class, args);
  }
}

4. 业务类

@RestController
public class FlowLimitController {
  @GetMapping("/testA")
  public String testA() {
    return "------testA";
  }

  @GetMapping("/testB")
  public String testB() {
    return "------testB";
  }
}

5. 开启

  • 可以开启nacos、sentinel和微服务了
  • 注意,sentinel采用懒加载的机制,因此一开始打开sentinel的时候什么服务都没有,对微服务点击几次请求之后,sentinel就会显示一些内容 image.png
  • 点击簇点链路,就可以查看当前服务的所有簇点,并对其进行一些操作。 image.png

四、流控规则

  • 官方文档
  • 流控,即流量控制。可以控制服务在单位时间内,可以处理的服务请求数或者处理线程数,多出来的请求会被拦截,这样就有效地保护了服务器,免于由于压力而造成的问题。 image.png image.png image.png
  • 可以通过以上方法,添加针对某个资源(可以是某个被请求的路径,也可以是某个被请求的资源通过@SentinelResource标注的方法)的流控规则

1. 流控模式

  • 从流控模式来分,可以选择直接、关联、链路。
  • 直接,即直接针对该资源进行监视,如果出问题了,对该资源进行操作
  • 关联,即指定一个规则,将本资源与另一个资源进行绑定,如果那个资源出现了问题,则控制本资源不能使用。(比如说,如果支付接口超过指定流量数了,就禁用订单接口,防止有更多的支付请求进来)
  • 链路,即设置入口资源以及出口资源,从入口资源到出口资源的整条链路上的请求都会受到约束,当超过规则之后,就无法被访问了。参考

1.1 直接

image.png

  • 表示针对/testA这个请求,1s内只能接收一个请求,超过了就会被拦截,显示Blocked by Sentinel (flow limiting)
  • 测试,快速点击http://localhost:8401/testA,点击第一次的时候可以,第二次的时候就开始失败。

1.2 关联

image.png

  • 表示针对资源/testA,如果/testB的1s内的请求数超过1次,/testA就无法访问了。
  • 测试,通过postman向/testB1s内连续发送多次请求,然后浏览器向/testA发送请求,发现被拦截了。

1.3 链路

  • 本人尝试过,但是貌似链路流控不起效,在这就不演示了。具体原因和解决方法可以看这个兄弟
  • 简单来说,就是如果某个资源resource被调用超过了阈值,那资源名到resource这条链路的所有调用都被拦截了。 image.png

2. 流控效果

  • 流控效果分为快速失败、WarmUp、排队等待三种。

2.1 快速失败

  • 就是默认的方法,直接返回一个字符串Blocked by Sentinel (flow limiting)

2.2 Warm Up

  • 官方介绍
  • 简单来说,就是开始的时候,将请求阈值限制到设定阈值的1/3(默认),然后慢慢放开,让阈值上升到设定的阈值。
  • 目的是,防止一开始大量的请求进来,后面的数据库还没开启连接,直接将系统冲垮。
  • 个人觉得,还有的原因是,一开始NoSql之类的缓存里面还没有内容,如果直接全部放进来,那数据库的压力就会很大,因此通过预热的手段,一开始先限制住请求进来的数量,等缓存一部分数据之后,系统处理请求的能力提升之后,再放开。 image.png
  • 上面的配置,限制单机阈值为9,默认的ColdFactor为3,因此一开始的阈值是3,然后慢慢升上去。预热时长为10s,以秒做单位。 image.png

2.3 排队等待

  • 简单来说,就是搞一个队列。一开始没办法处理这么多的时候,不直接拒绝,而是让这些请求一个一个排好队。只要排队没超过超时时间,请求就会在队列里面排队等待处理。
  • 这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。 image.png
  • 设定了一个规则,在20000ms以内都不算超时,每秒钟只能处理10个请求,后面来的一个一个等待。 image.png
  • 通过测试软件,在1s内发送了100个请求过来,由图可见,服务器正在10个10个地处理请求。非常优雅哈哈。

3. 阈值类型

  • 上面采用的都是默认的QPS作为阈值类型,但是还有其他的应用场景,适合使用控制服务使用的线程数量,防止线程资源被某个服务全部占用。
  • 并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。 image.png

五、降级规则

  • 官方介绍
  • 可以类比Hystrix的熔断、降级
  • Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
  • 当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
  • Sentinel是没有半开状态的,半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。

1. 降级策略

image.png

  • 在新增降级规则的时候,需要制定降级的策略。降级策略一共有三种:RT、异常比例、异常数
  1. RT(平均响应时间,秒级):平均响应时间 超出阈值 且 在时间窗口内通过的请求>=5,两个条件同时满足后触发降级。窗口期过后关闭断路器。RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
  2. 异常比例(秒级)。QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
  3. 异常数(分钟级)。异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

1.1 RT

  • 基本过程如下,直接配置即可,在此不再赘述。 image.png

1.2 异常比例

image.png

1.3 异常数

image.png

  • 值得一提的是,异常数降级策略的时间窗口时长需要设置在1min及以上,就算设置的时长不超过1min,窗口也会默认为1min。

六、热点key限流

image.png

  • 官方介绍
  • 即对某个经常被访问的数据进行限流或其他操作。
  • 从实现上来说,就是在url中可能会携带一些参数,我们可以针对携带了这些参数的url进行限流。如果某个url携带了某个参数就限制它的QPS

1. 普通版本

  • 首先,写一个请求处理的方法,可以看出方法有两个参数,第0个位置的参数是p1,第1个位置的参数是p1
  @GetMapping("/testHotKey")
  public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                           @RequestParam(value = "p2", required = false) String p2) {
    return "testHotKey";
  }
  • 如果要针对p1参数进行限流,就可以这样配置。这里限制携带第0个参数的url,1s只能发送一次请求。 image.png
  • 因此,对于以上配置来说http://localhost:8401/testHotKey?p1=abchttp://localhost:8401/testHotKey?p1=abc&p2=33均会收到限制。但是http://localhost:8401/testHotKey?p2=abc并不会收到限制。

2. 参数例外项

  • 除此之外,还可以针对该参数,设定例外值。 image.png
  • 表示当参数为p1=200时,限制的阈值可以提升到200,其他情况阈值被限制在1
  • 友情提示,在添加的时候,记得按一下添加按钮才算正式添加成功。

七、系统规则

image.png

  • 简单来说,之前的规则都是针对某个资源的,粒度比较小,而这个规则是一个全局的,粒度比较大。通过统计整个系统的数据,来对整个系统的请求进行限制。
  • 可以制定以下规则 image.png
  • 因为规则粒度比较大,不建议使用,在此不再赘述。

八、@SentinelResource

  • 简单来说,这个注解的功能就是声明该方法是sentinel的一个资源。并且可以将一些handler、fallback方法与之进行绑定。
  • 定位微服务中的资源,有两种方式,按照资源名称,按照URL地址。资源名称的方式,需要在@SentinelResource注解中给value()变量赋值,URL地址就是映射地址,无需声明。
  • 值得一提的是,如果要自定义处理拦截的方法,需要在@SentinelResource中声明,并且与@SentinelResource里面声明的资源名绑定,而不会自动和对应的URL绑定。使用的时候需要多加小心。

1. BlockException

  • 以下这种方法,就实现了自定义blockHandler。值得一提的是,参数里面必须接收BlockException
  @GetMapping("/testHandler")
  @SentinelResource(value = "testHandler", blockHandler = "blockHandler")
  public CommonResult<Object> testHandler() {
    return new CommonResult<>(CommonResult.SUCCESS, "testHandler Success");
  }
  
  public CommonResult<Object> blockHandler(BlockException exception) {
    return new CommonResult<>(CommonResult.FAIlURE, "blockHandler Failure");
  }
  • 也可以将一个类里面的方法作为handler
public class BlockHandler {
  public static CommonResult<Object> handleException1(BlockException exception) {
    return new CommonResult<>(CommonResult.FAIlURE, "handleException1: 这是一个失败的处理");
  }
  @GetMapping("/testHandler")
  @SentinelResource(value = "testHandler", blockHandler = "handleException1", blockHandlerClass = BlockHandler.class)
  public CommonResult<Object> testHandler() {
    return new CommonResult<>(CommonResult.SUCCESS, "testHandler Success");
  }

2. fallback

  • 记得参数列表要一样
  @SentinelResource(value = "testFallback", fallback = "exceptionFallback")
  @GetMapping("/testFallback/{i}")
  public CommonResult<Object> testFallback(@PathVariable("i") Integer i) {
    log.info("接收到数据 i = " + i);
    System.out.println(10 / i);
    return new CommonResult<>(CommonResult.SUCCESS, "testFallback: 接收到数据了", i);
  }
  
  public CommonResult<Object> exceptionFallback(Integer i) {
    log.info("接收到数据 i = " + i);
    return new CommonResult<>(CommonResult.FAIlURE, "exceptionFallback: 接收到数据了", i);
  }
  • 也可以和上面的blockHandler一样,引入其他类的方法,在此不再演示。
  • 也可以无视一些异常,在出现异常的时候,不予以处理
  @SentinelResource(value = "testFallback", fallback = "exceptionFallback", exceptionsToIgnore = ArithmeticException.class)
  @GetMapping("/testFallback/{i}")
  public CommonResult<Object> testFallback(@PathVariable("i") Integer i) {
    log.info("接收到数据 i = " + i);
    System.out.println(10 / i);
    return new CommonResult<>(CommonResult.SUCCESS, "testFallback: 接收到数据了", i);
  }

九、规则持久化

  • 一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
  • 将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效

1. 导包

  • 需要在包里面加一个nacos作为sentinel的数据库的包
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

2. 配置文件

  • 新增的部分就是datasource的部分。里面包含了nacos的地址、数据信息等。
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        # 一个用于和Sentinel DashBoard交互的HTTP Server端口号
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

management:
  endpoints:
    web:
      exposure:
        include: '*'

3. 添加数据

image.png

  • 在配置中新增一个配置,dataId与配置文件设置的一致。使用json格式表示。
[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]
  • resource:资源名称;
  • limitApp:来源应用;
  • grade:阈值类型,0表示线程数,1表示QPS;
  • count:单机阈值;
  • strategy:流控模式,0表示直接,1表示关联,2表示链路;
  • controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
  • clusterMode:是否集群。

4. 测试

  • 启动微服务之后,可以打开sentinel,发现已经将nacos的数据加入了。如果修改配置,sentinel的规则也会跟着修改。
  • 只要nacos的数据不丢失,sentinel的规则就不会丢失。