SpringCloud Sentinel实战限流熔断降级应用

目录

  • 1 Sentinel核心库
    • 1.1 Sentinel介绍
    • 1.2 Sentinel核心功能
      • 1.2.1 流量控制
      • 1.2.2 熔断降级
    • 2 Sentinel 限流熔断降级
      • 2.1 @SentinelResource定义资源
      • 2.2 Sentinel的规则
        • 2.2.1 流量控制规则 (FlowRule)
        • 2.2.2 熔断降级规则 (DegradeRule)
        • 2.2.3 系统保护规则 (SystemRule)
        • 2.2.4 访问控制规则 (AuthorityRule)
        • 2.2.5 热点规则 (ParamFlowRule)
      • 2.3 OpenFeign支持
        • 2.3.1 fallback
        • 2.3.2 fallbackFactory


1 Sentinel核心库

Sentinel主页 https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5

1.1 Sentinel介绍

SpringCloud Sentinel实战限流熔断降级应用_第1张图片

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

1)Sentinel核心组件

1:核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。

2:控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。

2)同组件功能对比

参看:https://github.com/alibaba/Sentinel/wiki/%E5%9C%A8%E7%94%9F%E4%BA%A7%E7%8E%AF%E5%A2%83%E4%B8%AD%E4%BD%BF%E7%94%A8-Sentinel#%E5%90%8C%E7%B1%BB%E7%BB%84%E4%BB%B6%E5%8A%9F%E8%83%BD%E5%AF%B9%E6%AF%94

对比内容 Sentinel Hystrix
隔离策略 信号量隔离 线程池隔离/信号量隔离
熔断降级策略 基于响应时间或失败比率 基于失败比率
实时指标实现 滑动窗口 滑动窗口(基于 RxJava)
规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件的形式
基于注解的支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 不支持
流量整形 支持慢启动、匀速器模式 不支持
系统负载保护 支持 不支持
控制台 开箱即用,可配置规则、查看秒级监控、机器发现等 不完善
常见框架的适配 Servlet、Spring Cloud、Dubbo、gRPC 等 Servlet、Spring Cloud Netflix

3)Sentinel基本概念

  • 资源

资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。

只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

  • 规则

围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

1.2 Sentinel核心功能

1.2.1 流量控制

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

SpringCloud Sentinel实战限流熔断降级应用_第2张图片

流量控制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、系统负载等;
  • 控制的效果,例如直接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。

1.2.2 熔断降级

1)什么是熔断降级

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

SpringCloud Sentinel实战限流熔断降级应用_第3张图片

Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。

2)Sentinel熔断降级设计

Hystrix 通过 线程池隔离 的方式,来对依赖(在 Sentinel 的概念中对应 资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。

Sentinel熔断降级设计:

并发线程数限制:和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。

响应时间降级:除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

3)系统自适应保护

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

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

2 Sentinel 限流熔断降级

Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。我们先来学习Sentinel 核心库的使用,后面再学习Dashboard使用。

SpringCloud Sentinel实战限流熔断降级应用_第4张图片

在我们项目中,用户请求通过hailtaxi-gateway路由到hailtaxi-driver或者hailtaxi-order,还有可能在hailtaxi-order中使用feign调用hailtaxi-driver,所以我们有可能在单个服务中实现熔断限流,也有可能要集成feign调用实现熔断限流,还有可能在微服务网关中实现熔断限流。我们接下来一步一步实现每一种熔断限流操作。

使用Spring Cloud Alibaba Sentinel

使用手册:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel

首先需要引入spring-cloud-starter-alibaba-sentinel依赖,并使用@SentinelResource标识资源。

hailtaxi-driver工程中引入spring-cloud-starter-alibaba-sentinel依赖,依赖如下:


<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    <version>2.2.5.RELEASEversion>
dependency>

2.1 @SentinelResource定义资源

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

具体参考:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

