𝓝𝓮𝓶𝓸&博客

【SpringCloud】SpringCloud Alibaba Sentinel实现熔断与限流

SpringCloud Alibaba Sentinel实现熔断与限流

限流与降级

限流 blockHandler
降级 fallback
降级需要运行时出现异常才会触发,而限流一旦触发,你连运行的机会都没有,当然就不会降级。
也就是说,两者如果同时触发,那么一定是限流触发(降级连机会都没有)。

Sentiel

官网

https://github.com/alibaba/Sentinel
中文:https://github.com/alibaba/Sentinel/wiki/介绍

是什么

一句话解释就是我们之前讲过的hystrix

去哪下

https://github.com/alibaba/Sentinel/releases

能干嘛

怎么玩

官方文档:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

服务中的各种问题

  • 服务雪崩
  • 服务降级
  • 服务熔断
  • 服务限流

安装Sentiel控制台

sentinel组件由两部分构成

  • 后台
  • 前台8080

安装步骤

下载

https://github.com/alibaba/Sentinel/releases

运行命令

前提

java8环境OK
8080端口不能被占用

命令

java -jar sentinel-dashboard-1.7.0.jar

访问sentinel管理界面

http://localhost:8080

登录账号密码均为sentinel

初始化演示功能

启动Nacos8848成功

http://localhost:8848/nacos/#/login

Module

cloudalibaba-sentinel-service8401

POM

<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>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <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>
    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </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>

YML

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinal-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentin dashboard地址
        dashboard: localhost:8080
        # 默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口
        port: 8719

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

主启动

@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401 {

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

业务类FlowLimitController

public class FlowLimitController {

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

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

启动Sentinel8080

java -jar sentinel-dashboard-1.7.0.jar

启动微服务8401

启动8401微服务后台查看sentinel控制台

空空如也,啥也没有

Sentinel采用懒加载说明

执行一次访问

http://localhost:8401/testA
http://localhost:8401/testB

效果

结论

sentinel8080正在监控微服务8401

流控规则

基本介绍

进一步解释说明


流控模式

直接(默认)

直接->快速失败

系统默认

配置及说明

测试

  • 快速点击访问http://localhost:8401/testA
  • 结果:Blocked by Sentinel(flow limiting)
  • 思考???
    直接调用默认报错信息,技术方面ok,but,是否应该有我们自己的后续处理
    类似有个fallback的兜底方法

关联

应用场景:比如支付时达到阈值,可以从源头上比如购买界面,进行限流
类比:下流洪灾,上流关水

是什么

  • 当关联的资源达到阈值时,就限流自己
  • 当与A关联的资源B达到阈值后,就限流自己
  • B惹事,A挂了

配置A

postman模拟并发密集访问testB

访问B成功

postman里新建多线程集合组

将访问地址添加进新线程组

RUN

大批量线程高并发访问B,导致A失效了

运行后发现testA挂了

  • 点击访问A
  • 结果:Blocked by Sentinel(flow limiting)

链路

  • 多个请求调用同一个微服务
  • 家庭作业试试

流控效果

直接->快速失败(默认的流控处理)

  • 直接失败,抛出异常
    Blocked by Sentinel(flow limiting)
  • 源码
    com.alibaba.csp.sentinel.slots.block.controller.DefaultController

预热

说明

公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

官网

源码

com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController

WarmUp配置

多次点击http://localhost:8401/testB

刚开始不行,后续慢慢OK

应用场景

如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,颍热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。

排队等待

匀速排队,阈值必须设置为QPS

官网

源码

com.ailibaba.csp.sentinel.slots.block.controller.RateLimiterController

测试

降级规则

官网

https://github.com/alibaba/Sentinel/wiki/熔断降级

基本介绍

进一步说明

Sentinel的断路器是没有半开状态的

Sentinel熔断隆级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高) ,对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。

半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用,具体参考Hystrix

复习Hystrix

降级策略实战

RT

是什么


测试

代码

配置

jmeter压测
结论


异常比例

是什么


测试

代码

配置

jmeter

结论

异常数

是什么


异常数是按照分钟统计的

测试

代码

同异常比例

配置

jmeter

热点key限流

基本介绍

是什么

官网

https://github.com/alibaba/Sentinel/wiki/热点参数限流

承上启下复习start

SentinelResource

兜底方法
分为系统默认和客户自定义,两种

之前的case,限流出问题后,都是用sentine|系统默认的提示: Blocked by Sentinel (flow limiting)

我们能不能自定?类似hystrix,某个方诎问题了,就找对应的兜底降级方法?

结论
从HystrixCommand到@SentinelResource

代码

com.alibaba.csp.sentinel.slots.block.BlockException

配置

配置

1

  • @SentinelResource(value = "testHotKey")
  • 异常打到了前台用户界面看到,不友好

2

  • @SentinelResource(value = "testHotKey",blockHandler="dealHandler_testHotKey")
  • 方法testHotKey里面第一个参数只要QPS超过每秒一次,马上降级处理
  • 用了我们自己定义的

测试

参数例外项

上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

特殊情况

普通

  • 超过1秒钟一个后,达到阈值1后马上被限流
  • 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
  • 特例:假如当p1的值等于5时,它的阈值可以达到200

配置


添加
按钮不能忘

测试

前提条件

热点参数的注意点,参数必须是基本类型或者String

其他

手贱添加异常看看o(╥﹏╥)o
后面讲

@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;

RuntimeException
int age = 10/0, 这个是java运行时报出的运行时异常RunTimeException, @SentinelResource不管

总结
@SentinelResource主管配置出错,运行出错该走异常走异常

系统规则

是什么

https://github.com/alibaba/Sentinel/wiki/ 系统自适应限流

各项配置说明

配置全局QPS

不合适,使用危险,一竹竿打死一船人

@SentinelResource

按资源名称限流+后续处理

启动nacos成功

启动Sentinel成功

Module

cloudalibaba-sentinel-service8401

pom

<dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>com.eiletxie.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
</dependency>

yml

业务类RateLimitController

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource",blockHandler = "handleException")
    public CommonResult byResource() {
        return  new CommonResult(200,"按照资源名称限流测试",new Payment(2020L,"serial001"));
    }

    public CommonResult handleException(BlockException exception) {
        return  new CommonResult(444,exception.getClass().getCanonicalName() + "\t 服务不可用");
    }
}

