SpringCloud微服务(十一)——Sentinel服务熔断限流

SpringCloud Alibaba Sentinel服务熔断与限流

简介

github:[https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5](https://github.com/alibaba/Sentinel/wiki/如何使用)

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

一句话解释,跟Hystrix一样的理念

随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的轻量级流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护等多个维度来帮助您保障微服务的稳定性。

在这里插入图片描述

流量控制

流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。

熔断降级

除了流量控制以外,及时对调用链路中的不稳定因素进行熔断也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,进而导致级联错误。

系统负载保护

Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。

针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

安装

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

下载的是sentinel-dashboard-1.7.1.jar

#默认8080端口,8080不要被占用
#直接运行即可,需要jdk环境
java -jar sentinel-dashboard-1.7.1.jar

#linux系统
nohup java -jar sentinel-dashboard-1.7.1.jar &
ctrl+c
cat nohup.out
#访问8080端口
#账号sentinel,密码sentinel

在这里插入图片描述

演示工程

依赖配置

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!--  sentinel-datasource-nacos 后续持久化用   -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

<!-- sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

yml配置

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        # Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        # sentinel dashboard 地址
        dashboard: 192.168.169.130:8080
        # 默认为8719,如果被占用会自动+1,直到找到为止
        # 簇点链路,可以把某些微服务归类到某个链路,统一处理
        port: 8719
        
      # 流控规则持久化到nacos
      datasource:
        dsl:
          nacos:
            server-addr: localhost:8848
            data-id: ${spring.application.name}
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: flow
            
management:
  endpoints:
    web:
      exposure:
        include: "*"

启动类使用的是nacos注册中心@EnableDiscoveryClient

启动,访问Rest请求,可以在sentinel管理页面看到确实有监控cloudalibaba-sentinel-service微服务:

如果不访问该微服务的rest请求或者长时间没有访问该微服务,sentinel管理页面会去掉对该微服务接口的监控。要是页面找不到就访问下资源接口。

在这里插入图片描述

流控规则

流量控制

就是限流,限制资源接口的访问数

官网:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6

  • 资源名:唯一名称,默认请求路径,就是restcontroller上的路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型/单机阈值
    • QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
    • 线程数:当调用该api的线程数达到阈值的时候,进行限流
    • 2者区别就是QPS会让请求全部过来访问,线程数最多只能过来对应线程数数量的请求数,来多了也没用
  • 是否集群:不需要集群
  • 流控模式:
    • 直接:api达到限流条件,直接限流
    • 关联:当关联的资源达到阈值时,就限流自己
    • 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)(api级别的针对来源)
  • 流控效果:
    • 快速失败:直接失败,抛异常
    • Warm Up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
    • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