value 资源名称,必需项(不能为空)
blockHandler / blockHandlerClass blockHandler 对应处理 BlockException 的函数名称,可选项。 ♞ blockHandler 函数访问范围需要是 public; ♞ 返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。 ♞ blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
fallback / fallbackClass fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求: ♞ 返回值类型必须与原函数返回值类型一致; ♞ 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。 ♞ fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
defaultFallback(1.6.0 开始) 默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求: ♞ 返回值类型必须与原函数返回值类型一致; ♞ 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。 ♞ defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore(1.6.0 开始) 用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
entryType entry 类型,可选项(默认为 EntryType.OUT)

blockHandler/blockHandlerClass

hailtaxi-driver中找到DriverController中的info方法,用户在打车的时候,会查询司机信息,如果司机不存在,此时会报错,代码改造如下:

/****
     * 司机信息
     */
@GetMapping(value = "/info/{id}")
//@RequestMapping(value = "/info/{id}")
public Driver info(@PathVariable(value = "id")String id){ // 去掉参数上的HttpServletRequest request
    log.info("当前服务占用的端口为:{}",port);
    Driver driver = driverService.findById(id);
    if (driver==null) {
        throw new RuntimeException("司机id="+id+"不存在");
    }
    return driver;
}

如果此时访问:http://localhost:18081/driver/info/3 查询司机信息,如果没有ID为3的司机信息,会报如下错误,

SpringCloud Sentinel实战限流熔断降级应用_第5张图片

这种体验非常差,我们可以集成Sentinel使用@SentinelResourceblockHandler返回默认错误信息,形成降级!!!

1、Sentinel 支持在程序中抛出它定义的BlockException异常,该异常会被Sentinel捕获,然后走降级方法,

info()方法添加一个@SentinelResource注解,用来标注资源,表示当前方法需要执行限流、降级,在注解中添加value属性,用来标注资源,说白了就是给当前资源起个名字,blockHandler用来表示当前方法发生BlockException异常的时候,将处理流程交给指定的方法blockExHandler()处理,此时blockExHandler()方法必须和抛出异常的方法在同一个类中,这是一种降级操作,代码如下:

/****
     * 司机信息
     */
@SentinelResource(value = "info",blockHandler = "blockExHandler")
@RequestMapping(value = "/info/{id}")
public Driver info(@PathVariable(value = "id")String id) throws BlockException {
    log.info("当前服务占用的端口为:{}",port);
    Driver driver = driverService.findById(id);
    if (driver==null) {
        //throw new RuntimeException("司机id="+id+"不存在");
        throw new SystemBlockException("info", "司机id="+id+"不存在",null); // 抛出BlockException
    }
    return driver;
}

/**
     * info资源出现BlockException后的降级处理
     */
public Driver blockExHandler(String id,BlockException e) {
    Driver driver = new Driver();
    driver.setId(id);
    driver.setName("系统繁忙,稍后再试");
    return driver;
}

SpringCloud Sentinel实战限流熔断降级应用_第6张图片

注意:

如果blockHandler方法和资源方法不在同一个类中,我们可以在@SentinelResource中添加blockHandlerClass属性,指定降级处理类的方法所在的类,且要求blockHandler方法是静态的,代码如下:

@SentinelResource(value = "info",blockHandler = "blockExHandler",blockHandlerClass = "xxx.xxx.Xxxx")

2、启动测试,访问:http://localhost:18081/driver/info/3 测试出错效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第7张图片

fallback/fallbackClass

1、如果我们希望抛出任何异常都能处理,都能调用默认处理方法,而并非只是BlockException异常才调用,此时可以使用@SentinelResourcefallback属性,代码如下:

/****
     * 司机信息
     */
@SentinelResource(value = "info"/*,blockHandler = "blockExHandler"*/,fallback = "exHandler")
@RequestMapping(value = "/info/{id}")
public Driver info(@PathVariable(value = "id")String id) throws BlockException {
    log.info("当前服务占用的端口为:{}",port);
    Driver driver = driverService.findById(id);
    if (driver==null) {
        throw new RuntimeException("司机id="+id+"不存在");
        // throw new SystemBlockException("info", "司机id="+id+"不存在",null); // 抛出BlockException
    }
    return driver;
}
/**
     * info资源出现任何类型异常后的降级处理
     * 方法参数可以添加一个Throwable 类型的参数,也可不添加
     */
