SpringCloud Alibaba—Sentinel实现熔断与限流
笔记整理自【尚硅谷】周阳SpringCloud框架开发教程
1. Sentinel简介
一句话,Sentinel就是升级的Hystrix。
Ⅰ. Sentinel是什么
官网
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
- 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Ⅱ. Sentinel能做什么
Sentinel 的主要特性
Sentinel 的开源生态
Ⅲ. Sentinel怎么使用
官网使用教程
服务使用中的各种问题
2. 安装Sentinel控制台
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
安装步骤
-
下载到本地:sentinel-dashboard-1.8.5.jar -
运行命令 java -jar sentinel-dashboard-1.8.5.jar
前提需要java8环境,且8080端口不能被占用。 -
访问 http://localhost:8080,账号密码均为sentinel
3. 初始化演示工程
-
启动Nacos8848成功 http://localhost:8848/nacos/#/login -
建Module cloudalibaba-sentinel-service8401 -
改POM 下面这3个依赖就是Nacos + Sentinel的标配
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<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>
OpenFeign后面会用到。 -
写YML -
主启动 -
业务类 FlowLimitController -
测试 ? 启动Sentinel8080 ? 启动微服务8401 ? 查看sentienl控制台 空空如也,啥都没有 ? Sentinel采用的懒加载 说明 执行一次访问即可 http://localhost:8401/testA http://localhost:8401/testB ? 效果 Sentinel8080正在监控微服务8401。
4. 流控规则
Ⅰ. 基本介绍
流量限制控制规则,分为:流控模式 和 流控效果
解释说明
Ⅱ. 流控模式
1?? 直接(默认)
api达到限流条件时,直接限流
-
直接 => 快速失败(系统默认) -
可以直接点击簇点链路添加流控 -
配置及说明 阈值类型为QPS 表示1秒钟内查询1次就是OK,若超过次数1,就直接 => 快速失败,报默认错误 -
测试 ? 快速点击访问:http://localhost:8401/testA ? 结果 Blocked by Sentinel (flow limiting) ? 思考 直接调用默认报错信息,技术方面OK,但,是否应该有我们自己的后续处理? 比如:类似有个fallback的兜底方法?
扩展:阈值类型为线程数
当调用该api的线程数达到阈值的时候,进行限流。
与QPS直接快速失败不同的是,QPS情况下限制的是流量,比如银行的人流量只能是1人/s,也就是说每秒只能一个人进入银行办理业务;
而线程数就好比银行只有一个窗口开放,一群人都可以进入银行,但是每次只能处理一个人的业务。
2?? 关联
当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阀值后,就限流A自己
- B惹事,A挂了
3?? 链路
多个请求调用了同一个微服务。
只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
需要测试链路的话,Spring Cloud Alibaba 版本需要2.1.1.RELEASE以上,在父工程的pom中修改,不要直接在子odule的pom中修改,版本有对应关系,不然报错。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
-
Sentinel从1.6.3版本开始,Sentinel Web Filter 默认收敛所有的URL入口的Context,因此链路限流不生效 -
1.7.0版本开始,官方在CommomFilter中引入了WEB_CONTEXT_UNIFY 这个init parameter,用于控制是否收敛context,将其配置为false 即可根据不同的URL进行链路限流 -
Spring Cloud Alibaba 在2.1.1.RELEASE版本后,可以通过配置spring.cloud.sentinel.web-context-unify=false 关闭 -
参考 -
测试 启动8401,给/testA设置链路 + 快速失败流控规则,入口资源填写/testA的入口资源地址sentinel_web_servlet_context : 然后再发送请求至 http://localhost:8401/testA 时,如果1秒内请求次数超过1次,就会自动触发限流。 此外,通过其他微服务模块请求/testA时,如果1秒内请求次数超过1次,同样会触发限流。 但是不会对/testB限制。 -
解释 实际上,链路的控制指的就是对一条链路的访问进行控制。 比方说,我有一个二叉树: a => b => d,a => b => e,a => c => f,a => c => g 均可视作链路。 假设我以a为入口资源,d为终点资源,对这条链路进行限制的话,则资源a,b,d均会被限制访问。
Ⅲ. 流控效果
1?? 直接 => 快速失败(默认的流控处理)
直接失败,抛出异常
Blocked by Sentinel (flow limiting)
2?? Warm Up 预热
根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
-
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值。 -
官网 默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。 限流 冷启动 -
源码 -
Warm Up配置 默认 coldFactor 为 3,即请求 QPS 从 (threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。 案例,阀值为10,预热时长设置5秒。 系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10。 -
测试 多次点击:http://localhost:8401/testB 刚开始扛不住,后续慢慢的能抗住了: -
应用场景 如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
3?? 排队等待
匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
-
官网 -
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。 匀速排队,阈值必须设置为QPS。 -
源码 com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController -
测试 @GetMapping("/testB")
public String testB() {
log.info(Thread.currentThread().getName() + "\t" + "...testB");
return "-----testB";
}
可以看到刚好满足1s一个请求,说明请求的执行进行了排队。
5. 降级规则
Ⅰ. 基本介绍
官网
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
老版本的Sentinel的断路器是没有半开状态的,半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix(在Hystrix中 快照时间窗口是值 阈值检测时间 ,而休眠时间窗口是指 断路器从开启到半开状态间隔的时间)。
新版本的Sentinel加入了半开状态!
Sentinel在1.8.0版本对熔断降级做了大的调整,可以定义任意时长的熔断时间,引入了半开启恢复支持。下面梳理下相关特性。
熔断状态
熔断状态有三种状态,非别为OPEN、HALF_OPEN、CLOSED
状态 | 说明 |
---|
OPEN | 表示熔断开启,拒绝所有请求 | HALF_OPEN | 探测恢复状态,如果接下来的一个请求顺利通过则表示结束熔断,否则继续熔断 | CLOSE | 表示熔断关闭,请求顺利通过 |
熔断降级规则说明
熔断降级规则(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?? 慢调用比例,RT(平均响应时间,秒级)
老版本:
新版本:
测试
-
代码 -
配置 熔断规则 在1000ms的统计时间内,总请求数(超过5次)中有80%的请求最大RT超过了200ms,那么触发熔断机制,熔断2s。 -
jmeter压测 -
结论 永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来s秒钟的时间内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了。 testD被熔断了: 从实时监控也可以看到,在09的时候开始熔断: 后续停止jmeter,没有这么大的访问量了,断路器半开到关闭(保险丝恢复),微服务恢复OK。
2?? 异常比例(秒级)
老版本:
新版本:
测试
3?? 异常数(分钟级)
老版本:
新版本:
测试
-
代码 -
配置 熔断规则 -
手动测试 -
结论 这里测试出现了Bug,虽然熔断了 但熔断时长不是我配置的5s,熔断了很久大概1分钟,统计时长也不是1s,因为我手动很慢的测试,同时没达到最小请求数,只达到3次异常就直接熔断了。但老师演示的老版本就可以依据配置进行熔断,可能是不会使用新版本。 我发现我根本改不了配置的统计时长,如果按照老版本的是按照分钟统计异常数,设置时间窗口 >= 60s,那新版本理应设置统计时长 >= 60000ms,但我修改统计时长后再编辑 还是默认的1000,虽然新版本没说异常数是分钟级的,但是修改不了统计时长就很迷惑。上面修改慢调用比例的比例阈值也没效果,但异常比例的比例阈值可以修改。 搞不懂。
6. 热点key限流
Ⅰ. 基本介绍
官网
何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限流或者其它操作。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限制会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限制。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
承上启下复习
兜底方法
- 分为 系统默认 和 客户自定义,两种
- 之前的case,限流出问题后,都是用Sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
- 我们能不能自定义 类似Hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论
- 从
@HystrixCommand 到 @SentinelResource
Ⅱ. 代码演示
-
新增 @SentinelResource 如果你违背了@SentinelResource 对控制台配置的规则,就会执行兜底的方法。 -
配置 这里资源名是根据 @SentinelResource 注解的 value 属性值配置的。 限流模式只支持QPS模式,固定写死了。(这才叫热点) @SentinelResource 注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推。 单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。 上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。 -
测试 ? 使用兜底方法 http://localhost:8401/testHotKey?p1=a ? 不使用兜底方法 http://localhost:8401/testHotKey?p1=a 把@SentinelResource 注解中的blockHandler 属性去掉: -
小总结 ? ? error http://localhost:8401/testHotKey?p1=abc ? ? error http://localhost:8401/testHotKey?p1=abc&p2=33 ? ?? right http://localhost:8401/testHotKey?p2=abc 只要带上p1参数且违背了规则 就会降级限流。
Ⅲ. 参数例外项
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流。
-
特例情况 ? 普通:超过1秒钟一个后,达到阈值1后马上被限流 ? 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样 ? 特例:假如当p1的值等于5时,它的阈值可以达到200 -
配置 参数类型只支持8种基本类型和String类型。 添加成功 -
测试 ? ?? http://localhost:8401/testHotKey?p1=5 ? ? http://localhost:8401/testHotKey?p1=3 当p1等于5的时候,阈值变为200,当p1不等于5的时候,阈值就是平常的1。
7. 系统规则
官网
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN ),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5 。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
配置全局QPS
- 不管是/testA还是/testB,只要QPS > 1 整个系统就不能用。
- 这个粒度太粗,就相当于一个窗口人很多,整个银行就不接待人了,不太建议使用。
8. @SentinelResource
Ⅰ. 按资源名称限流 + 后续处理
-
启动Nacos成功 -
启动Sentinel成功 -
改Module 修改cloudalibaba-sentinel-service8401 -
改POM <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
-
业务类 RateLimitController -
主启动 -
配置流控规则 ? 配置步骤 ? 图形配置和代码关系 表示1秒钟内查询次数大于1,就跑到我们自定义的处理,限流。 -
测试 ? 1秒钟点击1下,OK ? 超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生 -
额外问题 此时关闭问服务8401看看 Sentinel控制台,流控规则消失了?? 临时/持久?
Ⅱ. 按照Url地址限流 + 后续处理
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息。
-
业务类 RateLimitController 不配置blockHandler属性,没有兜底方法。 -
Sentinel控制台配置 这里是根据URL进行限流。 -
测试 ? 正常点击:http://localhost:8401/rateLimit/byUrl ? 疯狂点击:http://localhost:8401/rateLimit/byUrl 结果 会返回Sentinel自带的限流处理结果。
Ⅲ. 上面兜底方案面临的问题
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧。
- 全局统一的处理方法没有体现。
又回到了当时Hystrix的问题。
Ⅳ. 客户自定义限流处理逻辑
解决上述问题,创建CustomerBlockHandler 类用于自定义限流处理逻辑。
Ⅴ. 更多注解属性说明
注解支持文档
多说一嘴
所有的代码都要用try-catch-finally方式进行处理。
Sentinel主要有三个核心Api
- SphU定义资源
- Tracer定义统计
- ContextUtil定义了上下文
9. 服务熔断功能
Sentinel 整合 Ribbon + OpenFeign + fallback
Ⅰ. Ribbon系列
1?? 提供者9003/9004
-
建Module 新建cloudalibaba-provider-payment9003/9004 两个一样的做法 -
改POM <dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<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>
<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>
</dependencies>
-
写YML -
主启动 -
业务类 模拟数据库的DAO层。 -
测试 ? http://localhost:9003/paymentSQL/1 ? http://localhost:9004/paymentSQL/1
2?? 消费者84
-
建Module cloudalibaba-consumer-nacos-order84 -
改POM -
写YML -
主启动 -
业务类 ? config.ApplicationContextConfig ? controller.CircleBreakerController 目的:fallback管运行异常,blockHandler管配置违规。 测试:http://localhost:84/consumer/fallback/1 1?? 没有任何配置 结果 给客户error页面,不友好 ? http://localhost:84/consumer/fallback/4 ? http://localhost:84/consumer/fallback/5 2?? 只配置fallback 说明 本例Sentinel无配置。 结果 执行了兜底的handlerFallback方法。 3?? 只配置blockHandler 说明 本例sentinel需配置: 异常超过2次后,断路器打开,断电跳闸,系统被保护。 结果 服务被降级。 4?? fallback和blockHandler都配置 说明 本例sentinel需配置: 结果 没超过QPS,也就是没违反Sentinel中配置的规则,有运行异常则交给fallback处理: 违反了Sentinel中配置的规则,即使有运行异常,降级也交给blockHandler处理: 若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。 5?? 忽略属性… 说明 本例sentinel无配置。 结果 程序异常打到前台了,对用户不友好。 其他页面没有影响: 当然,触发流控之后,仍然通过blockHandler指定的方法进行熔断兜底。 -
总结 fallback + blockHandler两个一起配置,降级和限流的保护措施会更加完善。
Ⅱ. Feign系列
-
改Module 修改cloudalibaba-consumer-nacos-order84 模块 84消费者调用提供者9003 Feign组件一般是消费侧 -
改POM 添加OpenFeign
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
-
写YML 激活Sentinel对Feign的支持 -
主启动 @EnableFeignClients -
业务类 ? 带@FeignClient 注解的业务接口 service.PaymentService ? 实现接口 fallback = PaymentFallbackService.class => service.PaymentFallbackService ? controller.CircleBreakerController -
测试 http://localhost:84/consumer/openfeign/1 测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死。
Ⅲ. 熔断框架比较
10. 规则持久化
一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化。
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,Sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效。
步骤
-
改Module 修改cloudalibaba-sentinel-service8401 -
改POM 之前已引入
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
-
写YML 添加Nacos数据源配置 -
添加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:是否集群。
-
启动8401后刷新sentinel发现业务规则有了 -
快速访问测试接口:http://localhost:8401/rateLimit/byUrl 配置生效 -
停止8401再看Sentinel 停机后发现流控规则没有了 -
重新启动8401再看Sentinel 乍一看还是没有,稍等一会儿; 多次调用:http://localhost:8401/rateLimit/byUrl 配置重新出现了,持久化验证通过。
|