第十章 Sentinel微服务熔断限流

1,230 阅读26分钟

通过第四章对于Resilience4j的学习,我们知道微服务容错组件提供了限流、隔离、降级、熔断等手段,可以有效保护我们的微服务系统。Spring Cloud Alibaba也提供了微服务容错组件Sentinel。

10.1 Sentinel基本概念

\

10.1.1 基本指标

  1. 响应时间(RT)
    响应时间是指系统对请求作出响应的时间。直观上看,这个指标与人对软件性能的主观感受是非常一致的,因为它完整地记录了整个计算机系统处理请求的时间。由于一个系统通常会提供许多功能,而不同功能的处理逻辑也千差万别,因而不同功能的响应时间也不尽相同,甚至同一功能在不同输入数据的情况下响应时间也不相同。所以,在讨论一个系统的响应时间时,人们通常是指该系统所有功能的平均时间或者所有功能的最大响应时间。当然,往往也需要对每个或每组功能讨论其平均响应时间和最大响应时间。
    对于单机的没有并发操作的应用系统而言,人们普遍认为响应时间是一个合理且准确的性能指标。需要指出的是,响应时间的绝对值并不能直接反映软件的性能的高低,软件性能的高低实际上取决于用户对该响应时间的接受程度。对于一个游戏软件来说,响应时间小于100毫秒应该是不错的,响应时间在1秒左右可能属于勉强可以接受,如果响应时间达到3秒就完全难以接受了。而对于编译系统来说,完整编译一个较大规模软件的源代码可能需要几十分钟甚至更长时间,但这些响应时间对于用户来说都是可以接受的。
  2. 吞吐量(Throughput)
    吞吐量是指系统在单位时间内处理请求的数量。对于无并发的应用系统而言,吞吐量与响应时间成严格的反比关系,实际上此时吞吐量就是响应时间的倒数。前面已经说过,对于单用户的系统,响应时间(或者系统响应时间和应用延迟时间)可以很好地度量系统的性能,但对于并发系统,通常需要用吞吐量作为性能指标。
    对于一个多用户的系统,如果只有一个用户使用时系统的平均响应时间是t,当有你n个用户使用时,每个用户看到的响应时间通常并不是n×t,而往往比n×t小很多(当然,在某些特殊情况下也可能比n×t大,甚至大很多)。这是因为处理每个请求需要用到很多资源,由于每个请求的处理过程中有许多不走难以并发执行,这导致在具体的一个时间点,所占资源往往并不多。也就是说在处理单个请求时,在每个时间点都可能有许多资源被闲置,当处理多个请求时,如果资源配置合理,每个用户看到的平均响应时间并不随用户数的增加而线性增加。实际上,不同系统的平均响应时间随用户数增加而增长的速度也不大相同,这也是采用吞吐量来度量并发系统的性能的主要原因。一般而言,吞吐量是一个比较通用的指标,两个具有不同用户数和用户使用模式的系统,如果其最大吞吐量基本一致,则可以判断两个系统的处理能力基本一致。
  1. 并发用户数
    并发用户数是指系统可以同时承载的正常使用系统功能的用户的数量。与吞吐量相比,并发用户数是一个更直观但也更笼统的性能指标。实际上,并发用户数是一个非常不准确的指标,因为用户不同的使用模式会导致不同用户在单位时间发出不同数量的请求。一网站系统为例,假设用户只有注册后才能使用,但注册用户并不是每时每刻都在使用该网站,因此具体一个时刻只有部分注册用户同时在线,在线用户就在浏览网站时会花很多时间阅读网站上的信息,因而具体一个时刻只有部分在线用户同时向系统发出请求。这样,对于网站系统我们会有三个关于用户数的统计数字:注册用户数、在线用户数和同时发请求用户数。由于注册用户可能长时间不登陆网站,使用注册用户数作为性能指标会造成很大的误差。而在线用户数和同事发请求用户数都可以作为性能指标。相比而言,以在线用户作为性能指标更直观些,而以同时发请求用户数作为性能指标更准确些。
  2. QPS每秒查询率(Query Per Second)
    每秒查询率QPS是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准,在因特网上,作为域名系统服务器的机器的性能经常用每秒查询率来衡量。对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。 (看来是类似于TPS,只是应用于特定场景的吞吐量)