public Driver exHandler(String id,Throwable e) {
    Driver driver = new Driver();
    driver.setId(id);
    driver.setName("系统繁忙,稍后再试");
    return driver;
}

注意:

如果fallback方法和当前的资源方法不在同一个类中,可以使用@SentinelResource注解的fallbackClass实现,也要求fallback方法是静态的,代码如下:

@SentinelResource(value = "info",fallback ="exHandler" ,fallbackClass = "xx.xxx.xxx.xx.Xxx")

2、访问 http://localhost:18081/driver/info/3 测试出错效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第8张图片

defaultFallback

上面无论是blockHandler还是fallback,每个方法发生异常,都要为方法独立创建一个处理异常的方法,效率非常低,我们可以使用@SentinelResource注解的defaultFallback属性,为一个类指定一个全局的处理错误的方法,代码如下:

@RestController
@RequestMapping(value = "/driver")
@Slf4j
@RefreshScope
@SentinelResource(defaultFallback = "defaultExHandler")
public class DriverController {
    @Autowired
    private DriverService driverService;

    public Driver defaultExHandler(Throwable e) {
        Driver driver = new Driver();
        driver.setName("系统繁忙,稍后再试");
        return driver;
    }

    /****
     * 司机信息
     */
    //@SentinelResource(value = "info"/*,blockHandler = "blockExHandler"*/,fallback = "exHandler")
    @SentinelResource("info")
    @RequestMapping(value = "/info/{id}")
    public Driver info(@PathVariable(value = "id")String id) throws BlockException {
        log.info("当前服务占用的端口为:{}",port);
        Driver driver = driverService.findById(id);
        if (driver==null) {
            throw new RuntimeException("司机id="+id+"不存在");
            // throw new SystemBlockException("info", "司机id="+id+"不存在",null); // 抛出BlockException
        }
        return driver;
    }

访问 http://localhost:18081/driver/info/3 效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第9张图片

2.2 Sentinel的规则

查看:https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E8%A7%84%E5%88%99%E7%9A%84%E7%A7%8D%E7%B1%BB

Sentinel 的所有规则都可以在内存态中动态地查询及修改,修改之后立即生效。同时 Sentinel 也提供相关 API,供您来定制自己的规则策略。

Sentinel 支持以下几种规则:流量控制规则熔断降级规则系统保护规则来源访问控制规则热点参数规则

2.2.1 流量控制规则 (FlowRule)

查看:

https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6%E8%A7%84%E5%88%99-flowrule

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

流量规则的定义,重要属性如下:

Field 说明 默认值
resource 资源名,资源名是限流规则的作用对象
count 限流阈值
grade 限流阈值类型,QPS 模式(1)或并发线程数模式(0) QPS 模式
limitApp 流控针对的调用来源 default,代表不区分调用来源
strategy 调用关系限流策略:直接、链路、关联 根据资源本身(直接)
controlBehavior 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 直接拒绝
clusterMode 是否集群限流

同一个资源可以同时有多个限流规则,检查规则时会依次检查

strategy限流策略说明:

直接:资源达到限流条件时,直接限流。

关联:A资源关联B资源,当关联的B资源达到阈值限流时,A资源也会被限流。

链路:对于某资源C,有两个入口,从资源A->C,从资源B->C, 通过指定入口资源可以达到只记录从该入口进来的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以对其限流)。

controlBehavior流控说明:

直接拒绝:请求直接失败。
WarmUp:当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
排队等待:排队处理请求。

理解上面规则的定义之后,我们可以通过调用 FlowRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

QPS流量控制

1、我们先实现基于QPS流量控制,在hailtaxi-driverDriverApplication启动类上添加如下方法加载限流规则,当DriverApplication初始化完成之后加载规则,代码如下:

/***
 * 初始化规则
 */
@PostConstruct
private void initFlowRule() {
    //规则集合
    List<FlowRule> rules = new ArrayList<FlowRule>();
    //定义一个规则
    FlowRule rule = new FlowRule("info");
    // 设置阈值
    rule.setCount(2);
    //设置限流阈值类型
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
    //default,代表不区分调用来源
    rule.setLimitApp("default");
    //设置流控效果
    rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
    //将定义的规则添加到集合中
    rules.add(rule);
    //加载规则
    FlowRuleManager.loadRules(rules);
}

