代码地址

3. sentinel 服务容错

官网入口

3.1什么是sentinel

微服务架构中,服务与服务之间相互调用,由于网络或自身的问题并不能保证服务100%可用。如果单个服务瘫痪可能导致整个调用链路失败,最终会使整个服务失败。
sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来保障微服务的稳定性。

3.1.1 服务雪崩效应

image.png
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 逻辑封装成一个命令对象 HystrixCommandHystrixObservableCommand,其底层的执行是基于 RxJava 实现的。每个 Command 创建时都要指定 commandKeygroupKey(用于区分资源)以及对应的隔离策略(线程池隔离 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打包命令

  1. mvn clean package -DskipTests=true

得到 sentinel-dashboard.jar
在target文件夹下 运行cmd 可自定义nacos信息和端口 默认8858

  1. 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

  1. ADD sentinel-dashboard.jar app.jar
  2. EXPOSE 8858
  3. EXPOSE 8719
  4. CMD java -jar -Dserver.port=8858 app.jar

把Dockerfile和jar包放在同一目录下

  1. docker build -t sentinel-dashboard:1.8.2 .
  1. docker run -d -it -p8858:8858 -p8719:8719 -e TZ="Asia/Shanghai" --name sen-dashboard sentinel-dashboard:1.8.2

image.png

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 过高的时候可以自动将流量调整成合适的形状。常用的有:

  • 直接拒绝模式:即超出的请求直接拒绝。

  • 慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
    cloud Alibaba(3)-sentinel服务容错 - 图20
  • 匀速器模式:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。
    cloud Alibaba(3)-sentinel服务容错 - 图21

    3.3.3 系统负载保护

    Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想。当系统负载较高的时候,如果仍持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
    image.png

    3.4 微服务集成sentinel

    pom依赖
    1. <!--sentinel-->
    2. <dependency>
    3. <groupId>com.alibaba.cloud</groupId>
    4. <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    5. </dependency>
    6. <!--持久化-->
    7. <dependency>
    8. <groupId>com.alibaba.csp</groupId>
    9. <artifactId>sentinel-datasource-nacos</artifactId>
    10. </dependency>
    11. <!--通讯、实时监控-->
    12. <dependency>
    13. <groupId>com.alibaba.csp</groupId>
    14. <artifactId>sentinel-transport-simple-http</artifactId>
    15. </dependency>

bootstrap.yml

  1. server:
  2. port: 8002
  3. spring:
  4. application:
  5. name: consumer-order8002
  6. cloud:
  7. nacos:
  8. config:
  9. namespace: 3d0a77b8-817f-499b-bfda-f90d5a6e4dab
  10. server-addr: 192.168.19.128:13306
  11. group: DEFAULT_GROUP
  12. file-extension: yaml
  13. username: nacos
  14. password: nacos
  15. discovery:
  16. namespace: 3d0a77b8-817f-499b-bfda-f90d5a6e4dab
  17. server-addr: 192.168.19.128:13306
  18. group: DEFAULT_GROUP
  19. username: nacos
  20. password: nacos
  21. sentinel:
  22. transport:
  23. dashboard: 192.168.19.128:8858 #127.0.0.1:8080
  24. port: 8719 #默认为8719 ,假如被占用会自动从8719开始依次+1扫描,直到未被占用的端口
  25. # clientIp: localhost #设置心跳地址 让控制台出现实时监控
  26. web-context-unify: false
  27. datasource:
  28. flow:
  29. nacos:
  30. server-addr: ${spring.cloud.nacos.config.server-addr}
  31. namespace: ${spring.cloud.nacos.config.namespace}
  32. groupId: SENTINEL_GROUP
  33. username: ${spring.cloud.nacos.config.username}
  34. password: ${spring.cloud.nacos.config.password}
  35. dataId: ${spring.application.name}-flow-rules
  36. rule-type: flow
  37. degrade:
  38. nacos:
  39. server-addr: ${spring.cloud.nacos.config.server-addr}
  40. namespace: ${spring.cloud.nacos.config.namespace}
  41. groupId: SENTINEL_GROUP
  42. username: ${spring.cloud.nacos.config.username}
  43. password: ${spring.cloud.nacos.config.password}
  44. dataId: ${spring.application.name}-degrade-rules
  45. rule-type: degrade
  46. system:
  47. nacos:
  48. server-addr: ${spring.cloud.nacos.config.server-addr}
  49. namespace: ${spring.cloud.nacos.config.namespace}
  50. groupId: SENTINEL_GROUP
  51. username: ${spring.cloud.nacos.config.username}
  52. password: ${spring.cloud.nacos.config.password}
  53. dataId: ${spring.application.name}-system-rules
  54. rule-type: system
  55. authority:
  56. nacos:
  57. server-addr: ${spring.cloud.nacos.config.server-addr}
  58. namespace: ${spring.cloud.nacos.config.namespace}
  59. groupId: SENTINEL_GROUP
  60. username: ${spring.cloud.nacos.config.username}
  61. password: ${spring.cloud.nacos.config.password}
  62. dataId: ${spring.application.name}-authority-rules
  63. rule-type: authority
  64. param-flow:
  65. nacos:
  66. server-addr: ${spring.cloud.nacos.config.server-addr}
  67. namespace: ${spring.cloud.nacos.config.namespace}
  68. groupId: SENTINEL_GROUP
  69. username: ${spring.cloud.nacos.config.username}
  70. password: ${spring.cloud.nacos.config.password}
  71. dataId: ${spring.application.name}-param-flow-rules
  72. rule-type: param-flow
  73. management:
  74. endpoints:
  75. web:
  76. exposure:
  77. include: "*" # 暴露健康检测的接口
  78. feign:
  79. sentinel:
  80. enabled: true

3.5 sentinel规则

3.5.1 流控规则

流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

3.5.1.1 qps-直接-快速失败

  1. /**
  2. * qps-直接-快速失败
  3. * 设置QPS单机阈值为2 当每秒请求量大于2的时候开始限流。
  4. * Blocked by Sentinel (flow limiting)
  5. *
  6. * @return
  7. */
  8. @GetMapping(value = "/flow/qps/directly/failFast")
  9. public String flowQpsDirectlyFailFast() {
  10. return "qps-直接-快速失败";
  11. }

image.png image.png

3.5.1.2 qps-关联-快速失败

  1. /**
  2. * qps-关联-快速失败 -- 资源1
  3. * 当该请求关联到flowQpsRelevanceFailFast2,设置qps阈值为2 不断请求/flow/qps/relevance/failFast2 当阈值大于2时 /flow/qps/relevance/failFast1将被限流
  4. *
  5. * @return
  6. */
  7. @GetMapping(value = "/flow/qps/relevance/failFast1")
  8. public String flowQpsRelevanceFailFast1() {
  9. return "qps-关联-快速失败-资源1";
  10. }
  11. /**
  12. * qps-关联-快速失败 -- 关联资源
  13. *
  14. * @return
  15. */
  16. @GetMapping(value = "/flow/qps/relevance/failFast2")
  17. public String flowQpsRelevanceFailFast2() {
  18. return "qps-关联-快速失败-关联资源";
  19. }

image.png image.png

3.5.1.3 qps-链路-快速失败

使用@SentinelResource 定义一个资源

  1. /**
  2. * sentinelResource 资源 对上级资源限App限流 对Mini小程序没有影响
  3. * 可以通过配置关闭:spring.cloud.sentinel.web-context-unify=false
  4. * App 限流后被报错 com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
  5. *
  6. * @return
  7. */
  8. @SentinelResource("flow-qps-link")
  9. public String sentinelResource() {
  10. return "qps-链路-快速失败";
  11. }
  1. /**
  2. * qps-链路-快速失败 --资源Mini
  3. * 链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。
  4. * 它的功能有点类似于针对来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。 用到注解@SentinelResource
  5. *
  6. * @return
  7. */
  8. @GetMapping(value = "/flow/qps/link/failFastMini")
  9. public String flowQpsLinkFailFastMini() {
  10. String sentinelResource = sentinelFlowService.sentinelResource();
  11. return sentinelResource + "-资源小程序端";
  12. }
  13. /**
  14. * qps-链路-快速失败 --资源App
  15. *
  16. * @return
  17. */
  18. @GetMapping(value = "/flow/qps/link/failFastApp")
  19. public String flowQpsLinkFailFastApp() {
  20. String sentinelResource = sentinelFlowService.sentinelResource();
  21. return sentinelResource + "-资源app端";
  22. }
  1. /**
  2. * sentinelResource 资源 对上级资源限App限流 对Mini小程序没有影响
  3. * App 限流后被报错 com.alibaba.csp.sentinel.slots.block.flow.FlowException: null
  4. *
  5. * @return
  6. */
  7. @SentinelResource("flow-qps-link")
  8. public String sentinelResource() {
  9. return "qps-链路-快速失败";
  10. }

image.png image.png

3.5.1.4 qps—流控效果-Warm Up

  1. /**
  2. * 它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。
  3. * 设置阈值为15,预热时间3s 刚开始请求最大只能请求5次/s 超过会被限流 3s后阈值变成15
  4. *
  5. * @return
  6. */
  7. @GetMapping(value = "/flow/qps/directly/warmUp")
  8. public String flowQpsDirectlyWarmUp() {
  9. return "qps-直接-Warm Up 预热处理";
  10. }

刚开始刷新会出现限流,后面一直刷新(不超过15次/s)就不会出现限流
image.png image.png

3.5.1.5 qps—流控效果-排队等待

  1. /**
  2. * 让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。
  3. * 设置qps为3,超时时间150ms 请求过来最大通过3个/s 超过会等待 等待超过150ms被丢弃 返回 Blocked by Sentinel (flow limiting)
  4. *
  5. * @return
  6. */
  7. @GetMapping(value = "/flow/qps/directly/lineUp")
  8. public String flowQpsDirectlylineUp() {
  9. return "qps-直接-lineUp 排队等待";
  10. }

image.png image.png

3.5.1.6 线程数—流控模式-直接失败

  1. /**
  2. * 线程数-直接失败 设置最大线程数为2
  3. * 用jmeter 1s请求5个线程 再使用浏览器请求接口 被流控
  4. *
  5. * @return
  6. */
  7. @GetMapping(value = "/flow/thread/directly")
  8. public String flowThreadDirectly() {
  9. return "线程数-直接";
  10. }

image.png image.png

3.5.1.7 线程数—流控模式-关联

  1. /**
  2. * 线程数-关联 -- 资源1
  3. * 设置 资源1 关联资源 资源2 设置线程数阈值为2 用jmeter 1s请求5个线程压资源2 这个时候请求资源1被限流
  4. *
  5. * @return
  6. */
  7. @GetMapping(value = "/flow/thread/relevance1")
  8. public String flowThreadrelevance1() {
  9. return "线程数-关联-资源1";
  10. }
  11. /**
  12. * 线程数-关联 -- 资源2
  13. *
  14. * @return
  15. */
  16. @GetMapping(value = "/flow/thread/relevance2")
  17. public String flowThreadrelevance2() {
  18. return "线程数-关联-资源2";
  19. }

image.png image.png

3.5.2 熔断规则

3.5.2.1 熔断策略-慢调用比例

  1. /**
  2. * 熔断策略-慢调用比例
  3. * 最大RT(ms): 平均响应时间 最大4900 可通过-Dcsp.sentinel.statistic.max.rt=xxx设置 请求的响应时间大于该值则统计为慢调用
  4. * 比例阈值(0.0-1.0):熔断的慢调用比例 (超过最大RT的比例数)
  5. * 熔断时长:被熔断后多长时间不能访问
  6. * 最小请求数目:单位统计时长(s) 内请求数目大于设置的最小请求数目,并且 慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
  7. * <p>
  8. * <p>
  9. * 经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求响应时间小于设置的最大 RT 则结束熔断,若大于设置的最大 RT 则会再次被熔断。
  10. * example:设置最大RT:1ms 比例阈值:0.2 熔断:3s 最小请求数:2 进行测试 在1s内请求大于2个数目 肯定超过最大RT 比例也超过了20% 所以被限流
  11. *
  12. * @return
  13. */
  14. @GetMapping(value = "/degrade/slowCallRatio")
  15. public String degradeSlowCallRatio() {
  16. return "熔断策略-慢调用比例";
  17. }

image.png image.png

3.5.2.2 熔断策略-异常比例

正常访问情况下,每三次就会出现一次异常,当异常高于设定的条件时就会触发熔断。

  1. int num = 0;
  2. /**
  3. * 熔断策略-异常比例
  4. * 在统计周期内资源请求访问异常的比例大于设定的阈值,则接下来的熔断周期内对资源的访问会自动地被熔断
  5. * 设置比例阈值0.3 熔断时长4s 最小请求数2 当满足单位时间(s)请求大于2 并且异常比例大于0.2 将会触发熔断
  6. *
  7. * @return
  8. */
  9. @GetMapping(value = "/degrade/exceptionRatio")
  10. public String degradeExceptionRatio() {
  11. num++;
  12. if (num % 3 == 0) {
  13. throw new RuntimeException("触发异常比例熔断策略");
  14. }
  15. return "熔断策略-异常比例";
  16. }

image.pngimage.pngimage.png

3.5.2.3 熔断策略-异常数

  1. /**
  2. * 熔断策略-异常数
  3. * 在统计周期内资源请求访问异常数大于设定的阈值,则接下来的熔断周期内对资源的访问会自动地被熔断
  4. * 设置异常数2 熔断时长5s 最小请求数2 当满足单位时间(s)请求大于2 并且异常数大于2 将会触发熔断
  5. *
  6. * @return
  7. */
  8. @GetMapping(value = "/degrade/exceptionNum")
  9. public String degradeExceptionNum() {
  10. num++;
  11. if (num % 3 == 0) {
  12. throw new RuntimeException("触发异常数熔断策略");
  13. }
  14. return "熔断策略-异常数";
  15. }

image.pngimage.pngimage.png

3.5.3 热点规则

3.5.3.1 热点策略-基础策略

  1. /**
  2. * 热点规则配置
  3. * 必须使用@SentinelResource注解才能生效
  4. * 设置参数索引为0也就是name参数 阈值为3 窗口为1; 在1s的统计时间内传入name参数的qps超过3次 就会被限流
  5. * <p>
  6. * http://localhost:8002/hotkey/qps/base?name=zhangsan&age=18
  7. * 异常: com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException: zhangsan
  8. *
  9. * @param name
  10. * @param age
  11. * @return
  12. */
  13. @SentinelResource("/hotkey/qps/base")
  14. @GetMapping(value = "/hotkey/qps/base")
  15. public String hotkeyQpsBase(String name, Integer age) {
  16. return "热点规则基础:" + name + age;
  17. }

image.pngimage.png

3.5.3.2 热点策略-高级策略

  1. /**
  2. * 热点规则 高级配置
  3. * 必须使用@SentinelResource注解才能生效
  4. * 添加参数例外项 支持byte、char、float、long、double、int、java.lang.String 6个基本类型和Sting类型
  5. * 添加一个参数索引为1 阈值为2统计窗口为1,参数例外项中 参数类型为int 参数值为18阈值为10000,所以当传入参数age=18小于每秒10000次的时候不会触发限流,其他的和基础配置一样
  6. *
  7. * @param name
  8. * @param age
  9. * @return
  10. */
  11. @SentinelResource("/hotkey/qps/high")
  12. @GetMapping(value = "/hotkey/qps/high")
  13. public String hotkeyQpsHigh(String name, Integer age) {
  14. return "热点规则高级:" + name + age;
  15. }

首先新增一个基础配置,然后在这基础上修改为高级配置
image.pngimage.pngimage.png

3.5.4 授权规则

添加一个请求拦截,可以从请求参数或者请求头里拿到请求的数据,配置一个授权可以进行降级。

  1. @Component
  2. @Log4j2
  3. public class RequestOriginParserDefinition implements RequestOriginParser {
  4. @Override
  5. public String parseOrigin(HttpServletRequest request) {
  6. String serviceName = request.getParameter("serviceName");
  7. if (StringUtils.isNotBlank(serviceName)) {
  8. log.info("serviceName:{}", serviceName);
  9. return serviceName;
  10. }
  11. return request.getRemoteAddr();
  12. }
  13. }
  1. /**
  2. * 配置pc为黑名单
  3. * http://localhost:8002/authority/message?serviceName=pc
  4. * 当配置在注解的资源 /authority/resource 上会直接报错 com.alibaba.csp.sentinel.slots.block.authority.AuthorityException: null
  5. * 当配置在 /authority/message 接口上 会直接出现限流 Blocked by Sentinel (flow limiting) 内部实现对异常的兜底 两者都实现了功能
  6. *
  7. * @return
  8. */
  9. @GetMapping(value = "/authority/message")
  10. @SentinelResource("/authority/resource")
  11. public String authorityMessage() {
  12. return "授权规则配置";
  13. }

image.pngimage.png

3.5.5 系统规则

系统保护规则是从应用级别的入口流量进行控制,从单台机器的总体 Load、RT、入口 QPS 、CPU使用率和线程数五个维度监控应用数据,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量 (进入应用的流量) 生效。

  • Load(仅对 Linux/Unix-like 机器生效):当系统 load1 超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。系统容量由系统的 maxQps minRt 计算得出。设定参考值一般是 CPU cores 2.5。
  • RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
  • 线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  • 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
  • CPU使用率:当单台机器上所有入口流量的 CPU使用率达到阈值即触发系统保护。

    image.png

    3.6 自定义异常返回

当被sentinel限流之后,页面会出现 Blocked by Sentinel (flow limiting) 或者出现error页面,所以对异常进行拦截 处理返回

  1. @Component
  2. public class SentinelExceptionHandler implements BlockExceptionHandler {
  3. @Override
  4. public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
  5. String msg = null;
  6. if (e instanceof FlowException) {
  7. msg = "接口被限流了";
  8. } else if (e instanceof DegradeException) {
  9. msg = "接口被熔断流了";
  10. } else if (e instanceof ParamFlowException) {
  11. msg = "热点参数限流";
  12. } else if (e instanceof AuthorityException) {
  13. msg = "授权规则限流";
  14. }
  15. // http状态码
  16. response.setStatus(500);
  17. response.setCharacterEncoding("utf-8");
  18. response.setHeader("Content-Type", "application/json;charset=utf-8");
  19. response.setContentType("application/json;charset=utf-8");
  20. new ObjectMapper().writeValue(response.getWriter(), CommonResult.error(msg));
  21. }
  22. }