10.1.2 什么是Sentinel

\

Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。

官网:github.com/alibaba/Sen…

2012年,Sentinel诞生于阿里巴巴,其主要目标是流量控制。2013-2017年,Sentinel迅速发展,并成为阿里巴巴所有微服务的基本组成部分。 它已在6000多个应用程序中使用,涵盖了几乎所有核心电子商务场景。2018年,Sentinel演变为一个开源项目。2020年,Sentinel Golang发布。

Sentinel作为保护我们微服务的中间件产品,它具有的主要功能,有如下几点。

  1. 流量控制

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

图10-1 Sentinel 流量控制

Sentinel流量控制有以下几个角度。

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

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

  1. 熔断降级

除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。

Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

  1. 熔断降级设计理念

在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。Hystrix 默认通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。

Sentinel 对这个问题采取了两种手段。

(1)通过并发线程数进行限制

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

(2)通过响应时间对资源进行降级

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

  1. 系统负载保护

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

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

\

10.1.3 Sentinel和Hystrix的区别

\

如图8-2所示,Sentinel和Hystrix区别如下。

图10-2 Sentinel和Hystrix区别

10.1.4 安装

\

1)下载

\

github.com/alibaba/Sen… 下载地址

\

2)启动

\

nohup java -jar sentinel-dashboard-1.8.1.jar  > myout.file 2>&1 &

\

3)访问

\

http://localhost:8080,用户名密码都是sentinel

\

10.2 Sentinel使用

10.2.1 入门案例

\

  1. pom依赖:

\

        <!-- 后续做Sentinel的持久化会用到的依赖 -->
        <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>

\

  1. yml 配置:

在common.yml中配置如下。

\

spring:
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    sentinel:
      transport:
        dashboard: localhost:8080 #sentinel dashboard地址
        port: 8719 #默认占用8719端口,如果8719被占用,依次+1,直到找到可用的端口 

\

  1. FlowLimitController

\

package com.lxs.springcloud.alibaba.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class FlowLimitController
{
    @GetMapping("/testA")
    public String testA()
    {
        return "------testA";
    }

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

}

\

sentinel采用懒加载方式,执行一次微服务调用,sentinel控制台即可监控到该微服务

\

10.2.2 流量控制

\

  1. QPS

\

如图10-3所示,配置QPS,配置配置最基本的流量控制。

图10-3 QPS流量控制

流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。配置参数解析如下。

  • 资源名:唯一名称,默认请求路径
  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
  • 阈值类型:
  • QPS(每秒请求数量):当调用改api的QPS达到阈值的时候,进行限流
  • 线程数,当调用该api的线程数达到阈值的时候,进行限流
  • 是否集群:不需要集群
  • 流控模式
  • 直接:api达到限流条件时,直接限流
  • 关联:当关联的资源达到阈值时,限流自己
  • 链路,只记录链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)
  • 流控效果:
  • 快速失败:直接失败,抛出异常
  • Warm UP:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
  • 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置QPS,否则无效

启动并测试,测试每秒钟超过1此api请求,抛出Blocked by Sentinel (flow limiting)

  1. 线程数

并发数控制用于保护业务线程池不被慢调用耗尽。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。测试代码如下。

 @GetMapping("/testA")
 public String testA() throws InterruptedException {
     TimeUnit.MILLISECONDS.sleep(2000);
     return "------testA";
 }

线程数限流配置如图10-4所示。

图10-4 线程数限流

测试线程超出阈值,直接抛出Blocked by Sentinel (flow limiting)

  1. 流控模式:直接

流控模式-直接失败,限流表现:当超过阀值,就会被降级,抛出异常Blocked by Sentinel (flow limiting),配置如图10-5所示。

图10-5 流控模式-直接


  1. 流控模式--关联