2、访问 http://localhost:18081/driver/info/1 此时不会抛出异常,但是频繁刷新,则会调用降级方法,效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第10张图片

线程数流量控制

1、我们修改限流阈值类型,代码如下:

@PostConstruct
public void initFlowRule() {
    //规则集合
    List<FlowRule> rules = new ArrayList<>();
    // 定义一个规则
    FlowRule rule = new FlowRule("info");
    // 设置基流量控制 的类型
    rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);//默认是qps
    //设置流量阈值
    rule.setCount(2);
    // 将 规则添加到 集合中
    rules.add(rule);
    // 加载规则
    FlowRuleManager.loadRules(rules);
}

2、此时再来访问http://localhost:18081/driver/info/1我们发现用浏览器无论怎么访问都不会出现降级现象,但是如果用Jmeter模拟多个线程,效果就不一样了,效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第11张图片

2.2.2 熔断降级规则 (DegradeRule)

参看:

https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7%E8%A7%84%E5%88%99-degraderule

https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7

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

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 引入)

同一个资源可以同时有多个降级规则。

理解上面规则的定义之后,我们可以通过调用 DegradeRuleManager.loadRules() 方法来用硬编码的方式定义熔断规则,

1、在DriverApplication规则定义如下:

@PostConstruct
public void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    // 设置资源名称
    rule.setResource("info");
    /**
         * 设置熔断策略
         * DEGRADE_GRADE_RT:平均响应时间
         * DEGRADE_GRADE_EXCEPTION_RATIO:异常比例数量
         * DEGRADE_GRADE_EXCEPTION_COUNT:异常数
         */
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    //设置阈值
    rule.setCount(2);
    //设置 熔断时长
    rule.setTimeWindow(30);
    // 统计时长(单位为 ms) 默认1000
    rule.setStatIntervalMs(60*1000);
    //将规则添加到集合中
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

2、首先访问:http://localhost:18081/driver/info/1 ,确保没问题,

其次访问:http://localhost:18081/driver/info/3,多访问几次,造成熔断(5+2=7)

然后在访问:http://localhost:18081/driver/info/1,会发现已经有熔断降级效果了,

且查看服务控制台,发现不会有信息输出,表明已经熔断了,且从页面展示效果来看走了降级

SpringCloud Sentinel实战限流熔断降级应用_第12张图片

等待30s后,再次访问:http://localhost:18081/driver/info/1,查看熔断是否结束!

2.2.3 系统保护规则 (SystemRule)

参考:

https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E7%B3%BB%E7%BB%9F%E4%BF%9D%E6%8A%A4%E8%A7%84%E5%88%99-systemrule

https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81

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

系统规则包含下面几个重要的属性:

Field 说明 默认值
highestSystemLoad load1 触发值,用于触发自适应控制阶段 -1 (不生效)
avgRt 所有入口流量的平均响应时间 -1 (不生效)
maxThread 入口流量的最大并发数 -1 (不生效)
qps 所有入口资源的 QPS -1 (不生效)
highestCpuUsage 当前系统的 CPU 使用率(0.0-1.0) -1 (不生效)

理解上面规则的定义之后,我们可以通过调用 SystemRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。

1、在hailtaxi-driverDriverApplication中创建如下方法,代码如下:

/***
 * 系统自我保护
 */
@PostConstruct
private void initSystemRule() {
    //系统自我保护集合
    List<SystemRule> rules = new ArrayList<>();
    //创建系统自我保护规则
    SystemRule rule = new SystemRule();
    //CPU使用率 值为[0,1],-1 (不生效)
    rule.setHighestCpuUsage(0.2);
    //所有入口资源的 QPS,-1 (不生效)
    rule.setQps(10);
    //入口流量的最大并发数,-1 (不生效)
    rule.setMaxThread(5);
    //所有入口流量的平均响应时间,单位:毫秒,-1 (不生效)
    rule.setAvgRt(1000);
    //load1 触发值,用于触发自适应控制阶段,系统最高负载,建议取值 CPU cores * 2.5
    rule.setHighestSystemLoad(20);
    //将规则加入到集合
    rules.add(rule);
    SystemRuleManager.loadRules(rules);
}