image.png

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的异常
  1. @RestController
  2. public class SentinelResourcesController {
  3. @Autowired
  4. SentinelResourcesService sentinelResourcesService;
  5. @GetMapping(value = "/sentinel/resources1")
  6. public String resources1() {
  7. return sentinelResourcesService.message();
  8. }
  9. @GetMapping(value = "/sentinel/resources2")
  10. public String resources2() {
  11. return sentinelResourcesService.message2();
  12. }
  13. }
  1. @Service
  2. @Slf4j
  3. public class SentinelResourcesService {
  4. int i = 0;
  5. /**
  6. * 直接将限流和降级方法定义在方法中
  7. *
  8. * @return
  9. */
  10. @SentinelResource(
  11. value = "resource-message1",
  12. //指定发生BlockException是 进入的方法
  13. blockHandler = "blockHandlerMethod",
  14. //指定发生Throwable时 进入的方法
  15. fallback = "fallbackMethod"
  16. )
  17. public String message() {
  18. i++;
  19. if (i % 3 == 0) {
  20. throw new RuntimeException("我是故意的");
  21. }
  22. return "message11111";
  23. }
  24. public String blockHandlerMethod(BlockException e) {
  25. log.error("FlowException:", e);
  26. return "SentinelResourcesService.message 接口被限流了";
  27. }
  28. public String fallbackMethod(Throwable e) {
  29. log.error("RuntimeException:", e);
  30. return "接口发生了异常";
  31. }
  32. /**
  33. * 将限流和降级方法外置到单独的类中
  34. *
  35. * @return
  36. */
  37. @SentinelResource(
  38. value = "resource-message2",
  39. blockHandlerClass = SentinelResourcesServiceBlockHandler.class,
  40. blockHandler = "blockHandlerMethod2",
  41. fallbackClass = SentinelResourcesServiceFallback.class,
  42. fallback = "fallbackMethod2"
  43. )
  44. public String message2() {
  45. i++;
  46. if (i % 3 == 0) {
  47. throw new RuntimeException("我还是故意的");
  48. }
  49. return "message22222";
  50. }
  51. }

