首页 > 其他分享 >Sentinel系列之流量控制及熔断降级示例

Sentinel系列之流量控制及熔断降级示例

时间:2023-10-02 12:55:25浏览次数:40  
标签:调用 请求 示例 flowRule 熔断 Sentinel QPS 限流

关于Sentinel的介绍网上很多,不再复制粘贴。

本文主要演示Sentinel的两个重点功能:流量控制和熔断降级。

示例基于Sentinel 1.8.6, 同时使用JMeter进行并发请求(Postman无法并发)。当然也可以通过main方法,但这样就无法重复触发,并且无法学习Sentinel与Spring框架的集成

另外需要注意的是规则要一次载入

@PostConstruct
    private void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        rules.add(getQPSGradeFlowRule());
        rules.add(getQPSGradeRateLimiterFlowRule());
        rules.add(getQPSGradeWarmupFlowRule());
        rules.add(getThreadGradeFlowRule());
        rules.add(getThreadGradeRateLimiterFlowRule());
        /**
         * 加载规则,注意只能加载一次,否则会互相覆盖。最后会调到{@link com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager.FlowPropertyListener#configUpdate(java.util.List)}
         */
        FlowRuleManager.loadRules(rules);
    }

我认为既然已经学习到Sentinel,应该不需要手把手指导如何运行代码。

1. 流量控制

介绍

Sentinel提供了两大类的流量控制方式:基于QPS/并发数和基于调用关系,这里着重研究前者,因为后者是在前者的基础上细分了调用者、流量入口等,掌握了前者有助于理解后者。

限流的直接表现是在执行 Entry nodeA = SphU.entry(资源名字) 的时候抛出 FlowException 异常。FlowException 是 BlockException 的子类,可以捕捉 BlockException 来自定义被限流之后的处理逻辑。

同一个资源可以对应多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍历完毕。

一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:

  • resource:资源名,即限流规则的作用对象
  • count: 限流阈值
  • grade: 限流阈值类型,QPS 或线程数
  • strategy: 根据调用关系选择策略,有STRATEGY_DIRECT(默认值),STRATEGY_RELATE(基于关联关系),STRATEGY_CHAIN(基于调用链路)
  • controlBehavior:流量控制的手段,即超过阈值后,Sentinel的做法,包括直接拒绝(默认),冷启动,匀速器

实例

为方便演示,使用硬编码的方式设置流量规则。注意FlowRuleManager#loadRules方法只能全局调用一次,否则会互相覆盖。

@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:

  • value:资源名称,必需项(不能为空)

  • entryType:entry 类型,可选项(默认为 EntryType.OUT)

    资源调用的流量类型,是入口流量(EntryType.IN)还是出口流量(EntryType.OUT),注意系统规则只对 IN 生效

  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • fallback:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。

    fallback 函数签名和位置要求:返回值类型必须与原函数返回值类型一致;方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。

    fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。

  • defaultFallback(since 1.6.0):和fallback差不多,区别是全局的。

  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

示例1 QPS模式,直接拒绝

该方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。

    /**
     * 模拟场景:前端同时并发5个请求
     * 预期:2个接受,3个失败
     *
     * @return 响应
     */
    @SentinelResource(value = QPS_DEFAULT_RESOURCE, blockHandler = "testQPSBlocked")
    @GetMapping("/qps/default")
    public String testQPS() {
        logger.info("Visit QPS-Grade api successfully.");
        return "OK";
    }

    private FlowRule getQPSGradeFlowRule() {
        FlowRule flowRule = new FlowRule();
        //设置流控的资源
        flowRule.setResource(QPS_DEFAULT_RESOURCE);
        //设置流控规则-QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒只能访问两次
        flowRule.setCount(2);
        //默认行为是直接拒绝
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
        return flowRule;
    }

    public String testQPSBlocked(BlockException blockException) {
        logger.info("QPS-Grade blocked!");
        return "QPS-Grade blocked!";
    }

运行结果:

示例2 QPS模式,排队