流控模式
    @GetMapping("/testA")
    public String testA(){
        try {
            //0.8秒
            TimeUnit.MILLISECONDS.sleep(800);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "testA-----";
    }

    @GetMapping("/testB")
    public String testB(){
        log.info(Thread.currentThread().getName() + "...testB ");
        return "testB   -----";
    }
直接(默认)

系统默认直接快速失败,超过阈值直接限流

阈值类型:QPS或线程数

如下设置路径为/testA的流控:

单机阈值是每秒1个访问/每秒一个线程数

在这里插入图片描述

快速点击访问http://localhost:8401/testA

手速超过每秒1个访问/每秒一个线程数,就会限制访问。

在这里插入图片描述

关联

当关联的资源达到阈值时,就限流自己

当与A关联的资源B达到阈值后,就限流自己

举例子:支付接口遭到大量访问,且很多未处理,那么我们应该限制下订单的接口访问,不然支付接口访问会堆积更多,支付接口会挂。

当关联资源/testB的QPS阈值超过1时,就限流/testA的Rest访问地址,当关联资源到达阈值后限制配置好的的资源名。

在这里插入图片描述

postman模拟并发密集访问testB,jmeter也行。

在这里插入图片描述
在这里插入图片描述

这样就可以大量访问/testB,在网页访问/testA,将无法访问,被限流

在这里插入图片描述

链路

多个请求调用了同一个微服务

簇点链路端口默认为8719,如果被占用会自动+1,直到找到为止

簇点链路,可以把某些微服务归类到某个链路端口,统一处理

NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。

一棵典型的调用树如下图所示:

     	          machine-root
                    /       \
                   /         \
             Entrance1     Entrance2
                /             \
               /               \
      DefaultNode(nodeA)   DefaultNode(nodeA)

上图中来自入口 Entrance1Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 FlowRule.strategyRuleConstant.CHAIN,同时设置 FlowRule.ref_identityEntrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用。

调用链的入口(上下文)是通过 API 方法 ContextUtil.enter(contextName) 定义的,其中 contextName 即对应调用链路入口名称。

流控效果
直接(默认)

快速失败(默认的流控处理)

直接失败,抛出异常

Blocked by Sentinel(flow limiting)页面

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

预热

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

默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值

限流冷启动:https://github.com/alibaba/Sentinel/wiki/

在这里插入图片描述

系统初始化的阈值为10/3约等于3,即阈值刚开始为3;然后过了5秒后阈值才慢慢升高恢复到10。

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

一开始点太快会限流Blocked by Sentinel(flow limiting),5秒后10阈值,每秒10个内都可以访问。

应用场景:秒杀系统在开启的瞬间,会有大量流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢把流量放进来,慢慢的把阈值增长到设置的阈值。

排队等待

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

源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

让请求均匀的速度通过,阈值必须设置为QPS,否则无效

如下,/testA每秒1次请求,超过的话排队等待,等待的超时时间为20000毫秒。

在这里插入图片描述

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

postman模拟并发密集访问testA

controller打印日志

    @GetMapping("/testA")
    public String testA(){
        log.info(Thread.currentThread().getName() + "...testA ");
        return "testA   -----";
    }

每秒一个:

在这里插入图片描述

降级规则

服务降级,服务超时或异常给出友好提示或兜底方案,实际开发需要自定义返回,这里先测试。

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

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

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

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

  • RT(平均响应时间,秒级)

    平均响应时间:超出阈值在时间窗口内通过的请求>=5,有两个条件同时满足后触发降级

    窗口期过后关闭断路器

    RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)

  • 异常比例(秒级)

    QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

  • 异常数(分钟级)

    异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级

降级策略实战
RT

平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。

在这里插入图片描述

对controller睡眠1秒测试TimeUnit.SECONDS.sleep(1);,对资源选择降级设置:(条件必须每秒请求超过5个,且超过阈值)

在这里插入图片描述

使用jmeter压力测试

在这里插入图片描述
在这里插入图片描述

执行,一秒10个请求>5,请求均超过阈值200ms,未来的时间窗口被降级,jmeter一直执行,一直降级,关闭jmeter,即可恢复。

异常比例

异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

在这里插入图片描述

在对应controller加上异常int age = 10 /0 ;测试,访问,如下:

在这里插入图片描述

条件和设置:

在这里插入图片描述

jmeter高并发测试,再次网页访问,不是上次的报错页面了,而是降级:

在这里插入图片描述

异常数

异常数是按分钟统计的

异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

在这里插入图片描述

在对应controller加上异常int age = 10 /0 ;测试,访问,如下:

在这里插入图片描述

接着设置异常数降级规则:

在这里插入图片描述

然后网页访问请求,前5次都是报错的页面,第6次后都是降级页面,降级页面会持续70秒,70秒过后恢复。

在这里插入图片描述

热点规则

也是限流措施,热点key,对某个访问量高的参数值进行限流。

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

何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

在这里插入图片描述

Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。

兜底方案:

分为系统默认和客户自定义,两种

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

我们也可以自定义,类似hystrix的fallback降级兜底方法,结论是从@HystrixCommand—>>>@SentinelResource

源码:com.alibaba.csp.sentinel.slots.block.BlockException

默认是出错就抛异常BlockException处理。

普通设置

测试代码@RestController:

@SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey")

设置热点key的异常方法,就是热点key被触发限流了就跳到dealTestHotKey方法。通过BlockException异常来处理。

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey") //value自定义,唯一就行,尽量保持跟路径一致
    public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                             @RequestParam(value = "p2", required = false) String p2){
        //int age = 10 /0;
        return "testHotKey -----";
    }

    //BlockException blockException必须加,兜底方法
    public String dealTestHotKey(String p1, String p2, BlockException blockException){
        return "dealTestHotKey---------";
    }

sentinel上设置热点key:

testHotKey是注解上的名字,保持一致。

在这里插入图片描述

索引0开始,对应资源接口上的参数,也就是p1。

只要参数中没有p1带参,无论怎么访问都是没问题的。

在这里插入图片描述

如果带参p1,只要符合,不超过阈值也可以正常响应,如果1s内QPS访问次数超过阈值1时,则报错。当然这是自定义的报错页面,因为我们加了blockHandler = "dealTestHotKey"方法

在这里插入图片描述

去掉blockHandler = "dealTestHotKey"方法,则是如下页面

在这里插入图片描述

不是Blocked by Sentinel(flow limiting),热点key只处理Sentinel页面上的规则问题,如果代码加入int age = 10/0;等模拟异常代码,错误信息会直接在页面显示,不处理其他异常的降级,只处理这个BlockException异常,所以热点key必须加上兜底方法。

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

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

@SentinelResource主管配置出错,运行出错自己走异常。

参数额外项

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

我们期望p1参数当它是某个特殊值时,它的限流和平时不一样

比如:特例:假如当p1的值等于5时,它的阈值可以达到200

如下设置:

参数类型对应rest接口的p1参数类型

在这里插入图片描述

当p1=5时,连续快速测试访问都没触发降级,当p1不等于5的时候,阈值变为平常的1。当p1等于5的时候,阈值变为200。

在这里插入图片描述

系统规则

说白了就是这个系统规则会对所有rest接口生效。全局

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

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

  • Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5
  • CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。0%-100%。
  • 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。

在这里插入图片描述

比如选择入口QPS的阈值为1,则所有的请求都是每秒只能请求一次,不然就降级。

@SentinelResource

按资源名称+后续处理

测试自带的blockHandler异常方法

@RestController
public class RateLimitController {

    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")//名字可自定义,不唯一就行
    public CommonResult byResource(){
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));//hutool的工具包,生成UUID不带-
    }
    
    public CommonResult handleException(BlockException blockException){
        // 打印哪个异常方法在限流处理
        return new CommonResult<>(444, blockException.getClass().getCanonicalName()+"\t服务不可用" );
    }
}

用@SentinelResource上的名字byResource设置流控规则,才会调到自定义方法,如果超过sentinel控制台设置的阈值,跳到自定义的方法:

在这里插入图片描述

按照Url地址限流+后续处理
    @GetMapping("/rateLimit/byUrl")
    @SentinelResource(value = "byUrl")
    public CommonResult byUrl(){
        return new CommonResult(200, "by url限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
    }

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

通过路径url设置流控规则,超过阈值:

在这里插入图片描述

上面兜底方案面临的问题

  • 系统默认的,没有体现我们自己的业务要求
  • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观
  • 每个业务方法都添加一个兜底的,那代码膨胀
  • 全局统一的处理方法没有体现
客户自定义限流处理逻辑

实际开发常用

这是争对sentinel控制规则违规的兜底,程序异常不行

自定义限流处理类CustomerBlockHandler,参数必须得加上BlockException

package com.wzq.springcloud.myhandler;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.wzq.springcloud.entities.CommonResult;

/**
 * 自定义全局异常方法,方法可被不同的接口使用
 * @author wzq
 * @version 1.0
 * @create 2020/03/06
 */
public class CustomerBlockHandler {

    public static CommonResult handlerException(BlockException exception) {
        return new CommonResult(444, "客户自定义,global handlerException---1");
    }

    public static CommonResult handlerException2(BlockException exception) {
        return new CommonResult(444, "客户自定义,global handlerException---2");
    }
}

contoller调用

    //CustomerBlockHandler

    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")
    public CommonResult customerBlockHandler(){
        return new CommonResult(200, "客户自定义 限流测试OK", new Payment(2020L, IdUtil.simpleUUID()));
    }

blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")指定自定义限流类中的某个方法来处理。方法可多次被不用controller调用,解耦。

超过流控阈值:

在这里插入图片描述

注意点

注意:注解方式埋点不支持 private 方法。

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

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

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

  • 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 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:

    • 返回值类型必须与原函数返回值类型一致;
    • 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
    • defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。

注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallbackdefaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

Sentinel主要有三个核心Api:SphU定义资源、Tracer定义统计、ContextUtil定义了上下文

服务熔断

提供者9003/9004,设置2个一样的微服务9003/9004

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

server:
  port: 9003/9004

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

management:
  endpoints:
    web:
      exposure:
        include: "*"

启动类:@EnableDiscoveryClient

controller接口:

@RestController
public class PaymentController {

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

    public static Map<Long , Payment> hashMap = new HashMap<>();
    
    static {
        hashMap.put(1L, new Payment(1L, IdUtil.simpleUUID()));
        hashMap.put(2L, new Payment(2L, IdUtil.simpleUUID()));
        hashMap.put(3L, new Payment(3L, IdUtil.simpleUUID()));
    }

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

消费者84

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--     sentinel-datasource-nacos 后续持久化用   -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-datasource-nacos</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>

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

service-url:
  nacos-user-service: http://nacos-payment-provider

启动类:@EnableDiscoveryClient

Ribbon系列

负载均衡

消费者84添加rest负载均衡:

@Configuration
public class ApplicationContextConfig {

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

通过服务名调用方法后实现轮询。

无配置

controller调用提供者接口:

@RestController
@Slf4j
public class CircleBreakerController {
    
    private 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("id") Long id){
        CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
        }else if(commonResult.getData() == null){
            throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
        }
        return commonResult;
    }

@SentinelResource(value = "fallback") //没有配置

降级都没配置,默认访问,采用ribbon轮询负载均衡:

在这里插入图片描述

id = 4,测试程序异常,error页面:

在这里插入图片描述

id = 5,测试程序异常,error页面:

在这里插入图片描述

只配置程序异常fallback处理
    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
        }else if(commonResult.getData() == null){
            throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
        }
        return commonResult;
    }

    // 本例是fallback
    public CommonResult handlerFallback(Long id, Throwable e){
        Payment payment = new Payment(id, null);
        return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment);
    }

@SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常

fallback只处理程序异常的兜底,出错则跳到handlerFallback方法,测试程序异常,不再是error页面:

在这里插入图片描述

只配置sentinel控制台违规异常blockHandler处理
    @RequestMapping("/consumer/fallback/{id}") 
    @SentinelResource(value = "fallback",blockHandler = "blockHandler") 配置了blockHandler,只负责sentinel控制台配置违规 
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
        }else if(commonResult.getData() == null){
            throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
        }
        return commonResult;
    }

    public CommonResult blockHandler(Long id, BlockException exception){
        Payment payment = new Payment(id, null);
        return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment);
    }

@SentinelResource(value = "fallback",blockHandler = "blockHandler") 配置了blockHandler,只负责sentinel控制台配置违规

blockHandler只处理sentinel控制台中配置的限流规则违规异常,先配置测试限流,名字是fallback那个:

在这里插入图片描述

前2次访问还是error页面,之后访问就触发了blockHandler兜底方法:

在这里插入图片描述

fallback和blockHandler都配置
    @RequestMapping("/consumer/fallback/{id}")
    @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler")// 配置了blockHandler和fallback
    public CommonResult<Payment> fallback(@PathVariable("id") Long id){
        CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class);
        if(id == 4){
            throw new IllegalArgumentException("IllegalArgumentException,非法参数异常");
        }else if(commonResult.getData() == null){
            throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常");
        }
        return commonResult;
    }

    // 本例是fallback
    public CommonResult handlerFallback(Long id, Throwable e){
        Payment payment = new Payment(id, null);
        return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment);
    }

    // blockHandler
    public CommonResult blockHandler(Long id, BlockException exception){
        Payment payment = new Payment(id, null);
        return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment);
    }

@SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler")// 配置了blockHandler和fallback

配置了blockHandler和fallback

设置流控规则:

在这里插入图片描述

1秒一个正常访问,但是超过阈值,调用blockHandler方法:

在这里插入图片描述

程序异常测试,调用fallback方法:

在这里插入图片描述

但是访问次数超过阈值,一样还会报blockHandler方法:

在这里插入图片描述

若blockHandler和fallback都进行了配置,则被限流而抛出BlockException时只会进入blockHandler处理逻辑。

忽略属性
@SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler",exceptionsToIgnore = {IllegalArgumentException.class}) // 配置了blockHandler和fallback

exceptionsToIgnore={异常1,异常2,…}