3.7.1 设置在class内部处理异常

请求/sentinel/resources1接口
设置如下熔断策略,当出现降级时 ,会走自定义降级的方法,优先级比全局异常拦截高;当出现程序异常时,会走自定义fallback方法
image.pngimage.pngimage.png

3.7.2 将异常放在其他类处理

请求/sentinel/resources2接口

  1. @Slf4j
  2. public class SentinelResourcesServiceBlockHandler {
  3. /**
  4. * 必须是static 方法
  5. *
  6. * @param e
  7. * @return
  8. */
  9. public static String blockHandlerMethod2(BlockException e) {
  10. log.error("{0}", e);
  11. return "接口被限流了22222222";
  12. }
  13. }
  1. @Slf4j
  2. public class SentinelResourcesServiceFallback {
  3. /**
  4. * 必须是static 方法
  5. *
  6. * @param e
  7. * @return
  8. */
  9. public static String fallbackMethod2(Throwable e) {
  10. log.error("{0}", e);
  11. return "接口发生了异常2222222222222";
  12. }
  13. }

image.pngimage.pngimage.png

3.7 Feign整合sentinel

  1. 启动product 项目,让consumer8002调用 product
  2. 将product 7001,product 7002 添加sentinel依赖和yml配置,和consumer8002一样
  3. 通过feign调用product