这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

    /**
     * 模拟场景:前端同时并发5个请求
     * 预期:2个接受,3个排队
     *
     * @return 响应
     */
    @SentinelResource(value = QPS_RATE_LIMITER_RESOURCE, blockHandler = "testQPSBlocked")
    @GetMapping("/qps/ratelimiter")
    public String testQPSRateLimiter() throws InterruptedException {
        logger.info("Visit QPS-Grade RateLimiter api successfully.");
        return "OK";
    }

    private FlowRule getQPSGradeRateLimiterFlowRule() {
        FlowRule flowRule = new FlowRule();
        //设置流控的资源
        flowRule.setResource(QPS_RATE_LIMITER_RESOURCE);
        //设置流控规则-QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒只能访问两次
        flowRule.setCount(2);
        // 排队
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER);
        // 排队时间,默认是500毫秒,超过排队时间会抛出异常
        flowRule.setMaxQueueingTimeMs(3_000);
        return flowRule;
    }

运行结果,没有直接拒绝,而是排队,最后都通过了。

示例3 QPS模式,冷启动

该方式有多种叫法,预热、Warmup、冷启动。主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。

   /**
     * 模拟场景:前端不断地发送请求
     * 预期:在控制台看到明显的爬坡过程
     *
     * @return 响应
     */
    @SentinelResource(value = QPS_WARM_UP_RESOURCE, blockHandler = "testQPSBlocked")
    @GetMapping("/qps/warmup")
    public String testQPSWarmup() throws InterruptedException {
        logger.info("Visit QPS-Grade Warmup api successfully.");
        return "OK";
    }

    private FlowRule getQPSGradeWarmupFlowRule() {
        FlowRule flowRule = new FlowRule();
        //设置流控的资源
        flowRule.setResource(QPS_WARM_UP_RESOURCE);
        //设置流控规则-QPS
        flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //根据机器性能调整,每秒通过数和被拒绝数不能相差太多,否则影响曲线图的展示
        flowRule.setCount(5000);
        // 预热
        flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_WARM_UP);
        flowRule.setWarmUpPeriodSec(10);
        return flowRule;
    }

这里需要使用Dashboard观察效果。

可以看到有一个明显的缓慢爬坡过程。如果不用这种方式,则是相对陡峭的曲线。

示例4 Thread模式

线程数限流用于保护业务线程数不被耗尽。Sentinel线程数限流简单统计当前请求上下文的线程个数,如果超出阈值,新的请求会被立即拒绝。

这种模式和JUC的信号量一样,与时间窗无关。

	/**
     * 模拟场景:5秒内,前端每隔1秒,发送1个请求,共5个请求
     * 预期:5秒内可以接受2个请求,其余的失败
     *
     * @return 响应
     * @throws InterruptedException
     */
    @SentinelResource(value = THREAD_DEFAULT_RESOURCE, blockHandler = "testThreadBlocked")
    @GetMapping("/thread/default")
    public String testThreadRateLimiter() throws InterruptedException {
        logger.info("Enter Thread-Grade.");
        // 模拟耗时操作,前端每隔一秒发来一次请求,被限流。和JUC的semaphore一样。
        Thread.sleep(3_000L);
        logger.info("Visit Thread-Grade api successfully.");
        return "OK";
    }

    private FlowRule getThreadGradeFlowRule() {
        FlowRule flowRule = new FlowRule();
        //设置流控的资源
        flowRule.setResource(THREAD_DEFAULT_RESOURCE);
        //设置流控规则-Thread
        flowRule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        //同一时间只能有一个线程访问
        flowRule.setCount(1);
        //默认行为是直接拒绝
        return flowRule;
    }

运行结果如下:

2. 熔断降级

介绍

一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。

image-20230910152525511

熔断降级规则(DegradeRule)包含下面几个重要的属性:

Field 说明 默认值
resource 资源名,即规则的作用对象
grade 熔断策略,支持慢调用比例/异常比例/异常数策略 慢调用比例
count 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值
timeWindow 熔断时长,单位为 s
minRequestAmount 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) 5
statIntervalMs 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) 1000 ms
slowRatioThreshold 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)

实例