我们可以测试CPU使用率自我保护,使用jmeter测试如下:

SpringCloud Sentinel实战限流熔断降级应用_第13张图片

2.2.4 访问控制规则 (AuthorityRule)

参看:

https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E8%AE%BF%E9%97%AE%E6%8E%A7%E5%88%B6%E8%A7%84%E5%88%99-authorityrule

https://github.com/alibaba/Sentinel/wiki/%E9%BB%91%E7%99%BD%E5%90%8D%E5%8D%95%E6%8E%A7%E5%88%B6

很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的访问控制(黑白名单)的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。

授权规则,即黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:

  • resource:资源名,即规则的作用对象
  • limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB
  • strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式

了解了以上规则后,可以通过AuthorityRuleManager.loadRules来加载规则

1、在hailtaxi-driverDriverApplication中创建如下方法,代码如下:

@PostConstruct
    public void initAuthorityRule() {
        AuthorityRule rule = new AuthorityRule();
        rule.setResource("info");
        rule.setStrategy(RuleConstant.AUTHORITY_WHITE);
        rule.setLimitApp("127.0.0.1,appB");
        AuthorityRuleManager.loadRules(Collections.singletonList(rule));
    }

    /**
     * Sentinel提供了 RequestOriginParser 接口来处理访问来源,Sentinel保护的资源如果被访问,
     * 就会调用 RequestOriginParser解析访问来源
     */
    @Component
    public class IpLimiter implements RequestOriginParser{

        @Override
        public String parseOrigin(HttpServletRequest httpServletRequest) {
            return httpServletRequest.getRemoteAddr();
        }
    }

2、访问:http://localhost:18081/driver/info/1,不通过,走了降级

  访问:http://127.0.0.1:18081/driver/info/1
2.2.5 热点规则 (ParamFlowRule)

参看:https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8#%E7%83%AD%E7%82%B9%E8%A7%84%E5%88%99-paramflowrule

何为热点?热点即经常访问的数据。

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

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

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

SpringCloud Sentinel实战限流熔断降级应用_第14张图片

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

要使用热点参数限流功能,需要引入以下依赖,将该依赖加入到hailtaxi-driver中:


<dependency>
    <groupId>com.alibaba.cspgroupId>
    <artifactId>sentinel-parameter-flow-controlartifactId>
    <version>1.8.1version>
dependency>

然后为对应的资源配置热点参数限流规则,并在 entry 的时候传入相应的参数,即可使热点参数限流生效。

热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):

属性 说明 默认值
resource 资源名,必填
count 限流阈值,必填
grade 限流模式 QPS 模式
durationInSec 统计窗口时间长度(单位为秒),1.6.0 版本开始支持 1s
controlBehavior 流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 快速失败
maxQueueingTimeMs 最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 0ms
paramIdx 热点参数的索引,必填,对应 SphU.entry(xxx, args) 中的参数索引位置
paramFlowItemList 参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型
clusterMode 是否是集群参数流控规则 false
clusterConfig 集群流控相关配置

我们可以通过 ParamFlowRuleManagerloadRules 方法更新热点参数规则

1、在DriverController中创建一个司机筛选方法,比如根据城市来筛选,在DriverController中创建一个方法:

/***
     * 搜素指定城市的司机
     */
@SentinelResource(value = "search")
@GetMapping(value = "/search/{city}")
public Driver search(@PathVariable(value = "city")String city){
    System.out.println("查询的司机所在城市:"+city);
    //假设查询到了一个司机信息
    Driver driver = new Driver();
    driver.setName("唐僧老师");
    driver.setId("No.1");
    return driver;
}

2、对热门参数进行控制,对热点数据执行特殊限流,比如资源参数列表中的第一个参数值为恩施的时候执行限流,在DriverApplication中创建限流配置,代码如下:

/***
 * 热点参数初始化
 */
