9. SpringCloud Alibaba Sentinel 流量控制、熔断降级、系统负载,热点规则的部署设置讲解
@
目录
- [9. SpringCloud Alibaba Sentinel 流量控制、熔断降级、系统负载,热点规则的部署设置讲解](#9. SpringCloud Alibaba Sentinel 流量控制、熔断降级、系统负载,热点规则的部署设置讲解)
- [1. Sentinel 是什么?](#1. Sentinel 是什么?)
- [2. Sentinel 控制台](#2. Sentinel 控制台)
- [3. Sentinel 下载&安装&运行](#3. Sentinel 下载&安装&运行)
- [4. Sentinel 监控微服务](#4. Sentinel 监控微服务)
- [5. Sentinel 流量控制](#5. Sentinel 流量控制)
- [5.1 流量控制实例-QPS](#5.1 流量控制实例-QPS)
- [5.2 流量控制实例-线程数](#5.2 流量控制实例-线程数)
- [5.3 流量控制实例-关联](#5.3 流量控制实例-关联)
- [5.4 流量控制实例-Warm up](#5.4 流量控制实例-Warm up)
- [5.5 流量控制实例-排队](#5.5 流量控制实例-排队)
- [6. Sentinel 熔断降级](#6. Sentinel 熔断降级)
- [6.1 熔断策略](#6.1 熔断策略)
- [6.2 熔断降级实例-慢调用比例](#6.2 熔断降级实例-慢调用比例)
- [6.3 熔断降级实例-异常比例](#6.3 熔断降级实例-异常比例)
- [6.4 熔断降级实例-异常数](#6.4 熔断降级实例-异常数)
- [7. Sentinel 热点规则](#7. Sentinel 热点规则)
- [7.1 热点 Key 限流-实例](#7.1 热点 Key 限流-实例)
- [8. 系统规则](#8. 系统规则)
- [9. @SentinelResource 自定义全局限流处理类](#9. @SentinelResource 自定义全局限流处理类)
- [9.1 @ fallback](#9.1 @ fallback)
- [9.2 @exceptionsToIgnore 忽略](#9.2 @exceptionsToIgnore 忽略)
- [10. 补充: 接入 Sentinel 的方式](#10. 补充: 接入 Sentinel 的方式)
- [11. 最后:](#11. 最后:)
SpringCloud Alibaba Sentinel官方地址:https://github.com/alibaba/Sentinel
SpringCloud Alibaba Sentinel 快速开始: https://sentinelguard.io/zh-cn/docs/quick-start.html
SpringCloud Alibaba Sentinel官方中文地址: https://github.com/alibaba/Sentinel/wiki/介绍
SpringCloud Alibaba Sentinel 的使用手册:https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_al ibaba_sentinel
1. 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 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
一句话: Sentinel: 分布式系统的流量防卫兵, 保护你的微服务
Sentinel 核心功能:
拿旅游景点举个示例,每个旅游景点通常都会有最大的接待量,不可能无限制的放游 客进入,比如长城每天只卖八万张票,超过八万的游客,无法买票进入,因为如果超过 八万人,景点的工作人员可能就忙不过来,过于拥挤的景点也会影响游客的体验和心情, 并且还会有安全隐患;只卖 N 张票,这就是一种限流的手段
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
- 在调用系统的时候,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生 堆积,如下图:
- 熔断降级可以解决这个问题,所谓的熔断降级就是当检测到调用链路中某个资源出现不 稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限 制,让请求快速失败,避免影响到其它的资源而导致级联故障。
Sentinel 系统自适应保护从整体维度对应用入口流量进行控制,结合应用的 Load、总体平均 RT、入口 QPS 和线程数等几个维度的监控指标,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
根据系统能够处理的请求,和允许进来的请求,来做平衡,追求的目标是在系统不被 拖垮的情况下, 提高系统的吞吐率
- 消息削峰填谷:
某瞬时来了大流量的请求, 而如果此时要处理所有请求,很可能会导致系统负载过高, 影响稳定性。但其实可能后面几秒之内都没有消息投递,若直接把多余的消息丢掉则没 有充分利用系统处理消息的能力
Sentinel 的 Rate Limiter 模式能在某一段时间间隔内以匀速方式处理这样的请求, 充分利 用系统的处理能力, 也就是削峰填谷, 保证资源的稳定性
2. Sentinel 控制台
Sentinel 控制台官网说明地址:https://sentinelguard.io/zh-cn/docs/dashboard.html
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。这里,我们将会详细讲述如何通过简单的步骤就可以使用这些功能。
接下来,我们将会逐一介绍如何整合 Sentinel 核心库和 Dashboard,让它发挥最大的作用。同时我们也在阿里云上提供企业级的 Sentinel 服务:AHAS Sentinel 控制台,您只需要几个简单的步骤,就能最直观地看到控制台如何实现这些功能,并体验多样化的监控及全自动托管的集群流控能力。
Sentinel 控制台包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
注意:Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,若希望在生产环境使用请根据文档自行进行定制和改造。
3. Sentinel 下载&安装&运行
下载地址:https://github.com/alibaba/Sentinel/releases/tag/v1.8.0
运行:进入到对应 sentinel-dashoard-1.8.0 的目录当中,使用 cmd
bash
java -jar sentinel-dashboard-1.8.0.jar
注意: Sentinel 控制台 默认端口是 8080。
如果端口被占用了,我们可以更改 Sentinel 控制台的端口。
bash
java -jar sentinel-dashboard-1.8.0.jar --server.port=9090
访问:
浏览器: http://localhost:8080。注意:这里我的8080端口被占用了,所以这里我用的是9090端口。
浏览器输入: http://localhost:9090,
用户和密码都是 sentinel
登录成功后的页面, 目前是空的,因为 sentinel 还没有进行流量监控。
4. Sentinel 监控微服务
使用 Sentinel 控制台对 member-service-nacos-provider-10004 微服务 进行实时监控。
当调用了 member-service-nacos-provider-10004 微服务时 可以监控到请求的 url/QPS/ ,
响应时间/流量
使用 Sentinel 需要进入对应的 jar 包。如下
xml
<!-- 引入 alibaba-sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
在 application.yaml 文件当中配置 Sentinel 配置信息。如下图,所示:
yaml
spring:
application:
name: member-service-nacos-provider # 配置应用的名称
# 配置 nacos
cloud:
# 配置 Sentinel 的信息
sentinel:
transport:
dashboard: localhost:9090 # 注意这里按照你自己配置的 sentinel 端口信息,默认是8080端口
# 解读: spring.cloud.sentinel.transport.port
# 1. spring.cloud.sentinel.transport.port 端口配置会在被监控的微服务当中
# 对应的机器上启动一个 Http Server
# 2. 该 Server 会 与 Sentinel 控制台做交互
# 3. 比如: Sentinel 控制台添加了 1 个限流规则,会把规则数据 push 给这个
# Http Server 接收,Http Server 再将规则注册到 Sentinel 当中
# 简单的说明: spring.cloud.sentinel.transport.port: 指定被监控的微服务应用与
# Sentinel 控制台交互的端口,微服务应用本地会起一个该端口占用的 Http Server
port: 8719 # 默认的占用的端口是 8719,假如被占用了,会自动从 8719开始依次 + 1 进行一个扫描,
# 直到找到为占用的端口,进行一个使用。
yaml
server:
port: 10004
spring:
application:
name: member-service-nacos-provider # 配置应用的名称
# 配置 nacos
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 配置注册到哪个 Nacos Server的地址
# 配置 Sentinel 的信息
sentinel:
transport:
dashboard: localhost:9090 # 注意这里按照你自己配置的 sentinel 端口信息,默认是8080端口
# 解读: spring.cloud.sentinel.transport.port
# 1. spring.cloud.sentinel.transport.port 端口配置会在被监控的微服务当中
# 对应的机器上启动一个 Http Server
# 2. 该 Server 会 与 Sentinel 控制台做交互
# 3. 比如: Sentinel 控制台添加了 1 个限流规则,会把规则数据 push 给这个
# Http Server 接收,Http Server 再将规则注册到 Sentinel 当中
# 简单的说明: spring.cloud.sentinel.transport.port: 指定被监控的微服务应用与
# Sentinel 控制台交互的端口,微服务应用本地会起一个该端口占用的 Http Server
port: 8719 # 默认的占用的端口是 8719,假如被占用了,会自动从 8719开始依次 + 1 进行一个扫描,
# 直到找到为占用的端口,进行一个使用。
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 配置 alibaba 的数据库连接池
password: MySQL123
username: root
url: jdbc:mysql://localhost:3306/e_commerce_center_db?useSSL=true&useUnicode=true&characterEncoding=UTF-8
mybatis:
mapper-locations: classpath:mapper/*.xml # 指定 mapper.xml 文件位置 classpath 表示 resources 目录下
type-aliases-package: com.rainbowsea.springcloud.entity # 实例 bean 类所在的包,这样可以通过类名的方式
# 配置暴露所有的监控点:
management:
endpoints:
web:
exposure:
include: '*'
测试:
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: localhost:10004/member/get/1
Sentinel 控制台监控页面,浏览器输入: http://localhost:10004/member/get/1
进入到 Sentinel 查看实时监控效果, http://localhost:8080/#/dashboard
注意事项和细节:
- QPS: Queries Per Second(每秒查询率),是服务器每秒响应的查询次数
- Sentinel 采用的是懒加载, 只有调用了某个接口/服务,才能看到监控数据。
5. Sentinel 流量控制
规则
对上图的解读:
- **资源名∶**唯一名称,默认请求路径
- **针对来源∶**Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来 源)
- 阈值类型/单机阈值∶
- **QPS(每秒钟的请求数量)∶**当调用该 api 的 QPS 达到阈值的时候,进行限流 线程数∶当调用该 api 的线程数达到阈值的时候,进行限流
解读 QPS 和线程数的区别, 注意听, 比如 QPS 和线程我们都设置阈值为 1:
- 对 QPS 而言,如果 1秒内,客户端发出了2次请求,就到达阈值,从而限流。 QPS 表示: 每秒钟的请求数量
- 对线程而言,如果在 1秒内,客户端降发出了 2 次请求,不一定达到线程限制的阈值,为什么呢,假设我们 1次请求后台会创建一个线程,但是这个请求完成时间是 0.1秒(可以视为该请求对应的线程存活 0.1 秒),
所以当客户端第2次请求时,(比如客户端是在 0.3 秒发出的),这时第1个请求的线程就是已经结束了,因此就没有达到线程的阈值,也不会
限流。- 可以这样理解,如果 1 个请求对应的线程平均执行时间为 0.1 那么,就相当于 QPS 为 10
是否集群:不需要集群。
5.1 流量控制实例-QPS
演示: 当调用 member-service-nacos-provider-10004 的 /member/get/ 接口/API 时,限制 1 秒内最多访问 1 次,否则直接失败,抛异常.
配置实现步骤:
- 为/member/get/1 增加流控规则
- 在流控规则菜单,可以看到新增的流控规则
测试
启动 Nacos Server 8848
启动 Sentinel909控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: localhost:10004/member/get/1
Sentinel 控制台监控页面:
浏览器输入: http://localhost:10004/member/get/1 , 1 秒钟内访问次数不超过 1 次, 页 面显示正常
浏览器输入: http://localhost:10004/member/get/1 , 1 秒钟内访问次数超过 1 次, 页面 出现错误提示
注意事项和细节:
- 流量规则改动,实时生效,不需重启微服务 , Sentine 控制台
- 在 sentinel 配 置 流 量 规 则 时 , 如 何 配 置 通 配 符 问 题 , 比 如 /member/get/1 和/member/get/2 统一使用一个规则
方案一 :在sentinel中 /member/get?id=1 和 /member/get?id=2 被统一认为是 /member/get 所以只要对/member/get 限流就OK了.
java方案1: 在 sentinel 中/member/get?id=1 和 /member/get?id=2 被统一认为是 /member/get 所以 只要对 /member/get 限流就OK了。 /** * 这里我们使用 url占位符 + @PathVariable * * @param id * @return */ //@GetMapping("/member/get/{id}") // 在sentinel中 /member/get?id=1 和 /member/get?id=2 被统一认为是 /member/get 所以只要对/member/get 限流就OK了. 进行统一的限流 @RequestMapping(value = "/member/get/", params = "id", method = RequestMethod.GET) //public Result getMemberById(@PathVariable("id") Long id, HttpServletRequest request) { public Result getParameter(Long id) { Member member = memberService.queryMemberById(id); //String color = request.getParameter("color"); //String age = request.getParameter("age"); // 模拟超时 ,这里暂停 5秒 /* try { TimeUnit.SECONDS.sleep(5); } catch (Exception e) { e.printStackTrace(); }*/ // 使用 Result 把查询到的结果返回 if (member != null) { //return Result.success("查询会员成功 member-service-nacos-provider-10004 color" + color + "age" + age, member); return Result.success("查询会员成功 member-service-nacos-provider-10004 color",member); } else { return Result.error("402", "ID" + id + "不存在 member-service-nacos-provider-10004 "); } }
方案二: URL 资源清洗可以通过 UrlCleaner 接口来实现资源清洗,也就是对于 /member/get/{id} 这个 URL,
我们可以统一归集到 /member/get/* 资源下,具体的代码实现如下: 需要实现 UrlCleaner接口
并重写其中的 clean 方法即可。
javapackage com.rainbowsea.springcloud.controller; import com.alibaba.csp.sentinel.adapter.servlet.callback.UrlCleaner; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; /** * 方案2: URL 资源清洗 * 可以通过 UrlCleaner 接口来实现资源清洗,也就是对于 /member/get/{id} 这个 URL, * 我们可以统一归集到 /member/get/* 资源下,具体的代码实现如下: 需要实现 UrlCleaner接口 * 并重写其中的 clean 方法即可 */ @Component // 注意:同样要被 Spring IOC 容器管理起来 public class CustomUrlCleaner implements UrlCleaner { @Override public String clean(String originUrl) { // 判断字符串是否为空 Null // 特别注意: StringUtils.isBlank 是在:org.apache.commons.lang3.StringUtils 包下的 if (StringUtils.isBlank(originUrl)) { return originUrl; } if (originUrl.startsWith("/member/get")) { // 1.如果请求的是接口 /member/get 开头的,比如: /member/get/1 // 2.给sentinel 的返回的资源名就是 /member/get/* // 3. 在 sentinel 对 /member/get/* 添加流控规则即可 return "/member/get/*"; } return originUrl; } }
5.2 流量控制实例-线程数
通过 Sentinel 实现 流量控制
当调用 member-service-nacos-provider-10004 的 /member/get/* 接口/API 时,限制 只有一个工作线程,否则直接失败,抛异常.
配置实现步骤:
- 为/member/get/* 增加流控规则
- 在流控规则菜单,可以看到新增的流控规则
测试:
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: localhost:10004/member/get/1
结果页面:
浏览器输入: http://localhost:10004/member/get/1 , 快速刷新, 页面显示正常(原因 是服务执行时间很短,刷新下一次的时候,启动的工作线程,已经完成)
为了看到效果,我们修改下 com/rainbowsea/springcloud/controller/MemberController.java。模拟延时。
java// 让线程休眠1s,模拟执行时间 try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
java@GetMapping("/member/get/{id}") public Result getMemberById(@PathVariable("id") Long id) { Member member = memberService.queryMemberById(id); //String color = request.getParameter("color"); //String age = request.getParameter("age"); // 让线程休眠1s,模拟执行时间 try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(" enter getMemberById... 当前线程id = " + Thread.currentThread().getId() + "时间 = " + new Date()); // 使用 Result 把查询到的结果返回 if (member != null) { //return Result.success("查询会员成功 member-service-nacos-provider-10004 color" + color + "age" + age, member); return Result.success("查询会员成功 member-service-nacos-provider-10004 color" + member); //return Result.success("查询会员成功 member-service-nacos-provider-10004 color",member); } else { return Result.error("402", "ID" + id + "不存在 member-service-nacos-provider-10004 "); } }
重启 member-service-nacos-provider-10004 , 注意需要重新加入流控规则.
浏览器输入: http://localhost:10004/member/get/1 , 快速刷新 页面出现异常.
注意事项和细节:
- 当我们请求一次微服务的 API 接口时,后台会启动一个线程 [演示下]
阈值类型 QPS 和 线程数的区别讨论
如果一个线程平均执行时间 为 0.05 秒,就说明在 1秒钟,可以执行 20次(相当于 QPS 为 20)
如果一个线程平均执行时间 为 1秒,说明 1 秒钟,可以执行 1次数(相当于 QPS 为1)
如果一个线程平均执行时间 为 2 秒,说明2秒钟内,才能执行1次请求。
5.3 流量控制实例-关联
关联的含义: 当关联的资源达到阈值时,就限流自己
通过 Sentinel 实现 流量控制
当调用 member-service-nacos-provider-10004 的 /t2 API 接口时,如果 QPS 超过 1,这 时调用 /t1 API 接口 直接接失败,抛异常. 老师梳理 /t2 是关联的资源 , 限流的资源是 /t1
这里使用 postman 模拟高并发访问/t2 ;然后在 postman 执行高并发访问/t2没有结束时,去访问 /t1 才能看到流控异常出现。t1被流控了
配置实现步骤
- 为/t1 增加流控规则
- 在流控规则菜单,可以看到新增的流控规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
Postman 模拟高并发访问/t2
创建新的 http request
保存 request 到 一个新的 collection 中
设置 run collection 参数, 并运行
浏览器访问: http://localhost:10004/t1
注意事项和细节:
在 postman 执行 高并发访问 /t2 没有结束时, 去访问 /t1 才能看到流控异常出现。
5.4 流量控制实例-Warm up
Warm up 介绍:
- 当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长 一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的 增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动, 预热)模式就是为了实现这个目的的。
- 这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等
- 如图:
通常冷启动的过程系统允许通过的 QPS 曲线图(上图)
- 默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设 定的 QPS 阈值
- 这里的threshold 就是最终要达到的QPS阈值.
- 官方介绍 Warm Up 地址链接: https://github.com/alibaba/Sentinel/wiki/限流---冷启动
- 默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈 值
Warm up 称为 冷启动/预热
应用场景: 秒杀在开启瞬间,大流量很容易造成冲垮系统,Warmup 可慢慢的把流量放入,最 终将阀值增长到设置阀值。
通过 Sentinel 实现 流量控制,演示 Warm up
- 调用 member-service-nacos-provider-10004 的 /t2 API 接口,将 QPS 设置为 9, 设置 Warm up 值为 3
- 含义为 请求 /t2 的 QPS 从 threshold / 3( 9 /3 = 3) 开始,经预热时长(3 秒)逐渐升至 设定的 QPS 阈值(9)
- 为什么是 9 / 3, 这个是 3 就是默认冷启动启动加载因子 coldFactor=3
- 测试预期效果: 在前 3 秒,如果访问 /t2 的 QPS 超过 3, 会直接报错,在 3 秒后 访问 /t2 的 QPS 超过 3, 小于等于 9, 是正常访问
配置实现步骤:
- 为/t2 增加流控规则
- 在流控规则菜单,可以看到新增的流控规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: localhost:10004/t2
浏览器访问 http://localhost:10004/t2 快速刷新页面,在前 3 秒,会出现流控异常, 后 3 秒就正常了(如果你刷新非常快 QPS>9 , 仍然会出现流控异常)
注意事项和细节:
- 测试 Warm up 效果不是很好测,如果出不来可以尝试,调整 流控规则: 比如 QPS 为 11, Warm up 预热时间 6 秒
- 如果请求停止(即: 一段时间没有达到阈值), Warm up 过程将重复, 小伙伴可以理解是一个弹 性过程
5.5 流量控制实例-排队
排队方式:这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通 过,对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。比如这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的 空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。 类似前面说的 削峰填谷。
匀速排队,阈值必须设置为 QPS
通过 Sentinel 实现 流量控制-排队
-
调用 member-service-nacos-provider-10004 的 /t2 API 接口,将 QPS 设置为 1
-
当调用 /t2 的 QPS 超过 1 时,不拒绝请求,而是排队等待, 依次执行
-
当等待时间超过 10 秒,则为等待超时.
为了测试看到效果,修改 com/rainbowsea/springcloud/controller/MemberController.java
java
@GetMapping("/t2")
public Result t2() {
// 让线程休眠 1s,模拟执行时间为1s=>当多少个请求就会造成超时
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出线程信息
log.info("执行t2(),线程id={}",Thread.currentThread().getId());
return Result.success("t2执行成功");
}
配置实现步骤:
- 为/t2 增加流控规则。
- 在流控规则菜单,可以看到新增的流控规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: localhost:10004/t2
- 浏览器访问 http://localhost:10004/t2 快速刷新页面 9 次,观察前台/后台输出的情
况
- 输出结果分析
- 没有报错误
- 后台请求排队执行,每隔1s 匀速执行
浏览器访问 http://localhost:10004/t2 快速刷新页面 20 次,当请求等待时间超过 10S, 仍然出现流控异常
6. Sentinel 熔断降级
线程堆积引出熔断降级
- 一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等[架构图]。
- 例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需 要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现 了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用
- 这时,我们对不稳定的服务进行熔断降级,让其快速返回结果,不要造成线程堆积
- 文档地址: https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
- 现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成 复杂的调用链路。
- 链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最 终导致整个链路都不可用。
- 因此需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部 不稳定因素导致整体的雪崩
熔断 降级 限流三者的关系:
- 熔断强调的是服务之间的调用能实现自我恢复的状态
- 限流是从系统的流量入口考虑, 从进入的流量上进行限制 达到保护系统的作用。
- 降级, 是从系统业务的维度考虑,流量大了或者频繁异常, 可以牺牲一些非核心业务,保 护核心流程正常使用。
梳理:
- 熔断是降级方式的一种
- 降级又是限流的一种方式
- 三者都是为了通过一定的方式在流量过大或者出现异常时, 保护系统的手段
6.1 熔断策略
慢调用比例:
- 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的 慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用
- 当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的 比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 熔断时长后, 熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响 应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断
- 配置参考:
异常比例:
- 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最 小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
- 若接下来的一个请求成功完成(没有错误)则结束熔断 否则会再次被熔断
- 异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
- 配置参数
工作示图:
异常数:
- 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔 断
- 经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态)
- 若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断
- 配置参考:
6.2 熔断降级实例-慢调用比例
- 需求: 通过 Sentinel 实现 熔断降级控制-慢调用比例
- 当调用 member-service-nacos-provider-10004 的 /t3 API 接口时,如果在 1s 内持续进 入了 5 个请求,并且请求的平均响应时间超过 200ms, 那么就在未来 10 秒钟内,断路器 打开, 让 /t3 API 接口 微服务不可用
- 后面对/t3 API 接口 访问降到 1S 内 1 个请求,降低访问量了,断路器关闭,微服务恢复
- 修改 com/rainbowsea/springcloud/controller/MemberController.java 增加方法 t3()
为/t3 增加降级规则
- 在流控规则菜单,可以看到新增的降级规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
Postman 测试
- 先创建 collection , 也可以在已经存在的 collection 进行修改。
点击 Run sentinel
浏览器访问: http://localhost:10004/t3
停止 Postman
浏览器访问: http://localhost:10004/t3 , 结果正常了(需要在停止 Postman 10s 后)
注意事项和细节:
- 平均响应时间 超出阈值 且 在 1s 内通过的请求>=5, 两个条件同时满足后触发降级
- 熔断时间过后,关闭断路器,访问恢复正常。
6.3 熔断降级实例-异常比例
通过 Sentinel 实现 熔断降级控制
-
当调用 member-service-nacos-provider-10004 的 /t4 API 接口时,当资源的每秒请求 量>=5,并且每秒异常总数占通过量的比值超过 20%(即异常比例到 20%), 断路器打开(即: 进入降级状态), 让 /t4 API 接口 微服务不可用
-
当对/t4 API 接口 访问降到 1S 内 1 个请求,降低访问量了,断路器关闭,5 秒后微服务恢复。
-
修改 com/rainbowsea/springcloud/controller/MemberController.java 增加方法 t4()
配置实现步骤
- 为/t4 增加降级规则。
- 在流控规则菜单,可以看到新增的降级规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
Postman 测试:
- 先创建给 collection , 也可以在已经存在的 collection 进行修改, 一定确保更新成功.
2. 点击 Run sentinel
3. 浏览器访问: http://localhost:10004/t4
4. 停止 Postman
5. 浏览器访问: http://localhost:10004/t4 , 结果正常了(一次返回异常,一次返回正确结果)
注意事项和细节
- 当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值,资源进入降级状态, 需 要两个条件都满足
- 测试时,如果熔断降级和恢复服务两个状态切换不明显,将时间窗口值调整大一点比如 60, 就 OK 了
6.4 熔断降级实例-异常数
- 需求: 通过 Sentinel 实现 熔断降级控制
- 当调用 member-service-nacos-provider-10004 的 /t5 API 接口时,当资源的每分钟请 求量>=5,并且每分钟异常总数>=5 , 断路器打开(即: 进入降级状态), 让 /t5 API 接口 微服务不可用
- 当熔断时间(比如 20S)结束后,断路器关闭, 微服务恢复.
- 修改 com/rainbowsea/springcloud/controller/MemberController.java 增加方法 t5( )
配置实现步骤
- 为/t5 增加降级规则
- 在流控规则菜单,可以看到新增的降级规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: http://localhost:10004/t5
http://localhost:10004/t5 , 访问 5 次,出现 5 次异常(1 分钟内完成)
5 次异常后,出现熔断降级
20S 后,再次访问 http://localhost:10004/t5, 返回正常结果了
注意事项和细节
- 资源在 1 分钟的异常数目超过阈值之后会进行熔断降级
- 异常数统计是分钟级别的,若 设置的时间窗口 小于 60s,则结束熔断状态后仍可能再进入熔 断状态, 测试时,最好将时间窗口设置超过 60S
7. Sentinel 热点规则
一个问题引出热点 key 限流
- 热点: 热点即经常访问的数据。很多时候我们希望统计热点数据中, 访问频次最高的 Top K 数据,并对其访问进行限制。
- 比如某条新闻上热搜 ,在某段时间内高频访问, 为了防止系统雪崩, 可以对该条新 闻进行热点限流
- 文档地址:https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
- 热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含 热点参数的资源调用进行限流。
- 热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效
- Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数 级别的流控 https://blog.csdn.net/qq_34416331/article/details/106668747
- 热点参数限流支持集群模式
7.1 热点 Key 限流-实例
需求: 通过 Sentinel 实现 热点 Key 限流
-
对 member-service-nacos-provider-10004 的 /news?id=x&type=x API 接口进行热点限 流
-
假定 id=10 这一条新闻是当前的热点新闻, 当查询新闻时,对通常的 id(非热点新闻) 请求 QPS 限定为 2, 如果 id=10 QPS 限定为 100
-
如果访问超出了规定的 QPS, 触发热点限流机制, 调用自定义的方法,给出提示信息.
-
当对 /news?id=x&type=x API 接口 降低访问量,QPS 达到规定范围, 服务恢复
-
修 改 com/rainbowsea/springcloud/controller/MemberController.java 增 加 方 法 queryNews()
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard 10.6.4.3.3
启动 member-service-nacos-provider-10004
配置步骤:
- 为资源 news 增加热点规则, 注意不是 /news
- 在热点参数限流规则菜单,可以看到新增规则 。
浏览器: http://localhost:10004/news?id=1&type=教育
- 浏览器输入: http://localhost:10004/news?id=1&type=教育 , 如果 QPS 没有超过 2,则返回正确结果。
- 浏览器输入: http://localhost:10004/news?id=1&type=教育 , 如果 QPS 超过 2,则返回热点 key 处理信息
独立设置热点 id=10 的 QPS 阈值(即添加例外)
- 独立设置热点 id=10 的 QPS 阈值(即添加例外)
浏览器: http://localhost:10004/news?id=10&type=教育
- 浏览器输入: http://localhost:10004/news?id=10&type=教育 , 如 果 QPS 没有超过 100,则返回正确结果
- 浏览器访问的 id 不是 10 的,仍然遵守 QPS 不能超过 2 的热点限制
注意事项和细节
- 热点参数类型是(byte/int/long/float/double/char/String)
- 热点参数值,可以配置多个
- 热点规则只对指定的参数生效 (比如本实例对 id 生效, 对 type 不生效)
8. 系统规则
一个问题引出系统规则
- 如我们系统最大性能能抗 100QPS, 如何分配 /t1 /t2 的 QPS?
- 方案 1: /t1 分配 QPS=50 /t2 分配 QPS=50 , 问题, 如果/t1 当前 QPS 达到 50 , 而 /t2 的 QPS 才 10, 会造成没有充分利用服务器性能.
- 方案 2: /t1 分配 QPS=100 /t2 分配 QPS=100 , 问题, 容易造成 系统没有流量保护, 造成请求线程堆积,形成雪崩.
- 有没有对各个 资源请求的 QPS 弹性设置, 只要总数不超过系统最大 QPS 的流量保护规 则? ==> 系统规则
- 文档地址:
- 一句话: 系统规则作用, 在系统稳定的前提下,保持系统的吞吐量
图示解读:
- 系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水,当系统处理顺 畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;
- 反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间
系统规则:
- 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 达到阈值即触发系统保护。
需求: 通过 Sentinel 实现 系统规则-入口 QPS
-
对 member-service-nacos-provider-10004 的 所有 API 接口进行流量保护,不管访问 哪个 API 接口, 系统入口总的 QPS 不能大于 2, 大于 2,就进行限流控制
-
提示: 上面的 QPS 是老师为了方便看效果, 设置的很小
配置实现步骤:
- 增加入口 QPS 系统规则
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
- 浏览器输入: http://localhost:10004/t1 , 如果 QPS 超过 2, 打开断路器,返回流控信
息
- 浏览器输入: http://localhost:10004/news?id=1&type=教育 , 如果QPS 超过 2, 打开断路器,返回流控信息(说明: 老师项目的 /t2 资源对应方法有 休眠 代码,所以使用 /news?id=x&type=x 测试)
9. @SentinelResource 自定义全局限流处理类
@SentinelResource 自定义全局限流处理类
观察如下代码:
说明: 当配置的资源名 news 触发限流机制时,会调用 newsBlockHandler 方法
- 上面的处理方案存在一些问题
- 每个
@SentinelResource
对应一个异常处理方法,会造成方法很多- 异常处理方法和资源请求方法在一起,不利于业务逻辑的分离
- 解决方案-> 自定义全局限流处理类.
- 需求: 请编写一个自定义全局限流处理类,完成对异常处理.
创建 com/rainbowsea/springcloud/handler/CustomGlobalBlockHandler.java
javapackage com.rainbowsea.springcloud.handler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.rainbowsea.springcloud.entity.Result; /** * 1. CustomGlobalBlockHandler : 全局限流处理类 * 2. 在 CustomGlobalBlockHandler 类中,可以编写限流处理方法,但是要求方法是static */ public class CustomGlobalBlockHandler { public static Result handlerMethod1(BlockException blockException) { return Result.error("400", "客户自定义异常/限流处理方法handlerMethod1"); } public static Result handlerMethod2(BlockException blockException) { return Result.error("401", "客户自定义异常/限流处理方法handlerMethod2"); } }
修改 com/rainbowsea/springcloud/controller/MemberController.java 增加方法 t6()
javaprivate static int num = 0; // 执行的计数器-static静态 // 这里我们使用全局限流处理类,显示限流信息 /** * value="t6" 表示 sentinel 限流资源的名字 * blockHandlerClass = CustomGlobalBlockHandler.class:全局限流处理类 * blockHandler = "handlerMethod1" 指定使用全局限流处理类哪个方法,来处理限流信息 * fallbackClass = CustomGlobalFallbackHandler.class 全局 fallback处理类 * fallback = "fallbackHandlerMethod1" 指定使用全局fallback处理类哪个方法来处理java异常/业务异常 * exceptionsToIgnore = {NullPointerException.class} * * @return */ @GetMapping("/t6") @SentinelResource(value = "t6", // //设置处理sentinel 控制台违规后的异常 blockHand blockHandlerClass = CustomGlobalBlockHandler.class, blockHandler = "handlerMethod1", //设置处理Java异常的 fallback fallbackClass = CustomGlobalFallbackHandler.class, fallback = "fallbackHandlerMethod1", // 如果希望忽略某个异常,可以使用 exceptionsToIgnore,这里忽略NullPointerException异常 exceptionsToIgnore = {NullPointerException.class} ) public Result t6() { log.info("执行t6() 线程id={}", Thread.currentThread().getId()); // 假定;当访问t6资源次数是5倍数时,就出现Java异常 if (++num % 5 == 0) { throw new NullPointerException("null 指针异常 num=" + num); } if (++num % 6 == 0) { throw new RuntimeException("RuntimeException num=" + num); } return Result.success("200", "t6()执行OK~~~"); }
配置实现步骤
为资源 /t6 增加流控规则,方便测试
2. 在流控规则菜单,可以看到新增规则
测试启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: http://localhost:10004/t6
- 浏览器输入: http://localhost:10004/t6 , 如果 QPS 没有超过 1, 返回正常结果
2. 浏览器输入: http://localhost:10004/t6 , 如果 QPS 超过 1, 断路器打开,返回自定义限流处理方法信息。
9.1 @ fallback
看一段代码-引出 fallback
修 改 member-service-nacos-provider-10004 com/rainbowsea/springcloud/controller/MemberController.java 增加一段代码.
java
private static int num = 0; // 执行的计数器-static静态
// 这里我们使用全局限流处理类,显示限流信息
/**
* value="t6" 表示 sentinel 限流资源的名字
* blockHandlerClass = CustomGlobalBlockHandler.class:全局限流处理类
* blockHandler = "handlerMethod1" 指定使用全局限流处理类哪个方法,来处理限流信息
* fallbackClass = CustomGlobalFallbackHandler.class 全局 fallback处理类
* fallback = "fallbackHandlerMethod1" 指定使用全局fallback处理类哪个方法来处理java异常/业务异常
* exceptionsToIgnore = {NullPointerException.class}
*
* @return
*/
@GetMapping("/t6")
@SentinelResource(value = "t6",
// //设置处理sentinel 控制台违规后的异常 blockHand
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1",
//设置处理Java异常的 fallback
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallbackHandlerMethod1",
// 如果希望忽略某个异常,可以使用 exceptionsToIgnore,这里忽略NullPointerException异常
exceptionsToIgnore = {NullPointerException.class}
)
public Result t6() {
log.info("执行t6() 线程id={}", Thread.currentThread().getId());
// 假定;当访问t6资源次数是5倍数时,就出现Java异常
if (++num % 5 == 0) {
throw new NullPointerException("null 指针异常 num=" + num);
}
if (++num % 6 == 0) {
throw new RuntimeException("RuntimeException num=" + num);
}
return Result.success("200", "t6()执行OK~~~");
}
浏览器: http://localhost:10004/t6 , 看效果当 num 为 5 的整数时,返回的是 error 页面, 不友好.
怎么解决=> 使用 fallback
fallback 基本介绍
blockHandler 只负责 sentine 控制台配置违规
fallback 负责 Java 异常/业务异常
需求: 请编写一个自定义全局 fallback 处理类, 处理 java 异常/业务异常 。也就是解决前面我们提出的问题解
代码实现:
- 在 member-service-nacos-provider-10004 创建com/hspedu/springcloud/handler/CustomGlobalFallbackHandler.java
java
package com.rainbowsea.springcloud.handler;
import com.rainbowsea.springcloud.entity.Result;
/**
* CustomGlobalFallbackHandler :全局 fallback处理类
* 在 CustomGlobalFallbackHandler 类中,可以去编写处理Java异常/业务异常方法-static
*/
public class CustomGlobalFallbackHandler {
public static Result fallbackHandlerMethod1(Throwable throwable) {
return Result.error("402", "java异常信息 + " + throwable.getMessage());
}
public static Result fallbackHandlerMethod2(Throwable throwable) {
return Result.error("402", "java异常信息 + " + throwable.getMessage());
}
}
在 member-service-nacos-provider-10004 修改 com/hspedu/springcloud/controller/MemberController.java
java
// 这里我们使用全局限流处理类,显示限流信息
/**
* value="t6" 表示 sentinel 限流资源的名字
* blockHandlerClass = CustomGlobalBlockHandler.class:全局限流处理类
* blockHandler = "handlerMethod1" 指定使用全局限流处理类哪个方法,来处理限流信息
* fallbackClass = CustomGlobalFallbackHandler.class 全局 fallback处理类
* fallback = "fallbackHandlerMethod1" 指定使用全局fallback处理类哪个方法来处理java异常/业务异常
* exceptionsToIgnore = {NullPointerException.class}
*
* @return
*/
@GetMapping("/t6")
@SentinelResource(value = "t6",
// //设置处理sentinel 控制台违规后的异常 blockHand
blockHandlerClass = CustomGlobalBlockHandler.class,
blockHandler = "handlerMethod1",
//设置处理Java异常的 fallback
fallbackClass = CustomGlobalFallbackHandler.class,
fallback = "fallbackHandlerMethod1",
// 如果希望忽略某个异常,可以使用 exceptionsToIgnore,这里忽略NullPointerException异常
exceptionsToIgnore = {NullPointerException.class}
)
public Result t6() {
log.info("执行t6() 线程id={}", Thread.currentThread().getId());
// 假定;当访问t6资源次数是5倍数时,就出现Java异常
if (++num % 5 == 0) {
throw new NullPointerException("null 指针异常 num=" + num);
}
if (++num % 6 == 0) {
throw new RuntimeException("RuntimeException num=" + num);
}
return Result.success("200", "t6()执行OK~~~");
}
测试
启动 Nacos Server 8848
启动 Sentinel8080 控制台/Sentinel dashboard
启动 member-service-nacos-provider-10004
浏览器: http://localhost:10004/t6
浏览器输入: http://localhost:10004/t6 , 访问次数不是 5 的倍数, 返回正常结果
浏览器输入: http://localhost:10004/t6 , 访问次数是 5 的倍数 返回 fallback 指定方法,信息
为资源 /t6 增加流控规则,方便测试
在流控规则菜单,可以看到新增规则
浏览器输入: http://localhost:10004/t6 , 如果访问 QPS 大于 1 , 由 blockHandler 指 定的方法处理,访问次数是 5 的倍数, 由 fallback 指定方法处理, 其它情况返回正常的结果.
9.2 @exceptionsToIgnore 忽略
如果希望忽略某个异常,可以使用 exceptionsToIgnore
- 如果希望忽略某个异常(支持数组),可以使用 exceptionsToIgnore
- 浏览器输入: http://localhost:10004/t6 , 你会发现访问次数为 5 的倍数时,不再调用 fallback 指定方法处理
10. 补充: 接入 Sentinel 的方式
- 代码方式(硬编码,侵入性强, 不推荐)
文档地址: https://github.com/alibaba/Sentinel/wiki/介绍
基本使用:
- 注解方式(低侵入性, 前面用过, 推荐)
注 解 方 式 埋 点 不 支 持 private 方 法 https://github.com/alibaba/Sentinel/wiki/注解支持
11. 最后:
"在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。"