这样可以忽略掉某些程序异常,就是这些忽略的异常报错了,不会跳到fallback兜底方法,正常error页面。

Feign系列

新建提供者的feign模块,只需一个模块

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

直接写接口,无需配置和启动类:

接口跟服务提供者的controller一样

/**
 * @author wzq
 * @version 1.0
 * @date 2020/03/07
 */
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallback.class)
public interface PaymentFeign {

    @GetMapping("/paymentSQL/{id}")
    CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

Fallback熔断实现,对应该接口的方法:

/**
 * @author wzq
 * @version 1.0
 * @date 2020/03/07
 */
@Component
public class PaymentFallback implements PaymentFeign {
    
    // 熔断
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(444, "fallback");
    }
    
}

服务消费者:

引入需要feign接口的模块依赖

启动类加@EnableFeignClients

yml配置加:

#激活sentinel对feign的支持
feign:  
  sentinel:    
    enabled: true

contoller调用:

    @Resource
    private PaymentFeign paymentFeign;

    // 直接调用feign
    @GetMapping("/consumer/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id{
        return paymentFeign.paymentSQL(id);
    }

这样接口报错(程序错误和控制台违规)就会调用PaymentFallback的降级熔断方法

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

框架比较:

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

规则持久化

不配每次重启微服务,之前在sentinel控制台上设置的规则就会不见。

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

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

<!--  sentinel-datasource-nacos 后续持久化用   -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        # Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        # sentinel dashboard 地址
        dashboard: 192.168.169.130:8080
        # 默认为8719,如果被占用会自动+1,直到找到为止
        port: 8719
      # 流控规则持久化到nacos
      datasource:
        dsl:
          nacos:
            server-addr: 192.168.169.130:8848
            data-id: ${spring.application.name}
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: flow
management:
  endpoints:
    web:
      exposure:
        include: "*"

添加Nacos业务规则配置:

{
    "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:是否集群

在这里插入图片描述

重启后,访问微服务任一接口就出现原来的配置,持久化,可以说是初始化,之后可以改,一直存在nacos。

在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring Cloud Alibaba Sentinel是一个基于Java的开源框架,提供了熔断、降级、限流、系统负载保护等功能,可以帮助开发者实现微服务架构中的高可用性和稳定性。下面是一个使用Spring Cloud Alibaba Sentinel实现熔断与限流的项目介绍。 1. 创建Spring Boot项目 首先,需要创建一个Spring Boot项目,并添加Spring Cloud Alibaba Sentinel的依赖: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.3.RELEASE</version> </dependency> ``` 2. 配置Sentinel Dashboard Sentinel Dashboard是Sentinel的可视化管理平台,可以通过它来查看应用程序的运行状况、配置规则等。需要在项目中添加Sentinel Dashboard的依赖: ```xml <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel-datasource-nacos</artifactId> <version>2.2.3.RELEASE</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-sentinel-dashboard</artifactId> <version>2.1.1.RELEASE</version> </dependency> ``` 同时,在application.properties文件中添加以下配置: ```properties # Sentinel Dashboard配置 spring.cloud.sentinel.transport.dashboard=localhost:8080 # Nacos配置 spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.nacos.discovery.namespace= spring.cloud.nacos.discovery.username= spring.cloud.nacos.discovery.password= ``` 启动项目后,访问http://localhost:8080即可进入Sentinel Dashboard界面。 3. 实现熔断与限流 在项目中可以通过注解方式实现熔断与限流功能。例如,在Controller类中添加以下代码: ```java @RestController public class HelloController { @GetMapping("/hello") @SentinelResource(value = "hello", fallback = "fallback") public String hello(@RequestParam(required = false) String name) { if(StringUtils.isEmpty(name)) { throw new IllegalArgumentException("name is empty"); } return "Hello, " + name; } public String fallback(String name) { return "fallback " + name; } } ``` @SentinelResource注解指定了资源名称为hello,同时指定了fallback方法用于处理熔断降级。可以通过Sentinel Dashboard配置熔断规则和流量控制规则。 以上就是使用Spring Cloud Alibaba Sentinel实现熔断与限流的项目介绍。使用Sentinel可以帮助我们更好地保障应用程序的稳定性和可用性,避免因为异常情况导致系统崩溃。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wzq_55552

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值