主启动

配置流控规则

配置步骤

图形配置和代码关系

表示1秒钟内查询次数大于1,就跑到我们自定义的限流处,限流

测试

  • 1秒钟点击1下,OK
  • 超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生

额外问题

  • 此时关闭服务8401看看
  • Sentinel控制台,流控规则消失了?
    临时?持久?

按照Url地址限流+后续处理

通过访问URL来限流,会返回Sentinel自带默认的限流处理信息

业务类RateLimitController

访问一次

Sentinel控制台配置

测试

  • 疯狂点击http://localhost:8401/rateLimit/byUrl
  • 结果

上面兜底方案面临的问题

  1. 系统默认的, 没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在-块,不直观。
  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  4. 全局统一的处理方法没有体现。

客户自定义限流处理逻辑

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

自定义限流处理类

CustomerBlockHandler

RateLimitController

启动微服务后再调用一次

http://localhost:8401/rateLimit/customerBlockHandler

Sentinel控制台配置

测试后我们的自定义出来了

进一步说明

更多注解说明

https://github.com/alibaba/Sentinel/wiki/注解支持

多说一句

Sentinel主要有三个核心Api

  • sphU定义资源
  • Tracer定义统计
  • ContextUtil定义了上下文

服务熔断功能

sentinel整合ribbon+openFeign+fallback

Ribbon系列

启动nacos和sentinel

提供者9003/9004

新建cloudalibaba-provider-payment9003/9004

POM

<dependencies>
    <!-- SpringCloud ailibaba nacos-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- SpringCloud ailibaba sentinel-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    </dependency>
    <dependency><!-- 引用自己定义的api通用包,可以使用Payment支付Entity -->
        <groupId>com.eiletxie.springcloud</groupId>
        <artifactId>cloud-api-commons</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <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>
    <!--热部署-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </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>

YML

server:
  port: 9003

spring:
  application:
    name: nacos-payment-provider
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

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

主启动

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {

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

业务类

@RestController
public class PaymentController {

    @Value("${server.port}")
    private  String serverPort;

    public static HashMap<Long, Payment > map = new HashMap<>();
    static {
        map.put(1L,new Payment(1L,"1111"));
        map.put(1L,new Payment(2L,"2222"));
        map.put(1L,new Payment(3L,"3333"));
    }


    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
        Payment payment = map.get(id);
        CommonResult<Payment> result = new CommonResult<>(200,"from mysql,serverPort: " + serverPort,payment);
        return result;
    }
}