@PostConstruct
private static void initParamFlowRules() {
    ParamFlowRule rule = new ParamFlowRule("search")
            //参数下标为0
            .setParamIdx(0)
            //限流模式为QPS
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            //统计窗口时间长度(单位为秒)
            .setDurationInSec(10)
            //流控效果(支持快速失败和匀速排队模式)
            //CONTROL_BEHAVIOR_DEFAULT:限流行为,直接拒绝
            //CONTROL_BEHAVIOR_WARM_UP:限流行为,匀速排队
            //CONTROL_BEHAVIOR_RATE_LIMITER:限流行为,匀速排队
            .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)
            //最大排队等待时长(仅在匀速排队模式生效  CONTROL_BEHAVIOR_RATE_LIMITER)
            //.setMaxQueueingTimeMs(600)
            //最大阈值为5
            .setCount(5);

    // 为特定参数单独设置规则
    //如下配置:当参数值为恩施的时候,阈值到达2的时候则执行限流
    ParamFlowItem item = new ParamFlowItem()
            //参数类型为String类型
            .setClassType(String.class.getName())
            //设置阈值为2
            .setCount(2)
            //需要统计的值
            .setObject(String.valueOf("恩施"));
    rule.setParamFlowItemList(Collections.singletonList(item)); //返回的是不可变的集合,但是这个长度的集合只有1,可以减少内存空间
    //加载热点数据
    ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
}

2、我们访问 http://localhost:18081/driver/search/天津/ 的时候,连续执行5次,才会限流,

我们访问 http://localhost:18081/driver/search/恩施/ 的时候,连续执行2次,就会限流,

2.3 OpenFeign支持

参看:

https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel#feign-%E6%94%AF%E6%8C%81

Sentinel 适配了 Feign 组件。如果想使用,除了外还需要 2 个步骤:

1:引入 `spring-cloud-starter-alibaba-sentinel` 的依赖
2:加入 spring-cloud-starter-openfeign 依赖
3:配置文件打开 Sentinel 对 Feign 的支持:feign.sentinel.enabled=true

SpringCloud Sentinel实战限流熔断降级应用_第15张图片

在上面案例中,我们可以实现用户打车成功调用hailtaxi-order执行下单,并且通过feign调用hailtaxi-driver修改司机状态,此时我们可以使用Sentinel实现Feign调用降级、限流。

注意:

在进行操作之前,可以先将hailtaxi-driver中的访问控制规则注释掉,注释掉DriverApplication#initAuthorityRuleDriverApplication#initSystemRule上的注解即可

1、在hailtaxi-order中引入sentinelOpenFeign依赖,配置如下:


<dependency>
    <groupId>com.alibaba.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
    <version>2.2.5.RELEASEversion>
dependency>


<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-openfeignartifactId>
dependency>

2、在hailtaxi-order的配置文件中开启Feign支持sentinel,配置如下:

feign:
  #开启Sentinel对Feign的支持
  sentinel:
    enabled: true

注意:现在配置信息都存放在了nacos中,所以找到hailtaxi-order.yaml的Data ID配置,将以上配置添加进去!

SpringCloud Sentinel实战限流熔断降级应用_第16张图片

注意修改完后一定要发布才能生效!!!

3、因为hailtaxi-order要通过openfeign去调用hailtaxi-driver中的DriverController#status方法,改造一下该方法

/****
     * 更新司机信息
     */
@PutMapping(value = "/status/{id}/{status}")
public Driver status(@PathVariable(value = "id")String id,@PathVariable(value = "status")Integer status) throws Exception {
    log.info("当前服务占用的端口为:{}",port);
    //修改状态
    driverService.update(id,status);
    //修改状态后的司机信息
    Driver driver = driverService.findById(id);
    if (driver == null) {
        throw new RuntimeException("学生id="+id+",不存在");
    }
    return driver;
}

模拟被调用服务出现异常的情况

3、先验证正确性,启动hailtaxi-gatewayhailtaxi-orderhailtaxi-driver服务,使用postman访问:

http://localhost:8001/order

4、为了测试程序异常能实现降级操作,我们在hailtaxi-order中将OrderInfoController.add()方法的司机ID改成一个不存在的司机ID,让程序报错,测试降级处理,代码如下:

/***
     * 下单
     */
