Sentinel实现熔断与限流

官网:https://github.com/alibaba/Sentinel/wiki

是什么?

Sentinel是把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Hystrix的不足

  • 需要自己手工搭建监控平台
  • 没有一套web界面可以给我们进行更加细粒度化的配置。流控、速率控制、服务熔断、服务降级…

    安装

  • https://github.com/alibaba/Sentinel/releases/tag/1.7.1

  • 下载jar 运行java -jar
  • 默认端口8080 --server.port=8081更改端口

image.png

Sentinel分为两个部分

核心库(java客户端):不依赖任何框架/库,能够运行与所有的java运行环境,同时对Dubbo、SpringCloud 等框架也有较好的支持。
控制台(Dashboard):基于SpringBoot开发,打包后可以直接运行。

操作:

  • 启动Nacos8848
  • 新建cloudalibaba-sentinel-service8401,注册进Nacos,同时被 Sentinel监控

Pom

  1. <!-- SpringCloud ailibaba nacos-->
  2. <dependency>
  3. <groupId>com.alibaba.cloud</groupId>
  4. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  5. </dependency>
  6. <!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
  7. <dependency>
  8. <groupId>com.alibaba.csp</groupId>
  9. <artifactId>sentinel-datasource-nacos</artifactId>
  10. </dependency>
  11. <!-- SpringCloud ailibaba sentinel-->
  12. <dependency>
  13. <groupId>com.alibaba.cloud</groupId>
  14. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
  15. </dependency>

yaml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置sentine dashboard地址
        dashboard: localhost:8080
#        dashboard: 119.3.160.59:8858
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口。
        port: 8719
management:
  endpoints:
    web:
      exposure:
        include: "*"

Controller

@RestController
public class FlowLimitController {

    @GetMapping("testA")
    public String testA(){
        return "----testa";
    }

流量控制规则

QPS:每秒请求数。
线程数:当调用该api线程数达到阈值的时候,进行限流。(使用多个窗口访问测试)
image.png

流控模式

  • 直接:当api达到限流条件时,直接限流。
  • 关联:当关联的资源达到阈值时,就限流自己。
    • 使用TestA关联testB阈值设为1,使用postman多线程集合组访问testB,此时TestB访问达到阈值,testA也不能访问。

image.png

  • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)

    流控效果

  • 快速失败:直接失败,抛异常。调用package com.alibaba.csp.sentinel.slots.block.flow.controller;

  • Warm Up:根据orderFactor(冷加载因子,默认3),从阀值/codeFactor进过预热时长,才达到设置的QPS阀值。 调用package com.alibaba.csp.sentinel.slots.block.flow.controller;

image.png
image.png

  • 排队等待:匀速排队,让请求以均匀的速度通过,阀值必须设置为QPS

    熔断降级

    Sentinel和Hystrx的区别

    Sentine是没有半开状态的

    熔断策略

  • 慢调用比例

image.png

  • RT为相应时间,慢调用比例为超出设置响应时间比例,则开启熔断,熔断时长过了就关闭熔断。
    • 异常比例: 只需要设置异常比例一个参数0.1~1.0。达到最小请求数,且比例达到,进行熔断。
    • 异常数: 只需要设置异常数,达到最小请求数,且异常数达到,进行熔断。

      热点Key限流

      是什么?

      热点就是经常访问的数据。比如某个访问频率Top的数据。比如:某个商品id,某个用户id。

      原理

      热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。

      基本使用

      @GetMapping("/testHotkey")
      @SentinelResource(value = "testHotkey",blockHandler = "deal_testHotkey")//value为sentinel识别唯一标识,blockHandler为兜底解决方案
      public  String testHotKey(@RequestParam(value = "p1",required = false) String p1,
                         @RequestParam(value = "p2",required = false) String p2){
      return "=========testHotKey";
      }
      public String deal_testHotkey(String p1, String p2, BlockException e){
      return "########deal_testHotkey,o(╥﹏╥)o";
      }
      
  • value为sentinel识别唯一标识
  • blockHandler为兜底解决方案

image.png

参数例外项

image.png

  • 即在热点限流的基础上,可以添加参数例外项,比如参数为VIP,设置另外的限流阈值。(万恶的VIP用户)

  • 总结:@SentinelResource只管配置出错,运行出错走异常

    系统保护规则

    Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
    image.png
    image.png

    客户自定义限流处理逻辑

    创建CustomerBlockHandler类用于自定义限流处理逻辑

    创建自定义限流处理类:CustomerBlockHandler

    public class CustomerBlockHandler {
    
      //必须使用static
      public static CommonResult handlerException(BlockException e){
          return new CommonResult(444,"按客户自定义,global,handlerException----1");
      }
    
      public static CommonResult handlerException2(BlockException e){
          return new CommonResult(444,"按客户自定义,global,handlerException----2");
      }
    }
    

    RateLimitController

      @GetMapping("/ratelimit/CustomerBlockHandler")
      @SentinelResource(value = "CustomerBlockHandler",
              blockHandlerClass = CustomerBlockHandler.class,
              blockHandler = "handlerException2")
      public CommonResult CustomerBlockHandler(){
          return new CommonResult(200,"按客户自定义限流测试ok","serial003");
    
      }
    
  • blockHandlerClass:表示统一处理的类。

  • blockHandler:指定具体的方法

image.png

服务熔断功能

