一句话,Sentinel就是升级的Hystrix。
官网
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
Sentinel 的主要特性
Sentinel 的开源生态
服务使用中的各种问题
Sentinel 分为两个部分:
安装步骤
下载到本地:sentinel-dashboard-1.8.5.jar
运行命令
java -jar sentinel-dashboard-1.8.5.jar
前提需要java8环境,且8080端口不能被占用。
访问 http://localhost:8080,账号密码均为sentinel
启动Nacos8848成功
http://localhost:8848/nacos/#/login
建Module
cloudalibaba-sentinel-service8401
改POM
下面这3个依赖就是Nacos + Sentinel的标配
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
OpenFeign后面会用到。
写YML
主启动
业务类
FlowLimitController
测试
➢ 启动Sentinel8080
➢ 启动微服务8401
➢ 查看sentienl控制台
空空如也,啥都没有
➢ Sentinel采用的懒加载
说明
执行一次访问即可
http://localhost:8401/testA
http://localhost:8401/testB
➢ 效果
Sentinel8080正在监控微服务8401。
流量限制控制规则,分为:流控模式 和 流控效果
解释说明
api达到限流条件时,直接限流
直接 => 快速失败(系统默认)
可以直接点击簇点链路添加流控
配置及说明
阈值类型为QPS
表示1秒钟内查询1次就是OK,若超过次数1,就直接 => 快速失败,报默认错误
测试
➢ 快速点击访问:http://localhost:8401/testA
➢ 结果
Blocked by Sentinel (flow limiting)
➢ 思考
直接调用默认报错信息,技术方面OK,但,是否应该有我们自己的后续处理?
比如:类似有个fallback的兜底方法?
扩展:阈值类型为线程数
当调用该api的线程数达到阈值的时候,进行限流。
与QPS直接快速失败不同的是,QPS情况下限制的是流量,比如银行的人流量只能是1人/s,也就是说每秒只能一个人进入银行办理业务;
而线程数就好比银行只有一个窗口开放,一群人都可以进入银行,但是每次只能处理一个人的业务。
演示效果
修改一下8401的业务类:
测试
开两个页面访问:http://localhost:8401/testA
同时抢占,一个直接快速失败,另一个在处理请求
当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阀值后,就限流A自己
- B惹事,A挂了
配置A
设置效果
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名。
Ex:假如支付接口/testB即将瘫痪,那么限制下单接口/testA。
postman模拟并发密集访问testB
➢ 访问testB成功
➢ postman里新建多线程集合组
➢ 将访问地址添加进新新线程组
➢ Run
大批量线程高并发访问B,导致A失效了。
多个请求调用了同一个微服务。
只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
需要测试链路的话,Spring Cloud Alibaba 版本需要2.1.1.RELEASE以上,在父工程的pom中修改,不要直接在子odule的pom中修改,版本有对应关系,不然报错。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.1.RELEASEversion>
<type>pomtype>
<scope>importscope>
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均会被限制访问。
直接失败,抛出异常
Blocked by Sentinel (flow limiting)
源码
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
根据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
刚开始扛不住,后续慢慢的能抗住了:
应用场景
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
匀速排队,让请求以匀速的速度通过,阈值类型必须设置为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一个请求,说明请求的执行进行了排队。
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 引入) |
老版本:
新版本:
慢调用比例 (SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。(跟豪猪哥类似)
测试
代码
配置
熔断规则
在1000ms的统计时间内,总请求数(超过5次)中有80%的请求最大RT超过了200ms,那么触发熔断机制,熔断2s。
jmeter压测
结论
永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来s秒钟的时间内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了。
testD被熔断了:
从实时监控也可以看到,在09的时候开始熔断:
后续停止jmeter,没有这么大的访问量了,断路器半开到关闭(保险丝恢复),微服务恢复OK。
老版本:
新版本:
异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
测试
代码
配置
熔断规则
jmeter压测
结论
单独访问一次,必然来一次报错一次 (int age = 10/0) ,调一次错一次;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了,断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。
停掉jemeter后过2s,报/zero错误。
老版本:
新版本:
异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
测试
代码
配置
熔断规则
手动测试
结论
这里测试出现了Bug,虽然熔断了 但熔断时长不是我配置的5s,熔断了很久大概1分钟,统计时长也不是1s,因为我手动很慢的测试,同时没达到最小请求数,只达到3次异常就直接熔断了。但老师演示的老版本就可以依据配置进行熔断,可能是不会使用新版本。
我发现我根本改不了配置的统计时长,如果按照老版本的是按照分钟统计异常数,设置时间窗口 >= 60s,那新版本理应设置统计时长 >= 60000ms,但我修改统计时长后再编辑 还是默认的1000,虽然新版本没说异常数是分钟级的,但是修改不了统计时长就很迷惑。上面修改慢调用比例的比例阈值也没效果,但异常比例的比例阈值可以修改。
搞不懂。
何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限流或者其它操作。比如:
热点参数限制会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限制。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
承上启下复习
兜底方法
结论
@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。
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。配置全局QPS
@SentinelResource
启动Nacos成功
启动Sentinel成功
改Module
修改cloudalibaba-sentinel-service8401
改POM
com.atguigu.springcloud
cloud-api-commons
${project.version}
业务类
RateLimitController
主启动
配置流控规则
➢ 配置步骤
➢ 图形配置和代码关系
表示1秒钟内查询次数大于1,就跑到我们自定义的处理,限流。
测试
➢ 1秒钟点击1下,OK
➢ 超过上述,疯狂点击,返回了自己定义的限流处理信息,限流发生
额外问题
此时关闭问服务8401看看
Sentinel控制台,流控规则消失了??
临时/持久?
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息。
业务类
RateLimitController
不配置blockHandler属性,没有兜底方法。
Sentinel控制台配置
这里是根据URL进行限流。
测试
➢ 正常点击:http://localhost:8401/rateLimit/byUrl
➢ 疯狂点击:http://localhost:8401/rateLimit/byUrl
结果
会返回Sentinel自带的限流处理结果。
又回到了当时Hystrix的问题。
解决上述问题,创建CustomerBlockHandler
类用于自定义限流处理逻辑。
自定义限流处理类
CustomerBlockHandler
业务类
RateLimitController
Sentinel控制台配置
测试
正常访问:http://localhost:8401/rateLimit/customerBlockHandler
狂点:http://localhost:8401/rateLimit/customerBlockHandler
兜底的方法是全局的2号方法。
完成了业务与代码的解耦,代码膨胀。
进一步说明
多说一嘴
所有的代码都要用try-catch-finally方式进行处理。
Sentinel主要有三个核心Api
Sentinel 整合 Ribbon + OpenFeign + fallback
建Module
新建cloudalibaba-provider-payment9003/9004
两个一样的做法
改POM
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
写YML
主启动
业务类
模拟数据库的DAO层。
测试
➢ http://localhost:9003/paymentSQL/1
➢ http://localhost:9004/paymentSQL/1
建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两个一起配置,降级和限流的保护措施会更加完善。
改Module
修改cloudalibaba-consumer-nacos-order84
模块
84消费者调用提供者9003
Feign组件一般是消费侧
改POM
添加OpenFeign
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
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消费侧自动降级,不会被耗死。
一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化。
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,Sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效。
步骤
改Module
修改cloudalibaba-sentinel-service8401
改POM
之前已引入
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
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
配置重新出现了,持久化验证通过。