@PostMapping
public OrderInfo add(){
    //修改司机信息  司机ID=1
    Driver driver = driverFeign.status("3",2);// 改成一个不存在的id
    //创建订单
    OrderInfo orderInfo = new OrderInfo("No"+((int)(Math.random()*10000)), (int)(Math.random()*100), new Date(), "深圳北站", "罗湖港", driver);
    orderInfoService.add(orderInfo);
    return orderInfo;
}

SpringCloud Sentinel实战限流熔断降级应用_第17张图片

5、再次启动测试:

SpringCloud Sentinel实战限流熔断降级应用_第18张图片

注意:服务调用出错要进行降级操作有两个地方都可以进行,一是在被调用方进行降级,一是在调用方进行降级,

在被调用方进行降级前面已经讲过了,所以接下来讲解如何在调用方(openfeign)结合 sentinel 进行降级处理。

2.3.1 fallback

hailtaxi-api模块中找到DriverFeign接口:

1、为Feign接口创建一个实现类:com.itheima.driver.feign.fallback.DriverFeignFallback,在实现类中处理程序异常降级处理方法,代码如下:

package com.itheima.driver.feign.fallback;

import com.itheima.driver.feign.DriverFeign;
import com.itheima.driver.model.Driver;
import org.springframework.stereotype.Component;

@Component
public class DriverFeignFallback implements DriverFeign {

    /**
     * status()降级处理方法
     */
    @Override
    public Driver status(String id, Integer status) {
        Driver driver = new Driver();
        driver.setId(id);
        driver.setStatus(status);
        driver.setName("系统比较繁忙,请您稍后再试!");
        return driver;
    }
}

2、在DriverFeign接口上添加fallback属性指定降级处理的类,代码如下:

@FeignClient(value = "hailtaxi-driver",fallback = DriverFeignFallback.class)

注意:修改了hailtaxi-api模块,最好cleanpackage!!

3、启动运行,会发生如下问题:

java.lang.AbstractMethodError: com.alibaba.cloud.sentinel.feign.SentinelContractHolder.parseAndValidatateMetadata(Ljava/lang/Class;)Ljava/util/List;

出现上面问题的主要原因是当前SpringCloud版本存在问题。

Hoxton.SR1` 中,`fegin.context`接口方法的定义为`parseAndValidatateMetadata` `Hoxton.SR3` 中,`fegin.context`接口方法的定义为`parseAndValidateMetadata

我们现在需要把Hoxton.SR1换成Hoxton.SR3,因此需要在hailtaxi-parent修改SpringCloud版本:

SpringCloud Sentinel实战限流熔断降级应用_第19张图片

SpringCloud版本升级至Hoxton.SR3同时将SpringBoot版本升级至2.2.10,如上图:

https://github.com/spring-cloud/spring-cloud-release/wiki/Spring-Cloud-Hoxton-Release-Notes

此时我们测试,效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第20张图片

2.3.2 fallbackFactory

我们可以为DriverFeign接口创建一个降级处理的工厂对象,在工厂对象中处理程序异常降级处理方法,用工厂对象处理可以拿到异常信息,代码如下:

1、创建:com.itheima.driver.feign.fallback.DriverFeignFallbackFactory

package com.itheima.driver.feign.fallback;

import com.itheima.driver.feign.DriverFeign;
import com.itheima.driver.model.Driver;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

@Component
public class DriverFeignFallbackFactory implements FallbackFactory<DriverFeign> {
    @Override
    public DriverFeign create(Throwable throwable) {
        return new DriverFeign() {
            /**
             * status()降级处理方法
             */
            @Override
            public Driver status(String id, Integer status) {
                Driver driver = new Driver();
                driver.setId(id);
                driver.setStatus(status);
                driver.setName("系统比较繁忙,请您稍后再试!");
                return driver;
            }
        };
    }
}

2、在DriverFeign接口上添加fallbackFactory属性指定讲解处理的类,代码如下:

@FeignClient(value = "hailtaxi-driver",fallbackFactory = DriverFeignFallbackFactory.class)

3、再次启动测试,效果如下:

SpringCloud Sentinel实战限流熔断降级应用_第21张图片

你可能感兴趣的:(sentinel)