Sentinel整合了Ribbon+OpenFeign+fallback

Ribbon+fallback案例

  1. 启动nacos8848Sentinel8888
  2. 新建微服务提供者cloudalibaba-provider-payment9003/9004

    @RestController
    public class PaymentController {
    
     @Value("${server.port}")
     private  String serverPort;
    
     public static HashMap<Long, Payment> hashmap=new HashMap<>();
     static {
         hashmap.put(1L,new Payment(1L,"111111111111111"));
         hashmap.put(2L,new Payment(2L,"222222222222222"));
         hashmap.put(3L,new Payment(3L,"333333333333333"));
     }
    
     @GetMapping("/paymentSQL/{id}")
     public CommonResult<Payment> paymentSQL(@PathVariable("id")Long id){
         Payment payment = hashmap.get(id);
         return new CommonResult<>(200,"from sql,serverport"+serverPort,payment);
     }
    }
    
  3. 新建微服务消费者cloudalibaba-consumer-nacos-order84

    @RestController
    @Slf4j
    public class CircleBreakerController {
     public static final String SERVICE_URL="http://nacos-payment-provider";
    
     @Resource
     private RestTemplate restTemplate;
    
     @GetMapping("/consumer/fallback/{id}")
     // @SentinelResource(value = "fallback")
     // @SentinelResource(value = "fallback",fallbackClass = CustomerBlockHandler.class, fallback = "handlerFallback")
     @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
                         exceptionsToIgnore = {IllegalArgumentException.class})
     public CommonResult<Payment> fallback(@PathVariable long id){
         CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class,id);
         if (id==4){
             throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
         }else if(result.getData()==null){
             throw new NullPointerException("NullPointerException,该id没有对应记录,空指针异常");
         }
         return result;
    
     }
    
     public CommonResult<Payment> handlerFallback(@PathVariable long id,Throwable throwable){
         Payment payment = new Payment(id, null);
         return new CommonResult<>(444,"兜底异常handlerFallback,exeception内容"+throwable.getMessage(),payment);
     }
     public CommonResult<Payment> blockHandler(@PathVariable long id, BlockException blockException){
         Payment payment = new Payment(id, null);
         return new CommonResult<>(445,"blockHandler限流,无此流水"+blockException.getMessage(),payment);
     }
    }
    
  4. 使用order84访问服务提供者9003/9004,传参1,2,3为正确参数,其余为异常。

  5. 正确参数,9003和9004轮询访问,实现了负载均衡。

image.png

  1. 错误参数,返回报错页面,不友好。
    1. 配置Sentinel限流fallback,只会再达到限流条件时返回限流结果。
    2. 同时配置Sentinel熔断blockHandler,报错返回熔断结果,达到限流条件返回限流结果。
  2. 结论:fallback管运行时异常; BlockHandler管配置违规。
  3. 配置exceptionsToIgnore,会忽略代码中某个异常处理。

    OpenFeign案例

    在服务端order84创建OpenFeignService模块

  4. pom

    <!--openFeign-->
    <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    
  5. yaml

    #集合sentinel对feign的支持
    feign:
    sentinel:
     enabled: true
    
  6. 主启动 @EnableFeignClients //启用openfeign

  7. 业务类Service(远程调用)、ServiceImpl(降级容错):

    //远程调用
    @FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackServiceImlp.class)
    public interface PaymentService {
     @GetMapping("/paymentSQL/{id}")
     public CommonResult<Payment> paymentSQL(@PathVariable("id")Long id);
    }
    //降级容错
    //服务降级返回
    @Component
    public class PaymentFallbackServiceImlp implements PaymentService{
     @Override
     public CommonResult<Payment> paymentSQL(Long id) {
         return new CommonResult<>(444,"服务降级返回,---PaymentFallbackService", new Payment(id,"error"));
     }
    }
    
  8. Controller

    @RestController
    public class OpenFeignController {
    
     @Resource
     private PaymentService paymentService;
    
     @GetMapping("/consumer/paymentSQL/{id}")
     public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
         CommonResult<Payment> paymentCommonResult = paymentService.paymentSQL(id);
         return paymentCommonResult;
     }
    

    熔断框架比较

    image.png

    Sentinel持久化规则

    存在的问题

    默认的持久化规则,重启Sentinel将会丢失。

    解决办法

    将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的限流规则就持续有效。

    操作:

  9. 修改8401

    1. pom

      <!--sentinel持久化-->
      <dependency>
      <groupId>com.alibaba.csp</groupId>
      <artifactId>sentinel-datasource-nacos</artifactId>
      </dependency>
      
    2. yaml ```yaml server: port: 8401

spring: application: name: cloudalibaba-sentinel-service cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport:

    #配置sentine dashboard地址
    dashboard: localhost:8080

dashboard: 119.3.160.59:8858

    #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口。
    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: “*”


2. 新建Nacos配置:
   1. `DataID:cloudalibaba-sentinel-service`
   1. 配置格式:json
```json
[{
"resource":"/rateLimit/byUrl",
"limitApp":"default",
"grade":1,
"conut":1,
"strategy":0,
"controlBehavior":0,
"clusterMode":false
}]

image.png

  1. 刷新8401发现sentinel中已经存在业务规则。关闭8401发现sentinel中配置消失了,重启后访问8401,sentinel重新出现,持久化配置成功。