当关联的资源达到阈值,就限流自己,也就是当与testA关联的testB达到阈值,就限流自己,配置如图10-6所示。

图10-6 流控模式-关联

使用Postman请求/testB,效果:/testB达到访问阈值,/testA降级,Postman配置如图10-7所示。

图10-7 Postman发送请求


  1. 流控模式:链路

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

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

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

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

  1. 流控效果--预热

Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮,如图10-8所示。

图10-8 流控效果-预热

配置Warm Up流控效果,如图10-9所示,开始阈值默认3,经过预热时长5秒后,QPS阈值慢慢涨到10

图10-9 流控效果-预热

  1. 流控效果:匀速排队

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。该方式的作用如图10-10所示。

图10-10 流控效果-匀速排队

\

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

注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

图10-11 流控效果-匀速排队

使用Postman测试,配置如图10-12所示,根据控制台打印,发现每秒执行一次请求

图10-12 Postman发送请求

\

10.2.3 熔断降级


  1. 概述

\

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。服务调用关系如图10-13所示。

\

图10-13 服务调用关系

\

微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。


  1. 熔断策略

\

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 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。

\

  1. 慢调用比例

\

慢调用 RT表示最大的响应时间,请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。具体配置,如图10-14所示。

\

图10-14 慢比例配置

testD代码如下所示。

	@GetMapping("/testD")  
	public String testD() {  
	    try {  
	        TimeUnit.SECONDS.sleep(1);  
	    } catch (InterruptedException e) {  
	        e.printStackTrace();  
	    }  
	    log.info(Thread.currentThread().getName() + "\t" + "*****testD*****");  
	    return "****testD****";  
	} 

\

\

使用JMeter启动并发请求,配置如图10-15所示。

\

图10-15 JMeter配置

\

此时,JMeter并发请求访问时,符合慢比例调用条件,则/testD服务降级,抛出异常Blocked by Sentinel (flow limiting),关闭JMeter,1秒后, 熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。

\

  1. 异常比例

\

当单位统计时长内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成,则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0],代表 0% - 100%。异常比例配置,如图10-16所示。

\

图10-16 异常比例配置

\

testD中抛出0作为除数异常,代码如下。

	@GetMapping("/testD")  
	public String testD() {  
	  
	    int i = 1 / 0;  
	    log.info(Thread.currentThread().getName() + "\t" + "*****testD*****");  
	    return "****testD****";  
	}  

\

使用JMeter启动并发请求,如图10-15所示。

\

此时,JMeter并发请求访问时,符合异常比例条件,浏览器中访问/testD服务降级,抛出异常Blocked by Sentinel (flow limiting),关闭JMeter,3秒后, 熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求没有错误,恢复正常,否则会再次被熔断。

\

  1. 异常数

\

异常数 (ERROR_COUNT)是指当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常数配置,如图10-17所示。

\

图10-17 异常数限流配置

\

使用JMeter启动并发请求,如图10-15所示。

\

此时,JMeter并发请求访问时,符合异常数服务降级条件,浏览器中访问/testD服务降级,抛出异常Blocked by Sentinel (flow limiting),关闭JMeter,10秒后, 熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求没有错误,恢复正常,否则会再次被熔断。

\

10.2.4 热点参数限流

\

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

\

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

\

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

图10-18 热点参数

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

\

热点参数限流需要使用@SentinelResource注解,具体代码如下:

\

@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
                         @RequestParam(value = "p2", required = false) String p2) {
    //int age = 10/0;
    return "------testHotKey";
}

public String deal_testHotKey(String p1, String p2, BlockException exception) {
    return "------deal_testHotKey,o(╥﹏╥)o";  //sentinel默认抛出ParamFlowException
}

\

热点参数限流需要使用@SentinelResource注解,@SentinelResource功能类似@HystrixCommand,直接使用访问路径作为热点参数限流的资源名,设置无效,必须使用@SentinelResource定义,作为热点参数限流的资源名。

\

在sentinel控制台,热点参数限流,配置如图10-19所示。

\