product

  1. @GetMapping("/product/getProduct/{id}")
  2. public CommonResult<Product> getProduct(@PathVariable("id") Integer id) {
  3. Optional<Product> optional = Optional.ofNullable(productService.findBuyId(id));
  4. return optional.map(CommonResult::ok).orElseGet(CommonResult::error);
  5. }

consumer -service

  1. /**
  2. * 需要声明被调用方的serviceName
  3. *
  4. * @author Rem
  5. * @date 2021-07-15
  6. */
  7. @Service
  8. @FeignClient(value = "provider-product",
  9. // 异常处理方法和工厂处理的事情是一样, 只能选择一个
  10. // fallback = ProductServiceFallback.class,
  11. fallbackFactory = ProductServiceFallBackFactory.class)
  12. public interface ProductService {
  13. /**
  14. * 指定调用的方法
  15. *
  16. * @param id
  17. * @return
  18. * @FeignClient+@GetMapping 就是一个完整的请求路径 http://provider-product/product/getProduct/{id};
  19. */
  20. @GetMapping("/product/getProduct/{id}")
  21. CommonResult<Product> getProduct(@PathVariable("id") Integer id);
  22. @GetMapping("/product/test/{id}")
  23. CommonResult<String> test(@PathVariable("id") Integer id);
  24. }

