第十七章_Sentinel实现熔断与限流
1.Sentinel介绍
1.1是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 ==Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护==等多个维度保护服务的稳定性。
用来代替Hystrix
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- **完备的实时监控:**Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- **广泛的开源生态:**Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- **完善的 SPI 扩展点:**Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 的主要特性:
1.2Hystrix与Sentinel比较
Hystrix:
- 需要我们程序员自己手工搭建监控平台
- 没有一套web界面可以给我们进行更加细粒度化得配置流控、速率控制、服务熔断、服务降级
Sentinel:
- 单独一个组件,可以独立出来。
- 直接界面化的细粒度统一配置。
约定 > 配置 > 编码
都可以写在代码里面,但是我们本次还是大规模的学习使用配置和注解的方式,尽量少写代码
2.Sentinel下载安装运行
官方文档:Spring Cloud Alibaba Reference Documentation (spring-cloud-alibaba-group.github.io)
服务使用中的各种问题:
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
Sentinel 分为两个部分:
- ==核心库(Java 客户端)==不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- ==控制台(Dashboard)==基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
2.1安装步骤
(1)下载
地址:
(2)运行命令
前提:java8环境OK + 8080端口不能被占用
运行命令:
java -jar sentinel-dashboard-1.7.1.jar
(3)访问sentinel管理界面
3.初始化演示工程
3.1新建cloudalibaba-sentinel-service8401
(1)pom
<dependencies><!-- 后续做Sentinel的持久化会用到的依赖--><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><!-- springcloud alibaba nacos 依赖,Nacos Server 服务注册中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!-- springboot整合Web组件 --><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><!-- 日常通用jar包 --><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><dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --><groupId>springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency></dependencies>
(2)yml
(3)主启动
(4)controller层
(5)测试
启动成功之后,Sentinel控制台空空如也
原因:Sentinel采用懒加载,执行一次即可
localhost:8401/testB
4.流控规则
4.1基本介绍
(1)解释说明—重要
- 资源名:唯一名称,默认请求路径
- 针对来源:sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
- 阈值类型/单机值:
- QPS(每秒钟的请求数量):当调用该api就QPS达到阈值的时候,进行限流
- 线程数.当调用该api的线程数达到阈值的时候,进行限流
- 是否集群:不需要集群
- 流控模式:
- 直接:api达到限流条件时,直接限流。分为QPS和线程数
- 关联:当关联的资源达到阈值时,就限流自己。
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api级别的针对来源】
- 流控效果:
- 快速失败:直接抛异常
- warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设置的QPS阈值
- 排队等待:匀速排队,让请求以匀速通过,阈值类型必须设置为QPS,否则无效
(2)QPS和线程数的区别
- QPS 类似于银行的保安:所有的请求到Sentinel 后,他会根据阈值放行,超过报错
- 线程数类似于银行的窗口:所有的请求会被放进来,但如果阈值设置为1,那么其他的请求就会报错也就是,银行里只有一个窗口,一个人在办理业务中,其他人跑过来则会告诉你“现在不行,没到你”
(3)重要属性
4.2流控模式
(1)直接—默认
- 配置及说明:表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
测试:快速点击访问:http://localhost:8401/testA
(2)关联
-
解释:关联的资源达到阈值时,就限流自己
-
配置及说明:当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,
使用postman新建多线程集合组:
运行后发现testA挂了,点击访问:http://localhost:8401/testA
(3)链路
只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】
4.3流控效果
(1)快速失败
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
(2)预热
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
(3)WarmUp配置
默认 coldFactor 为 3,即请求QPS从(阈值 / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值
系统配置系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后==过了5秒后阀值才慢慢升高恢复到10==。效果为:开始访问 localhost:/testA 时每秒请求别超过10/3个才能正常访问,5秒后可以接受的请求可以达到每秒10次。
- 应用场景
秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值
(4)排队等待
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒
说明:匀速排队,阈值必须设置为QPS
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考流量控制 - 匀速器模式 ,具体的例子可以参见 PaceFlowDemo。
5.降级规则
5.1熔断降级概述
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
5.2降级策略介绍
-
RT(平均响应时间,秒级)
-
平均响应时间超出阈值且在时间窗口内通过的请求>=5,两个条件同时满足后触发降级
-
窗口期过后关闭断路器
-
RT最大4900(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
-
-
异常比列(秒级)
- QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
-
异常数**(分钟级)**
- 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
5.3RT
(1)介绍
平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意Sentinel 默认统计的RT上限是4900 ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。
-
注意:Sentinel 1.7.0才有平均响应时间(
DEGRADE_GRADE_RT
),Sentinel 1.8.0的没有这项,取而代之的是慢调用比例 (SLOW_REQUEST_RATIO
)。 -
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。熔断降级 · alibaba/Sentinel Wiki · GitHub
如图为Sentinel1.7.0的降级规则为RT策略流程图
(2)实操
在controller中加入textD
配置:当1s内持续进入5个请求(默认5个请求),对应时刻的平均响应时间(秒级)均超过0.2s( count,以ms为单位),那么在接下的时间窗口1s之内,对这个方法的调用都会自动地熔断
- Jmeter压测:永远一秒钟打进来10个线程(大于5个)调用testD
测试
5.4异常比例
(1)介绍
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且==每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count)==之后,资源进入降级状态,即在接下的时间窗口( DegradeRule 中的 timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0],代表0% -100%。
- 注意:与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。熔断降级 · alibaba/Sentinel Wiki · GitHub
接下来讲解Sentinel 1.7.0
如图为Sentinel1.7.0的降级规则为异常比例策略流程图
(2)实操
在controller中加入textE
- 配置说明:若满足1秒访问testE资源,并且有异常的访问次数超过百分之20的话,将进行服务降级,经过1秒的时间窗口之后,关闭服务降级
-
测试
-
单独访问一次:localhost:8401/testE
-
Jmeter压测:
-
结论
按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了
5.5异常数
(1)介绍
异常数( DEGRADE_GRADF_EXCEPTION_COUNT ):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后可能再进入熔断状态。
说明:假设时间窗口期为10秒,设置的异常数不能超过5,统计阶段第1秒的时候有5个异常,服务熔断后进入时间窗口期,时间窗口期结束,但是一分钟还没结束,那么此时的异常数还是5个,又进入熔断状态。
注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:
异常数 (ERROR_COUNT):当单位统计时长(默认一分钟)内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
如图为Sentinel1.7.0的降级规则为异常数策略流程图
由于异常数是按照分钟统计的,时间窗口一定要大于等于60秒。
(2)实战
- 测试:连续5次之后
6.热点key限流
6.1基本介绍
(1)热点介绍
何为热点:热点即经常访问的数据,很多时候我们==希望统计或者限制某个热点数据中访问频次最高的 Top N 数据,并对其访问进行限流==或者其它操作:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
(2)兜底方法介绍
兜底方法,分为 系统默认 和 客户自定义 两种。
之前的案例,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论 - 从HystrixCommand到@SentinelResource
6.2实操
在controller中加入:
说明:@SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)方法 testHotKey 里面第一个参数只要QPS超过每秒1次,马上降级处理,异常用了我们自己定义的兜底方法。
配置说明:在1秒的窗口时间内,访问次数超过1的话,就开启限流
-
测试:快速点击,携带参数p1
-
测试:快速点击,携带参数p2
6.3参数例外项
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
特例情况:我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
特例:假如当p1的值等于5时,它的阈值可以达到200
热点参数的注意点,参数必须是基本类型或者String
配置如下:
- 快速点击测试,当p1参数为5时候
7.系统规则
7.1各项配置参数说明
7.2@SentinelResource配置
pom文件增加:
<!-- 引入自己定义的api通用包,可以使用payment对象--><dependency><groupId>springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>1.0-SNAPSHOT</version></dependency>
(1)按自定义资源名称限流
①controller层
@RestController
public class RateLimitController {@GetMapping("/byResource")@SentinelResource(value = "byResource",blockHandler = "handleException")public CommonResult byResource(){return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));}public CommonResult handleException(BlockException exception){return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");}}
②流控配置
③测试
快速点击测试:localhost:8401/byResource
④额外问题
此时关闭服务8401,查看Sentinel控制台,流控规则消失,即为临时
(2)按url地址限流
①controller层
@GetMapping("/rateLimit/byUrl")public CommonResult byUrl(){return new CommonResult(200,"按url限流测试",new Payment(2020L,"serial002"));}
②流控配置
③测试
快速点击:localhost:8401/rateLimit/byUrl
(3)上面兜底方案面临的问题
- 系统默认的,没有体现我们自己的业务要求
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观
- 每个业务方法都添加一个兜底的,那代码膨胀加剧,局统一的处理方法没有体现
(4)客户自定义限流处理逻辑
①自定义限流处理逻辑CustomerBlockHandler
public class CustomerBlockHandler {public static CommonResult handlerException1(BlockException exception){return new CommonResult(4444,"按用户自定义,global handlerException.......1");}public static CommonResult handlerException2(BlockException exception){return new CommonResult(4444,"按用户自定义,global handlerException.......2");}
}
②controller层
//根据用户自定义流控规则进行限流,且实现全局流控规则@GetMapping("/rateLimit/customerBlockHandler")@SentinelResource(value = "customerBlockHandler",blockHandlerClass = CustomerBlockHandler.class,blockHandler = "handlerException2")public CommonResult customerBlockHandler(){return new CommonResult(200,"按用户自定义",new Payment(2020L,"serial003"));}
③配置
注意:两个配置不能同时生效
按自定义资源名流控
按url地址流控
④测试
按自定义资源名称流控配置
按url地址流控配置
⑤结论
在设置了 URL地址流控的前提下,设置按资源名称流控无效
即:URL地址流控优先级大于资源名称流控
7.3更多注解属性说明
8.服务熔断功能
sentinel整合ribbon+spenFeign+fallback
8.1新建实验环境
(1)Ribbon系列-服务提供者9003/9004
启动nacos和sentinel,新建cloudalibaba-provider-payment9003/9004两个服务提供者
①pom
<dependencies><!-- springcloud alibaba nacos 依赖 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- springboot整合Web组件 --><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><!-- 日常通用jar包 --><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><dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency></dependencies>
②yml
③主启动
④controller层
(2)Ribbon系列-消费者84
新建cloudalibaba-consumer-nacos-order84
①pom
<dependencies><!-- 后续做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><!-- springcloud alibaba nacos 依赖 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!-- springboot整合Web组件 --><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><!-- 日常通用jar包 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</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><dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --><groupId>com.atguigu.springcloud</groupId><artifactId>cloud-api-commons</artifactId><version>${project.version}</version></dependency></dependencies>
②yml
③主启动
④配置类
⑤controller层
(3)针对消费者84有无配置fallback和blockHandler
①无配置
-
测试
-
测试id为4
-
测试id为5
-
②配置fallback
③配置blockHandler
-
sentinel新增服务降级规则,是服务降级、是服务降级
若一分钟内异常数超过2,则开启断路器,在80秒的时间窗口内,使用自定义的兜底方法,80秒一过,恢复默认的异常报错页面
-
测试
④同时配置fallback和blockHandler
-
测试:
-
第一、二次访问异常路径,显示的是配置fallback的兜底方法
-
第三次访问异常路径,显示的是配置blockHandler的方法
-
8.2忽略异常
- 84服务消费者的controller层
-
测试
9.sentinel整合feign
9.1步骤
(1)pom
<!-- sentinel整合openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency>
(2)yml
(3)主启动
主启动激活feign
(4)feign指定寻找的服务
(5)controller层
(6)测试
- 测试一
- 测试二:模拟feign调用远程服务超时(feign默认1秒)
9003和9004服务提供者controller新增:
测试时发现如果只给一个微服务加延时,另一个不加,会一直调用没有延时的微服务,所以要么两个都加延时或者停掉一个
测试结果:服务降级
10.Sentinel持久化
目前的sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以==被持久化到 nacos 的数据库中。==
10.1步骤
(1)pom
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
(2)yml
(3)nacos新增配置
resource:资源名称
limitApp:来源应用
grade:阈值类型,0表示线程数,1表示QPS,
count:单机阈值,
strategy:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
cIusterM0de是否集群
总结: 就是在 sentinel 启动的时候,去 nacos 上读取相关规则配置信息,实际上它规则的持久化,就是第三步,粘贴到nacos上保存下来,就算以后在 sentinel 上面修改了,重启应用以后也是无效。
(4)测试
快速点击:
- 查看规则