图10-19 热点参数限流配置

\

如图所示,上面的配置表示限流p1参数,不限流p2参数。

热点参数限流,还可以添加参数例外项,配置如图10-20所示。

\

图10-20 热点参数配置

\

10.2.5 @SentinelResource详解

\

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

\

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

  • value:资源名称,必需项(不能为空)
  • entryType:entry 类型,可选项(默认为 EntryType.OUT
  • blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
  • fallback/fallbackClass: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.8.0 版本开始,defaultFallback 支持在类级别进行配置。

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

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

\

10.2.6 熔断具体应用

\

  1. Spring Cloud LoadBalancer方式

\

下面实现在订单支付熔断的具体用法,在订单微服务中使用RestTemplate调用支付微服务,配置服务限流和降级,代码如下。

\

    @GetMapping("/lb/{id}")
    @SentinelResource(value = "ribbon", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
    public ResponseEntity<String> consumerRibbon(@PathVariable("id") Long id) {
        String result = restTemplate.getForObject(SERVICE_URL + "/payment/" + id, String.class);
        if (4 == id) {
            throw new IllegalArgumentException("id=4,抛出非法参数异常");
        } else if (id == null) {
            throw new NullPointerException("空指针异常,id不存在。。。");
        }
        return ResponseEntity.ok("ribbon方式:订单调用支付成功, 订单号= " + id + ",调用结果=" + result);
    }

    public ResponseEntity<String> handlerFallback(@PathVariable("id") Long id, Throwable e) {
        return new ResponseEntity<String>("异常降级方法 e = " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    public ResponseEntity<String> blockHandler(@PathVariable("id") Long id, BlockException e) {
        return new ResponseEntity<String>("异常降级方法 e = " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
    }

\

设置限流规则,或者关闭支付微服务,可以看到服务降级的效果

\

2. Feign方式

\

PaymentService

\

@FeignClient(value = "payment-service", fallback = PaymentService.PaymentFallbackService.class)
public interface PaymentService {

    @GetMapping("/payment/{id}")
    public ResponseEntity<String> payment(@PathVariable("id") Long id);

    @Component
    static class PaymentFallbackService implements PaymentService {

        @Override
        public ResponseEntity<String> payment(Long id) {
            return new ResponseEntity<String>("feign调用,异常降级方法", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

}

\

OrderController

\

    @Autowired
    private PaymentService paymentService;

    @GetMapping("/feign/{id}")
    public ResponseEntity<String> consumerFeign(@PathVariable("id") Long id) {
        ResponseEntity<String> result = paymentService.payment(id);
        return ResponseEntity.ok("feign方式:订单调用支付成功, 订单号= " + id + ", 调用结果=" + result.getBody());
    }

\

common.yaml配置

\

#对Feign的支持
feign:
  sentinel:
    enabled: true

\

设置限流规则,或者关闭支付微服务,可以看到服务降级的效果

\

规则持久化

\

目前的sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以被持久化到 nacos 的数据库中。

\

  1. pom依赖:

\

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

\

  1. yml配置:

\

spring:
  cloud:
    sentinel:
      datasource:
        ds1:  
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}
            group: DEFAULT_GROUP
            data-type: json
            rule-type: flow

\

  1. 去nacos上创建一个dataid ,名字和yml配置的一致,json格式,内容如下:

\

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

\

  • resource:资源名
  • limitApp:来源应用
  • grade: 限流阈值类型(QPS 或并发线程数),0表示线程数,1表示QPS
  • count: 限流阈值
  • strategy: 调用关系限流策略
  • controlBehavior: 流量控制效果(0:直接拒绝、1:Warm Up、2:匀速排队)
  • clusterMode是否集群

\

  1. 启动应用,发现存在 关于 /testA 请求路径的流控规则

\

总结: 就是在 sentinel 启动的时候,去 nacos 上读取相关规则配置信息,实际上它规则的持久化,就是第三步,粘贴到nacos上保存下来,就算以后在 sentinel 上面修改了,重启应用以后也是有效的。