示例1 慢调用熔断

	/**
     * 模拟场景:
     * 1. 并发5个请求,全部超过耗时阈值,以及比例阈值,触发熔断
     * 2. 在熔断期内,并发5个请求,预期:全部熔断
     * 3. 熔断期(10秒)结束,并发5个请求,预期:先放开一个试试,并不会全部通过
     * 4. 再次并发5个请求,预期:由于第3步已经尝试一个成功,那么熔断器关闭
     *
     * @return
     * @throws InterruptedException
     */
    @SentinelResource(value = SLOW_REQUEST_RATIO_RESOURCE, blockHandler = "handleBlockException")
    @GetMapping("/slowrequestratio")
    public String testSlowRequestRatio() throws InterruptedException {
        int counter = slowRequestRatioCounter.incrementAndGet();
        if (counter <= 5) {
            Thread.sleep(2_000L);
        } else {
            logger.info("Enter!");
            Thread.sleep(500L);
        }
        logger.info("Visit SlowRequestRatio api successfully.");
        return "OK";
    }

    private DegradeRule getSlowRequestRatioDegradeRule() {
        DegradeRule degradeRule = new DegradeRule();
        //设置流控的资源
        degradeRule.setResource(SLOW_REQUEST_RATIO_RESOURCE);
        // 统计耗时的模式
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        // 响应时间超过1秒,认为系统卡顿
        degradeRule.setCount(1_000L);
        // 一分钟内有90%慢就要熔断
        degradeRule.setSlowRatioThreshold(0.9);
        degradeRule.setStatIntervalMs(60_000);
        // 熔断时间,注意这里是秒
        degradeRule.setTimeWindow(10);
        return degradeRule;
    }

运行结果:

源码分析:com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.AbstractCircuitBreaker

如果熔断器是关闭状态,直接通过。

如果是开启状态,通过CAS确保只有一个请求能把开关设置为半开状态,并尝试调用;CAS失败认为不能通过。

如果是半开启状态,说明已经有别的请求在尝试调用了,并且还没完成,所以也认为是不能通过。

示例2 异常比例

 	/**
     * 模拟场景:
     * 1. 并发5个请求,3个异常,2个正常,超过阈值0.4,触发熔断
     * 2. 在熔断期内,并发5个请求,预期:全部熔断
     * 3. 熔断期(30秒)结束,并发5个请求,预期:先放开一个试试,并不会全部通过
     * 4. 再次并发5个请求,预期:由于第3步已经尝试一个成功,那么熔断器关闭
     *
     * @return
     * @throws InterruptedException
     */
    @SentinelResource(value = EXCEPTION_RATIO_RESOURCE, blockHandler = "handleBlockException")
    @GetMapping("/exceptionratio")
    public String testExceptionRatio() throws InterruptedException {
        int counter = exceptionRatioCounter.incrementAndGet();
        if (counter <= 5) {
            // 奇数抛出异常,共抛出3次,占60%,超过阈值
            if (counter % 2 == 1) {
                throw new RuntimeException("Unknown exception.");
            }
        }
        logger.info("Visit ExceptionRatio api successfully.");
        return "OK";
    }


    private DegradeRule getExceptionRatioDegradeRule() {
        DegradeRule degradeRule = new DegradeRule();
        //设置流控的资源
        degradeRule.setResource(EXCEPTION_RATIO_RESOURCE);
        // 统计耗时的模式
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        // 40%以上的请求失败,触发熔断
        degradeRule.setCount(0.4);
        degradeRule.setStatIntervalMs(60_000);
        // 熔断时间,注意这里是秒
        degradeRule.setTimeWindow(30);
        return degradeRule;
    }

    public String fallback(Throwable throwable) {
        logger.error("Exception occurs!");
        return "Exception";
    }

运行结果:

示例3 异常数

	/**
     * 模拟场景:
     * 1. 并发5个请求,全部抛出异常,触发熔断
     * 2. 在熔断期内,并发5个请求,预期:全部熔断
     * 3. 熔断期(10秒)结束,并发5个请求,预期:先放开一个试试,并不会全部通过
     * 4. 再次并发5个请求,预期:由于第3步已经尝试一个成功,那么熔断器关闭
     *
     * @return
     * @throws InterruptedException
     */
    @SentinelResource(value = EXCEPTION_COUNT_RESOURCE, blockHandler = "handleBlockException", fallback = "fallback")
    @GetMapping("/exceptioncount")
    public String testExceptionCount() throws InterruptedException {
        int counter = exceptionCountCounter.incrementAndGet();
        if (counter <= 5) {
            throw new RuntimeException("Unknown exception.");
        }
        logger.info("Visit ExceptionCount api successfully.");
        return "OK";
    }


    private DegradeRule getExceptionCountDegradeRule() {
        DegradeRule degradeRule = new DegradeRule();
        //设置流控的资源
        degradeRule.setResource(EXCEPTION_COUNT_RESOURCE);
        // 统计耗时的模式
        degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        // 5个以上的请求失败,触发熔断。注意,这里是开区间,必须是大于
        degradeRule.setCount(4);
        degradeRule.setMinRequestAmount(1);
        degradeRule.setStatIntervalMs(60_000);
        // 熔断时间,注意这里是秒
        degradeRule.setTimeWindow(10);
        return degradeRule;
    }