测试地址

http://localhost:9003/paymentSQ/

消费者84

新建cloudalibaba-consumer-nacos-order84

POM

与提供者pom一致

YML

server:
  port: 84

spring:
  application:
    name: nacos-order-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719

#消费者将去访问的微服务名称
server-url:
  nacos-user-service: http://nacos-payment-provider

主启动

@EnableDiscoveryClient
@SpringBootApplication
public class OrderMain84 {

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

业务类

ApplicationContextConfig
@Configuration
public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate() {
        return new RestTemplate();
    }
}
CircleBreakerController
public class CircleBreakerController {

    public static  final  String SERVICE_URL = "http://nacos-payment-provider";

    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback")
    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("IllegalArgument ,非法参数异常...");
        }else if(result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return  result;
    }
}
修改后请重启微服务
  • 热部署对java代码级生效及时
  • 对@SentinelResource注解内属性,有时效果不好
目的
  • fallback管运行异常
  • blockHandler管配置违规
测试地址

http://localhost:84/consumer/fallback/3

没有任何配置

直接给用户error界面。,,不太友好

只配置fallback
  @RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")
    @SentinelResource(value = "fallback",fallback = "handlerFallback")
    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("IllegalArgument ,非法参数异常...");
        }else if(result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return  result;
    }


    public CommonResult handlerFallback(@PathVariable Long id,Throwable e) {
        Payment payment = new Payment(id,"null");
        return new CommonResult(444,"异常handlerFallback,exception内容: " + e.getMessage(), payment);
    }

#######结果

只配置blockHandler

#######编码

  @RequestMapping("/consumer/fallback/{id}")
//    @SentinelResource(value = "fallback")
//    @SentinelResource(value = "fallback",fallback =
    @SentinelResource(value = "fallback",blockHandler = "blockHandler")
    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("IllegalArgument ,非法参数异常...");
        }else if(result.getData() == null) {
            throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常");
        }

        return  result;
    }
 
public CommonResult blockHandler(@PathVariable Long id,BlockException e) {
    Payment payment = new Payment(id,"null");
    return new CommonResult(444,"blockHandler-sentinel 限流,BlockException: " + e.getMessage(), payment);
}

记得配置一个qps的控制

#######结果

同时配置fallback和blockHandler

异常忽略

Feign系列

修改84模块

  • 84消费者调用提供者9003
  • Feign组件一般是消费端

POM

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

YML

业务类

  • 带@FeignClient注解的业务接口
  • fallback = PaymentFallbackService.class
  • Controller

主启动

添加@EnableFeignClients启动Feign的功能

http://localhost:84/consumer/paymentSQL/2

测试84调用9003,此时故意关闭9003微服务提供者,看84消费自动降级,不会被耗死

熔断框架比较

Sentinel Hystrix resilience4j
隔离策略 信号量隔离(并发线程数限流) 线程池隔离/信号量隔离 信号量隔离
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 基于异常比率、响应时间
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于RxJava) Ring Bit Buffer
动态规则配置 支持多种数据源 支持多种数据源 有限支持
扩展性 多个扩展点 插件的形式 接口的形式
基于注解的支持 支持 支持 支持
限流 基于QPS.支持基于调用关系的限流 有限的支持 Rate Limiter

规则持久化

是什么

一旦我们重启应用,sentinel规则消失,生产环境需要将配置规则进行持久化

怎么玩

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

步骤

修改cloudalibaba-sentinel-server8401

POM

<!-- SpringCloud ailibaba sentinel-datasource-nacos 持久化需要用到-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

YML

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinal-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentin dashboard地址
        dashboard: localhost:8080
        # 默认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: '*'
 
 feign:
  sentinel:
    enabled: true #激活Sentinel 对Feign的支持

添加Nacos业务规则配置

内容解析

[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

这一步的作用是每次消费者微服务启动时在nacos中定义sentinel的流控规则,从而做到持久化的效果

启动8401刷新sentinel发现业务规则变了

快速访问测试接口

http://localhost:8401/rateLimit/byUrl
默认

停止8401再看sentinel

重新启动8401再看sentinel

posted @ 2020-08-23 22:26  Nemo&  阅读(1218)  评论(0编辑  收藏  举报