3. sentinel 服务容错
3.1什么是sentinel
微服务架构中,服务与服务之间相互调用,由于网络或自身的问题并不能保证服务100%可用。如果单个服务瘫痪可能导致整个调用链路失败,最终会使整个服务失败。
sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。
3.1.1 服务雪崩效应
A服务依赖于B服务,B服务依赖C服务,当C挂掉了,B一直拿不到C的响应结果,同样也会导致A挂掉。
雪崩形成的原因很多,设计不合理,高并发等等。所以需要在源头上控制容错不影响其他服务。
3.1.2 常见容错方案
隔离:将系统之间各个模块独立,当出现故障时,把故障隔离在某一个模块内部。常见的有线程池隔离和信号量隔离。
- 线程池隔离:每次请求都开启一个独立线程。隔离是通过线程池隔离,即单个隔离隔离度是线程池。超时直接返回。
- 信号量隔离:每次调用线程,当请求通过计数信号量进行限制,当大于限制立即返回fallback。
参考:https://my.oschina.net/u/867417/blog/2120713
超时:A接口调用B接口设置一个最大请求时间,超过最大请求时间B没有反应就断开请求,释放线程。
限流:限流就时限制输入和输出流量以达到保护系统的目的。
熔断:下游服务因为访问量过大变慢或者失败,上游系统为了保护系统整体可用性切断服务调用。牺牲局部保护整体。
- 熔断关闭状态:对调用不做任何限制。
- 熔断开启状态:对接口调用不经过网络直接fallback。
- 半熔断状态:尝试恢复调用,有限流量调用,如果成功率达到预期就关闭熔断,否则重新进入熔断。
3.1.3 sentinel和hystrix对比
Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。
Sentinel 的侧重点在于:
- 多样化的流量控制
- 熔断降级
- 系统负载保护
- 实时监控和控制台
参考:https://www.jianshu.com/p/d85c0ed44235
对比项 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 不支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC | Servlet、Spring Cloud Netflix |
1. 资源模型和执行模型上的对比
Hystrix 的资源模型设计上采用了命令模式,将对外部资源的调用和 fallback 逻辑封装成一个命令对象 HystrixCommand
或 HystrixObservableCommand
,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 commandKey
和 groupKey
(用于区分资源)以及对应的隔离策略(线程池隔离 or 信号量隔离)。线程池隔离模式下需要配置线程池对应的参数(线程池名称、容量、排队超时等),然后 Command 就会在指定的线程池按照指定的容错策略执行;信号量隔离模式下需要配置最大并发数,执行 Command 时 Hystrix 就会限制其并发调用。
Sentinel 的设计则更为简单。相比 Hystrix Command 强依赖隔离规则,Sentinel 的资源定义与规则配置的耦合度更低。Hystrix 的 Command 强依赖于隔离规则配置的原因是隔离规则会直接影响 Command 的执行。在执行的时候 Hystrix 会解析 Command 的隔离规则来创建 RxJava Scheduler 并在其上调度执行,若是线程池模式则 Scheduler 底层的线程池为配置的线程池,若是信号量模式则简单包装成当前线程执行的 Scheduler。而 Sentinel 则不一样,开发的时候只需要考虑这个方法/代码是否需要保护,置于用什么来保护,可以任何时候动态实时的区修改。
2. 隔离设计上的对比
隔离是 Hystrix 的核心功能之一。Hystrix 提供两种隔离策略:线程池隔离 Bulkhead Pattern
和信号量隔离,其中最推荐也是最常用的是线程池隔离。Hystrix 的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。
但是,实际情况下,线程池隔离并没有带来非常多的好处。最直接的影响,就是会让机器资源碎片化。考虑这样一个常见的场景,在 Tomcat 之类的 Servlet 容器使用 Hystrix,本身 Tomcat 自身的线程数目就非常多了(可能到几十或一百多),如果加上 Hystrix 为各个资源创建的线程池,总共线程数目会非常多(几百个线程),这样上下文切换会有非常大的损耗。另外,线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。
Hystrix 的信号量隔离限制对某个资源调用的并发数。这样的隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以 overhead 比较小,但是效果不错。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。
Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。
3. 熔断降级的对比
Sentinel 和 Hystrix 的熔断降级功能本质上都是基于熔断器模式 Circuit Breaker Pattern
。Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级,在调用达到一定量级并且失败比率达到设定的阈值时自动进行熔断,此时所有对该资源的调用都会被 block,直到过了指定的时间窗口后才启发性地恢复。上面提到过,Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。
4. 实时指标统计实现的对比
Hystrix 和 Sentinel 的实时指标数据统计实现都是基于滑动窗口的。Hystrix 1.5 之前的版本是通过环形数组实现的滑动窗口,通过锁配合 CAS 的操作对每个桶的统计信息进行更新。Hystrix 1.5 开始对实时指标统计的实现进行了重构,将指标统计数据结构抽象成了响应式流(reactive stream)的形式,方便消费者去利用指标信息。同时底层改造成了基于 RxJava 的事件驱动模式,在服务调用成功/失败/超时的时候发布相应的事件,通过一系列的变换和聚合最终得到实时的指标统计数据流,可以被熔断器或 Dashboard 消费。
Sentinel 目前抽象出了 Metric 指标统计接口,底层可以有不同的实现,目前默认的实现是基于 LeapArray 的滑动窗口,后续根据需要可能会引入 reactive stream 等实现。
3.2 sentinel控制台
Sentinel 控制台(Dashboard)提供了机器发现、配置规则、查看实时监控、查看调用链路信息等功能,使得用户可以非常方便地去查看监控和进行配置。
sentinel规则默认不支持持久化,单机部署,所以在生产环境中不能使用,需要进行改造,改造支持nacos持久化
从官网下载进行改造
- 在编辑规则后跳转的配置再进行修改会走/v1/flow 接口报错 所以增加 FlowControllerV1的改造
- 热点规则和系统规则条件不生效,是json对应实体关系错误 对热点规则进行和系统规则实体改造
改造后传送门 https://gitee.com/remember0324/sentinel1.8.2_dashboard.git
运行maven打包命令
mvn clean package -DskipTests=true
得到 sentinel-dashboard.jar
在target文件夹下 运行cmd 可自定义nacos信息和端口 默认8858
java -jar -Dnacos.address=192.168.19.128:13306 -Dnacos.namespace=3d0a77b8-817f-499b-bfda-f90d5a6e4dab -Dnacos.username=nacos -Dnacos.password=nacos -Dserver.port=8858 ./sentinel-dashboard.jar
打包成DockerFile
ADD sentinel-dashboard.jar app.jar
EXPOSE 8858
EXPOSE 8719
CMD java -jar -Dserver.port=8858 app.jar
把Dockerfile和jar包放在同一目录下
docker build -t sentinel-dashboard:1.8.2 .
docker run -d -it -p8858:8858 -p8719:8719 -e TZ="Asia/Shanghai" --name sen-dashboard sentinel-dashboard:1.8.2
3.3 sentinel概念与功能
概念:
- 资源:资源就是Sentinel要保护的东西
-
功能:
3.3.1 轻量级、高性能
Sentinel 作为一个功能完备的高可用流量管控组件,其核心 sentinel-core 没有任何多余依赖,打包后只有不到 200KB,非常轻量级。开发者可以放心地引入 sentinel-core 而不需担心依赖问题。同时,Sentinel 提供了多种扩展点,用户可以很方便地根据需求去进行扩展,并且无缝地切合到 Sentinel 中。
引入 Sentinel 带来的性能损耗非常小。只有在业务单机量级超过 25W QPS 的时候才会有一些显著的影响(5% - 10% 左右),单机 QPS 不太大的时候损耗几乎可以忽略不计。3.3.2 流量控制
Sentinel 可以针对不同的调用关系,以不同的运行指标(如 QPS、并发调用数、系统负载等)为基准,对资源调用进行流量控制,将随机的请求调整成合适的形状。
Sentinel 支持多样化的流量整形策略,在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有: 直接拒绝模式:即超出的请求直接拒绝。
- 慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
- 匀速器模式:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
3.3.3 系统负载保护
Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
3.4 微服务集成sentinel
pom依赖<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--通讯、实时监控-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
bootstrap.yml
server:
port: 8002
spring:
application:
name: consumer-order8002
cloud:
nacos:
config:
namespace: 3d0a77b8-817f-499b-bfda-f90d5a6e4dab
server-addr: 192.168.19.128:13306
group: DEFAULT_GROUP
file-extension: yaml
username: nacos
password: nacos
discovery:
namespace: 3d0a77b8-817f-499b-bfda-f90d5a6e4dab
server-addr: 192.168.19.128:13306
group: DEFAULT_GROUP
username: nacos
password: nacos
sentinel:
transport:
dashboard: 192.168.19.128:8858 #127.0.0.1:8080
port: 8719 #默认为8719 ,假如被占用会自动从8719开始依次+1扫描,直到未被占用的端口
# clientIp: localhost #设置心跳地址 让控制台出现实时监控
web-context-unify: false
datasource:
flow:
nacos:
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
groupId: SENTINEL_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
dataId: ${spring.application.name}-flow-rules
rule-type: flow
degrade:
nacos:
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
groupId: SENTINEL_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
dataId: ${spring.application.name}-degrade-rules
rule-type: degrade
system:
nacos:
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
groupId: SENTINEL_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
dataId: ${spring.application.name}-system-rules
rule-type: system
authority:
nacos:
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
groupId: SENTINEL_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
dataId: ${spring.application.name}-authority-rules
rule-type: authority
param-flow:
nacos:
server-addr: ${spring.cloud.nacos.config.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
groupId: SENTINEL_GROUP
username: ${spring.cloud.nacos.config.username}
password: ${spring.cloud.nacos.config.password}
dataId: ${spring.application.name}-param-flow-rules
rule-type: param-flow
management:
endpoints:
web:
exposure:
include: "*" # 暴露健康检测的接口
feign:
sentinel:
enabled: true
3.5 sentinel规则
3.5.1 流控规则
流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
3.5.1.1 qps-直接-快速失败
/**
* qps-直接-快速失败
* 设置QPS单机阈值为2 当每秒请求量大于2的时候开始限流。
* Blocked by Sentinel (flow limiting)
*
* @return
*/
@GetMapping(value = "/flow/qps/directly/failFast")
public String flowQpsDirectlyFailFast() {
return "qps-直接-快速失败";
}
3.5.1.2 qps-关联-快速失败
/**
* qps-关联-快速失败 -- 资源1
* 当该请求关联到flowQpsRelevanceFailFast2,设置qps阈值为2 不断请求/flow/qps/relevance/failFast2 当阈值大于2时 /flow/qps/relevance/failFast1将被限流
*
* @return
*/
@GetMapping(value = "/flow/qps/relevance/failFast1")
public String flowQpsRelevanceFailFast1() {
return "qps-关联-快速失败-资源1";
}
/**
* qps-关联-快速失败 -- 关联资源
*
* @return
*/
@GetMapping(value = "/flow/qps/relevance/failFast2")
public String flowQpsRelevanceFailFast2() {
return "qps-关联-快速失败-关联资源";
}
3.5.1.3 qps-链路-快速失败
使用@SentinelResource 定义一个资源
/**
* sentinelResource 资源 对上级资源限App限流 对Mini小程序没有影响
* 可以通过配置关闭:spring.cloud.sentinel.web-context-unify=false
* App 限流后被报错 com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
*
* @return
*/
@SentinelResource("flow-qps-link")
public String sentinelResource() {
return "qps-链路-快速失败";
}
/**
* qps-链路-快速失败 --资源Mini
* 链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。
* 它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。 用到注解@SentinelResource
*
* @return
*/
@GetMapping(value = "/flow/qps/link/failFastMini")
public String flowQpsLinkFailFastMini() {
String sentinelResource = sentinelFlowService.sentinelResource();
return sentinelResource + "-资源小程序端";
}
/**
* qps-链路-快速失败 --资源App
*
* @return
*/
@GetMapping(value = "/flow/qps/link/failFastApp")
public String flowQpsLinkFailFastApp() {
String sentinelResource = sentinelFlowService.sentinelResource();
return sentinelResource + "-资源app端";
}
/**
* sentinelResource 资源 对上级资源限App限流 对Mini小程序没有影响
* App 限流后被报错 com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
*
* @return
*/
@SentinelResource("flow-qps-link")
public String sentinelResource() {
return "qps-链路-快速失败";
}
3.5.1.4 qps—流控效果-Warm Up
/**
* 它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。
* 设置阈值为15,预热时间3s 刚开始请求最大只能请求5次/s 超过会被限流 3s后阈值变成15
*
* @return
*/
@GetMapping(value = "/flow/qps/directly/warmUp")
public String flowQpsDirectlyWarmUp() {
return "qps-直接-Warm Up 预热处理";
}
刚开始刷新会出现限流,后面一直刷新(不超过15次/s)就不会出现限流
3.5.1.5 qps—流控效果-排队等待
/**
* 让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。
* 设置qps为3,超时时间150ms 请求过来最大通过3个/s 超过会等待 等待超过150ms被丢弃 返回 Blocked by Sentinel (flow limiting)
*
* @return
*/
@GetMapping(value = "/flow/qps/directly/lineUp")
public String flowQpsDirectlylineUp() {
return "qps-直接-lineUp 排队等待";
}
3.5.1.6 线程数—流控模式-直接失败
/**
* 线程数-直接失败 设置最大线程数为2
* 用jmeter 1s请求5个线程 再使用浏览器请求接口 被流控
*
* @return
*/
@GetMapping(value = "/flow/thread/directly")
public String flowThreadDirectly() {
return "线程数-直接";
}
3.5.1.7 线程数—流控模式-关联
/**
* 线程数-关联 -- 资源1
* 设置 资源1 关联资源 资源2 设置线程数阈值为2 用jmeter 1s请求5个线程压资源2 这个时候请求资源1被限流
*
* @return
*/
@GetMapping(value = "/flow/thread/relevance1")
public String flowThreadrelevance1() {
return "线程数-关联-资源1";
}
/**
* 线程数-关联 -- 资源2
*
* @return
*/
@GetMapping(value = "/flow/thread/relevance2")
public String flowThreadrelevance2() {
return "线程数-关联-资源2";
}
3.5.2 熔断规则
3.5.2.1 熔断策略-慢调用比例
/**
* 熔断策略-慢调用比例
* 最大RT(ms): 平均响应时间 最大4900 可通过-Dcsp.sentinel.statistic.max.rt=xxx设置 请求的响应时间大于该值则统计为慢调用
* 比例阈值(0.0-1.0):熔断的慢调用比例 (超过最大RT的比例数)
* 熔断时长:被熔断后多长时间不能访问
* 最小请求数目:单位统计时长(s) 内请求数目大于设置的最小请求数目,并且 慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
* <p>
* <p>
* 经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求响应时间小于设置的最大 RT 则结束熔断,若大于设置的最大 RT 则会再次被熔断。
* example:设置最大RT:1ms 比例阈值:0.2 熔断:3s 最小请求数:2 进行测试 在1s内请求大于2个数目 肯定超过最大RT 比例也超过了20% 所以被限流
*
* @return
*/
@GetMapping(value = "/degrade/slowCallRatio")
public String degradeSlowCallRatio() {
return "熔断策略-慢调用比例";
}
3.5.2.2 熔断策略-异常比例
正常访问情况下,每三次就会出现一次异常,当异常高于设定的条件时就会触发熔断。
int num = 0;
/**
* 熔断策略-异常比例
* 在统计周期内资源请求访问异常的比例大于设定的阈值,则接下来的熔断周期内对资源的访问会自动地被熔断
* 设置比例阈值0.3 熔断时长4s 最小请求数2 当满足单位时间(s)请求大于2 并且异常比例大于0.2 将会触发熔断
*
* @return
*/
@GetMapping(value = "/degrade/exceptionRatio")
public String degradeExceptionRatio() {
num++;
if (num % 3 == 0) {
throw new RuntimeException("触发异常比例熔断策略");
}
return "熔断策略-异常比例";
}
3.5.2.3 熔断策略-异常数
/**
* 熔断策略-异常数
* 在统计周期内资源请求访问异常数大于设定的阈值,则接下来的熔断周期内对资源的访问会自动地被熔断
* 设置异常数2 熔断时长5s 最小请求数2 当满足单位时间(s)请求大于2 并且异常数大于2 将会触发熔断
*
* @return
*/
@GetMapping(value = "/degrade/exceptionNum")
public String degradeExceptionNum() {
num++;
if (num % 3 == 0) {
throw new RuntimeException("触发异常数熔断策略");
}
return "熔断策略-异常数";
}
3.5.3 热点规则
3.5.3.1 热点策略-基础策略
/**
* 热点规则配置
* 必须使用@SentinelResource注解才能生效
* 设置参数索引为0也就是name参数 阈值为3 窗口为1; 在1s的统计时间内传入name参数的qps超过3次 就会被限流
* <p>
* http://localhost:8002/hotkey/qps/base?name=zhangsan&age=18
* 异常: com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: zhangsan
*
* @param name
* @param age
* @return
*/
@SentinelResource("/hotkey/qps/base")
@GetMapping(value = "/hotkey/qps/base")
public String hotkeyQpsBase(String name, Integer age) {
return "热点规则基础:" + name + age;
}
3.5.3.2 热点策略-高级策略
/**
* 热点规则 高级配置
* 必须使用@SentinelResource注解才能生效
* 添加参数例外项 支持byte、char、float、long、double、int、java.lang.String 6个基本类型和Sting类型
* 添加一个参数索引为1 阈值为2统计窗口为1,参数例外项中 参数类型为int 参数值为18阈值为10000,所以当传入参数age=18小于每秒10000次的时候不会触发限流,其他的和基础配置一样
*
* @param name
* @param age
* @return
*/
@SentinelResource("/hotkey/qps/high")
@GetMapping(value = "/hotkey/qps/high")
public String hotkeyQpsHigh(String name, Integer age) {
return "热点规则高级:" + name + age;
}
3.5.4 授权规则
添加一个请求拦截,可以从请求参数或者请求头里拿到请求的数据,配置一个授权可以进行降级。
@Component
@Log4j2
public class RequestOriginParserDefinition implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String serviceName = request.getParameter("serviceName");
if (StringUtils.isNotBlank(serviceName)) {
log.info("serviceName:{}", serviceName);
return serviceName;
}
return request.getRemoteAddr();
}
}
/**
* 配置pc为黑名单
* http://localhost:8002/authority/message?serviceName=pc
* 当配置在注解的资源 /authority/resource 上会直接报错 com.alibaba.csp.sentinel.slots.block.authority.AuthorityException: null
* 当配置在 /authority/message 接口上 会直接出现限流 Blocked by Sentinel (flow limiting) 内部实现对异常的兜底 两者都实现了功能
*
* @return
*/
@GetMapping(value = "/authority/message")
@SentinelResource("/authority/resource")
public String authorityMessage() {
return "授权规则配置";
}
3.5.5 系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。
- Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps minRt 计算得出。设定参考值一般是 CPU cores 2.5。
- RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
- CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。
3.6 自定义异常返回
当被sentinel限流之后,页面会出现 Blocked by Sentinel (flow limiting) 或者出现error页面,所以对异常进行拦截 处理返回
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
String msg = null;
if (e instanceof FlowException) {
msg = "接口被限流了";
} else if (e instanceof DegradeException) {
msg = "接口被熔断流了";
} else if (e instanceof ParamFlowException) {
msg = "热点参数限流";
} else if (e instanceof AuthorityException) {
msg = "授权规则限流";
}
// http状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
new ObjectMapper().writeValue(response.getWriter(), CommonResult.error(msg));
}
}
3.7 @SentinelResource的使用
value | 资源名称 |
---|---|
entryType | entry类型,标记流量的方向,取值IN/OUT,默认是OUT |
blockHandler | 处理BlockException的函数名称,函数要求: 1. 必须是 public 1. 返回类型 参数与原方法一致 1. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置blockHandlerClass ,并指定blockHandlerClass里面的方法。 |
blockHandlerClass | 存放blockHandler的类,对应的处理函数必须static修饰。 |
fallback | 用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型) 进行处理。函数要求: 1. 返回类型与原方法一致 1. 参数类型需要和原方法相匹配 1. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。 |
fallbackClass | 存放fallback的类。对应的处理函数必须static修饰。 |
defaultFallback | 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进行处理。若同时配置了 fallback 和 defaultFallback, 以fallback为准。函数要求: 1. 返回类型与原方法一致 1. 方法参数列表为空,或者有一个 Throwable 类型的参数。 1. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。 |
exceptionsToIgnore | 指定排除掉哪些异常。排除的异常不会计入异常统计,也不会进入 fallback逻辑,而是原样抛出。 |
exceptionsToTrace | 需要trace的异常 |
@RestController
public class SentinelResourcesController {
@Autowired
SentinelResourcesService sentinelResourcesService;
@GetMapping(value = "/sentinel/resources1")
public String resources1() {
return sentinelResourcesService.message();
}
@GetMapping(value = "/sentinel/resources2")
public String resources2() {
return sentinelResourcesService.message2();
}
}
@Service
@Slf4j
public class SentinelResourcesService {
int i = 0;
/**
* 直接将限流和降级方法定义在方法中
*
* @return
*/
@SentinelResource(
value = "resource-message1",
//指定发生BlockException是 进入的方法
blockHandler = "blockHandlerMethod",
//指定发生Throwable时 进入的方法
fallback = "fallbackMethod"
)
public String message() {
i++;
if (i % 3 == 0) {
throw new RuntimeException("我是故意的");
}
return "message11111";
}
public String blockHandlerMethod(BlockException e) {
log.error("FlowException:", e);
return "SentinelResourcesService.message 接口被限流了";
}
public String fallbackMethod(Throwable e) {
log.error("RuntimeException:", e);
return "接口发生了异常";
}
/**
* 将限流和降级方法外置到单独的类中
*
* @return
*/
@SentinelResource(
value = "resource-message2",
blockHandlerClass = SentinelResourcesServiceBlockHandler.class,
blockHandler = "blockHandlerMethod2",
fallbackClass = SentinelResourcesServiceFallback.class,
fallback = "fallbackMethod2"
)
public String message2() {
i++;
if (i % 3 == 0) {
throw new RuntimeException("我还是故意的");
}
return "message22222";
}
}
3.7.1 设置在class内部处理异常
请求/sentinel/resources1接口
设置如下熔断策略,当出现降级时 ,会走自定义降级的方法,优先级比全局异常拦截高;当出现程序异常时,会走自定义fallback方法
3.7.2 将异常放在其他类处理
请求/sentinel/resources2接口
@Slf4j
public class SentinelResourcesServiceBlockHandler {
/**
* 必须是static 方法
*
* @param e
* @return
*/
public static String blockHandlerMethod2(BlockException e) {
log.error("{0}", e);
return "接口被限流了22222222";
}
}
@Slf4j
public class SentinelResourcesServiceFallback {
/**
* 必须是static 方法
*
* @param e
* @return
*/
public static String fallbackMethod2(Throwable e) {
log.error("{0}", e);
return "接口发生了异常2222222222222";
}
}
3.7 Feign整合sentinel
- 启动product 项目,让consumer8002调用 product
- 将product 7001,product 7002 添加sentinel依赖和yml配置,和consumer8002一样
- 通过feign调用product
product
@GetMapping("/product/getProduct/{id}")
public CommonResult<Product> getProduct(@PathVariable("id") Integer id) {
Optional<Product> optional = Optional.ofNullable(productService.findBuyId(id));
return optional.map(CommonResult::ok).orElseGet(CommonResult::error);
}
consumer -service
/**
* 需要声明被调用方的serviceName
*
* @author Rem
* @date 2021-07-15
*/
@Service
@FeignClient(value = "provider-product",
// 异常处理方法和工厂处理的事情是一样, 只能选择一个
// fallback = ProductServiceFallback.class,
fallbackFactory = ProductServiceFallBackFactory.class)
public interface ProductService {
/**
* 指定调用的方法
*
* @param id
* @return
* @FeignClient+@GetMapping 就是一个完整的请求路径 http://provider-product/product/getProduct/{id};
*/
@GetMapping("/product/getProduct/{id}")
CommonResult<Product> getProduct(@PathVariable("id") Integer id);
@GetMapping("/product/test/{id}")
CommonResult<String> test(@PathVariable("id") Integer id);
}
consumer -FallBackFactory 可以查看异常
@Component
public class ProductServiceFallBackFactory implements FallbackFactory<ProductService> {
@Override
public ProductService create(Throwable throwable) {
return new ProductService() {
@Override
public CommonResult<Product> getProduct(Integer id) {
//打印异常
throwable.printStackTrace();
Product product = new Product();
product.setPid(-1);
return CommonResult.error(product);
}
@Override
public CommonResult<String> test(Integer id) {
return null;
}
};
}
}
consumer - Fallback
@Slf4j
@Component
public class ProductServiceFallback implements ProductService {
/**
* 当调用product 调用失败的时候回自动进入该方法
* <p>
* 设置调用7002出现异常 7001正常 然后调用创建订单接口,每间隔一次就会走进该方法
*
* @param id
* @return
*/
@Override
public CommonResult<Product> getProduct(Integer id) {
Product product = new Product();
product.setPid(-1);
return CommonResult.error(product);
}
@Override
public CommonResult<String> test(Integer id) {
return null;
}
}
consumer 接口
@GetMapping("/createOrder/{pid}")
public CommonResult createOrder(@PathVariable("pid") Integer pid) {
CommonResult<Product> productCommonResult = productService.getProduct(pid);
log.info("通过feign调用的数据:" + productCommonResult);
if (productCommonResult.getCode().equals(0)) {
orderService.createOrder(productCommonResult.getData());
return CommonResult.ok();
}
return CommonResult.error();
}
设置 product - getProduct 接口限流,使用consumer调用 当被限流的时候会处理失败
会进入兜底方法 打印出异常信息