运行结果:

标签:调用,请求,示例,flowRule,熔断,Sentinel,QPS,限流
From: https://www.cnblogs.com/kingsleylam/p/17739851.html

相关文章

  • 打造高效外卖外送商城系统:代码示例和关键功能介绍
    随着外卖外送服务的普及,开发一款高效的外卖外送商城系统对于餐馆和食品供应商来说至关重要。这篇文章将为您提供一个简单的外卖外送商城系统的示例代码,并介绍关键功能,以帮助您了解这类系统的工作原理和关键部分。1.准备工作首先,我们需要设置一些基本的准备工作。我们将使用Python......
  • 44、Flink之module模块介绍及使用示例和Flink SQL使用hive内置函数及自定义函数详细示
    文章目录Flink系列文章一、模块Modules1、模块介绍2、模块类别ModuleTypes1)、CoreModule2)、HiveModule3)、User-DefinedModule3、模块生命周期和解析顺序ModuleLifecycleandResolutionOrder4、模块Modules的使用1)、SQL方式2)、编码方式-java二、HiveFunctions内置函数和自定......
  • 26、Flink 的SQL之概览与入门示例
    文章目录Flink系列文章一、SQL1、数据类型2、保留关键字二、SQL入门1、FlinkSQL环境准备1)、安装Flink及提交任务方式2)、SQL客户端使用介绍3)、简单示例2、Source表介绍及示例3、连续查询介绍及示例4、Sink表介绍及示例本文简单的介绍了SQL和SQL的入门,并以三个简单的示例进行介......
  • Compose基础示例(列表,状态,Image,Text Field, 定时器)
    @file:Suppress("UNREACHABLE_CODE")packagecom.by.composeappimportandroid.os.Bundleimportandroid.util.Logimportandroid.widget.Toastimportandroidx.activity.ComponentActivityimportandroidx.activity.compose.setContentimportandroidx.co......
  • 熔断、限流、降级 —— SpringCloud Alibaba Sentinel
    Sentinel简介Sentinel是阿里中间件团队开源的,面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性Sentinel提供了两个服务组件:Sentinel用来实现微服务系统中服务熔断......
  • kettle教程:spoon kettle数据转换示例
    Kettle:四大家族(核心组件)Chef(中文:厨师)、Kitchen(中文:厨房)、Spoon(中文:勺子)、Pan(中文:平底锅)Chef—工作(job)设计工具(GUI方式)。Kitchen—工作(job)执行器(命令行方式)。Spoon—转换(transform)设计工具(GUI方式)。pan—转换(transform)执行器(命令行方式)。Job和Transformation......
  • 构建国际化的海外版外送商城系统:代码示例与实现
    在快节奏的现代生活中,外送服务变得越来越受欢迎,因此开发一个高效便捷的外送商城系统至关重要。本文将通过代码示例来介绍如何构建一个简单的外送商城系统,以展示其核心功能和工作原理。#导入所需的库importrandom#模拟商店数据shops=[{"name":"餐厅A","menu":{"汉......
  • fmt 库简介和示例【GO 基础】
    〇、关于fmtfmt标准库是Go语言标准库的一部分,提供了格式化字符串、输入输出等基本功能。通过fmt库,我们可以进行简单的格式化输出、读取用户输入、错误输出等操作。fmt库实现了类似C语言printf和scanf的格式化I/O,主要分为向外输出内容和获取输入内容两大部分,本文将......
  • 27、Flink 的SQL之SELECT (Pattern Recognition 模式检测)介绍及详细示例(7)
    Flink系列文章1、Flink部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接13、Flink的tableapi与sql的基本概念、通用api介绍及入门示例14、Flink的tableapi与sql之数据类型:内置数据类型以及它们的属性15、Flink的tableap......
  • 27、Flink 的SQL之SELECT (Group Aggregation分组聚合、Over Aggregation Over聚合 和
    文章目录Flink系列文章一、GroupAggregation分组聚合1、count示例2、groupby的聚合示例3、distinct聚合4、GROUPINGSETS1)、ROLLUP2)、CUBE5、Having二、OverAggregation1、语法1)、ORDERBY2)、PARTITIONBY3)、RangeDefinitions4)、RANGEintervals5)、ROWintervals2、示例三、......