consumer -FallBackFactory 可以查看异常

  1. @Component
  2. public class ProductServiceFallBackFactory implements FallbackFactory<ProductService> {
  3. @Override
  4. public ProductService create(Throwable throwable) {
  5. return new ProductService() {
  6. @Override
  7. public CommonResult<Product> getProduct(Integer id) {
  8. //打印异常
  9. throwable.printStackTrace();
  10. Product product = new Product();
  11. product.setPid(-1);
  12. return CommonResult.error(product);
  13. }
  14. @Override
  15. public CommonResult<String> test(Integer id) {
  16. return null;
  17. }
  18. };
  19. }
  20. }

consumer - Fallback

  1. @Slf4j
  2. @Component
  3. public class ProductServiceFallback implements ProductService {
  4. /**
  5. * 当调用product 调用失败的时候回自动进入该方法
  6. * <p>
  7. * 设置调用7002出现异常 7001正常 然后调用创建订单接口,每间隔一次就会走进该方法
  8. *
  9. * @param id
  10. * @return
  11. */
  12. @Override
  13. public CommonResult<Product> getProduct(Integer id) {
  14. Product product = new Product();
  15. product.setPid(-1);
  16. return CommonResult.error(product);
  17. }
  18. @Override
  19. public CommonResult<String> test(Integer id) {
  20. return null;
  21. }
  22. }

consumer 接口

  1. @GetMapping("/createOrder/{pid}")
  2. public CommonResult createOrder(@PathVariable("pid") Integer pid) {
  3. CommonResult<Product> productCommonResult = productService.getProduct(pid);
  4. log.info("通过feign调用的数据:" + productCommonResult);
  5. if (productCommonResult.getCode().equals(0)) {
  6. orderService.createOrder(productCommonResult.getData());
  7. return CommonResult.ok();
  8. }
  9. return CommonResult.error();
  10. }

设置 product - getProduct 接口限流,使用consumer调用 当被限流的时候会处理失败
image.pngimage.png

会进入兜底方法 打印出异常信息
image.png