12.SpringCloudAlibabaSentinel实现熔断和限流
1.Sentinel
1.1 官网
sentinel官网,类似Spring Cloud Circuit Breaker
。
1.2 是什么
面向分布式、多语言异构化服务架构的流量治理组件。
1.3 下载地址
https://github.com/alibaba/Sentinel/releases
1.4 能干吗
Sentinel
以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
Sentinel的特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel的主要特性:
2.服务雪崩、降级、熔断、限流、隔离、超时区别
2.1 服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出
”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
2.2 服务降级
服务降级,说白了就是一种服务托底方案,如果服务无法完成正常的调用流程,就使用默认的托底方案来返回数据。
例如,在商品详情页一般都会展示商品的介绍信息,一旦商品详情页系统出现故障无法调用时,会直接获取缓存中的商品介绍信息返回给前端页面。
2.3 服务熔断
在分布式与微服务系统中,如果下游服务因为访问压力过大导致响应很慢或者一直调用失败时,上游服务为了保证系统的整体可用性,会暂时断开与下游服务的调用连接。这种方式就是熔断。类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务熔断一般情况下会有三种状态:闭合、开启和半熔断;
闭合状态(保险丝闭合通电OK):服务一切正常,没有故障时,上游服务调用下游服务时,不会有任何限制。
开启状态(保险丝断开通电Error):上游服务不再调用下游服务的接口,会直接返回上游服务中预定的方法。
半熔断状态:处于开启状态时,上游服务会根据一定的规则,尝试恢复对下游服务的调用。此时,上游服务会以有限的流量来调用下游服务,同时,会监控调用的成功率。如果成功率达到预期,则进入关闭状态。如果未达到预期,会重新进入开启状态。
2.4 服务限流
服务限流就是限制进入系统的流量,以防止进入系统的流量过大而压垮系统。其主要的作用就是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务和数据崩溃(如前端缓存大量实效),造成不可用;还可用于平滑请求,类似秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流(一般为1s),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。
2.5 服务隔离
有点类似于系统的垂直拆分,就按照一定的规则将系统划分成多个服务模块,并且每个服务模块之间是互相独立的,不会存在强依赖的关系。如果某个拆分后的服务发生故障后,能够将故障产生的影响限制在某个具体的服务内,不会向其他服务扩散,自然也就不会对整体服务产生致命的影响。
互联网行业常用的服务隔离方式有:线程池隔离和信号量隔离。
2.6 服务超时
整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。
形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。服务超时就是在上游服务调用下游服务时,设置一个最大响应时间,如果超过这个最大响应时间下游服务还未返回结果,则断开上游服务与下游服务之间的请求连接,释放资源。
3.安装Sentinel
3.1 Sentinel组件由2部分组成
Sentinel 分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
3.2 安装步骤
3.2.1 下载
略,见上文1.3的,下载地址。
3.2.2 运行命令
前提:JDK环境OK。8080端口不能占用。
命令:
java -jar sentinel-dashboard-1.8.6.jar
3.2.3 访问Sentinel管理界面
登录账号密码均为sentinel
4.微服务8401整合Sentinel入门案例
4.1 启动Nacos8848
启动命令
startup.cmd -m standalone
前台界面
4.2 启动Sentinel8080
java -jar sentinel-dashboard-1.8.6.jar
建议,启动命令,固定为start.bat文件。
4.3 新建微服务8401
4.3.1 新建Module
cloudalibaba-sentinel-service8401
4.3.2 POM
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>cloudalibaba-sentinel-service8401</artifactId>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud alibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--SpringBoot通用依赖模块-->
<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>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.3.3 YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service #8401微服务提供者后续将会被纳入阿里巴巴sentinel监管
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
4.3.4 主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class Main8401 {
public static void main(String[] args) {
SpringApplication.run(Main8401.class, args);
}
}
4.3.5 业务类
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @auther zzyy
* @create 2023-05-24 15:35
*/
@RestController
public class FlowLimitController{
@GetMapping("/testA")
public String testA(){
return "------testA";
}
@GetMapping("/testB")
public String testB(){
return "------testB";
}
}
4.4 启动8401微服务后查看Sentiel控制台
Sentinel采用的懒加载说明:想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使Sentinel检测出相应的接口。
执行一次访问,在Sentinel前台界面,看到。
5.流控规则
5.1 基本介绍
Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。参数见最下方:
名称 | 作用 |
---|---|
1资源名 | 资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。 |
2针对来源 | 具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。 |
3阈值类型 | QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。 |
4单机阈值 | 与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。 |
5是否集群 | 选中则表示集群环境,不选中则表示非集群环境。 |
5.2 流控模式
5.2.1 直接模式
默认的流控模式,当接口达到限流条件时,直接开启限流功能。
配置
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误。
测试
快速访问http://localhost:8401/testA,结果Blocked by Sentinel (flow limiting)
思考,直接调用默认报错信息,技术方面OK,但是是否该有我们的后续处理,类似fallback的兜底方法。
5.2.2 关联模式
当关联的资源达到阈值时,就限流自己。
配置A
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名,B惹事,A挂了。
使用Jmeter模拟并发密集访问testB
大批量的访问testB,在访问testA会发现A,访问出现Blocked by Sentinel (flow limiting)。
5.2.3 链路模式
来自不同链路的请求对同一目标访问时,实施针对性的不同限流措施,比如C请求来访问就限流,D请求来访问就是OK。
修改
YML
spring:
application:
name: cloudalibaba-sentinel-service #8401微服务提供者后续将会被纳入阿里巴巴sentinel监管
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路,新增
业务类
FlowLimitController类
/**
* 流控-链路演示demo
* C和D两个请求都访问flowLimitService.common()方法,阈值到达后对C限流,对D不管
*/
@Resource
private FlowLimitService flowLimitService;
/**流控-链路演示demo
* C和D两个请求都访问flowLimitService.common()方法,阈值到达后对C限流,对D不管
*/
@GetMapping("/testC")
public String testC() {
flowLimitService.common();
return "------testC";
}
@GetMapping("/testD")
public String testD() {
flowLimitService.common();
return "------testD";
}
FlowLimitService类
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
@Service
public class FlowLimitService {
@SentinelResource(value = "common") #该注解,后面介绍
public void common() {
System.out.println("------FlowLimitService come in");
}
}
Sentinel配置
说明:C和D两个请求都访问flowLimitService.common()方法,对C限流,对D不管。
测试
C链路,超过每秒1次,限流。D链路OK。
5.3 流控效果
5.3.1 直接
快速失败(默认的流控处理),直接失败,抛出异常,Blocked by Sentinel (flow limiting)。
5.3.2 预热Warm Up
限流 冷启动:当流量突然增大的时候,会希望系统从空闲状态到繁忙状态的切换的时间长一些。
公式:阈值除以冷却因子coldFactor(默认值3),经过预热时长后才会达到阈值。
官网:Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo。
源码:
public class WarmUpController implements TrafficShapingController {
protected double count;
private int coldFactor;
protected int warningToken = 0;
private int maxToken;
protected double slope;
protected AtomicLong storedTokens = new AtomicLong(0);
protected AtomicLong lastFilledTime = new AtomicLong(0);
public WarmUpController(double count, int warmUpPeriodInSec, int coldFactor) {
construct(count, warmUpPeriodInSec, coldFactor);
}
public WarmUpController(double count, int warmUpPeriodInSec) {
construct(count, warmUpPeriodInSec, 3);
}
...
}
WarmUp配置
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
案例,单机阈值为10,预热时长设置5秒。系统初始化的阈值为10 / 3 约等于3,即单机阈值刚开始为3(我们人工设定单机阈值是10,sentinel计算后QPS判定为3开始);然后过了5秒后阀值才慢慢升高恢复到设置的单机阈值10,也就是说5秒钟内QPS为3,过了保护期5秒后QPS为10
应用场景:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
5.3.3 排队等待
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
修改FlowLimitController
@GetMapping("/testE")
public String testE()
{
System.out.println(System.currentTimeMillis()+" testE,排队等待");
return "------testE";
}
按照单机阈值,一秒钟通过一个请求,10秒后的请求作为超时处理,放弃。
5.4 流控效果(并发线程数)
Jmeter模拟多个线程并发,循环请求。
Jmeter打满了,只有Jmeter线程切换,系统判断没访问时,才能访问到。
6.熔断规则
6.1 官网
https://github.com/alibaba/Sentinel/wiki/熔断降级
6.2 基本介绍
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
6.3 熔断规则实战
6.3.1 慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
6.3.2 名词解释
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数且实际慢调用比例>比例阈值 ,进入熔断状态。
1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。
2.最大RT(Response Time):即最大的响应时间,指系统对请求作出响应的业务处理时间。
3.慢调用:处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用。
4.慢调用比例:在所以调用中,慢调用占有实际的比例=慢调用次数/总调用次数。
5.比例阈值:自己设定的 , 比例阈值=慢调用次数/调用次数。
6.统计时长:时间的判断依据。
7.最小请求数:设置的调用最小请求数,上图比如1秒钟打进来10个线程(大于我们配置的5个了)调用被触发。
6.3.3 触发条件 + 熔断状态
熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断。
探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态。
结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。
6.3.4 测试
/**
* 新增熔断规则-慢调用比例
* @return
*/
@GetMapping("/testF")
public String testF()
{
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("----测试:新增熔断规则-慢调用比例 ");
return "------testF 新增熔断规则-慢调用比例";
}
按照上述配置,熔断触发:
多次循环,一秒钟打进来10个线程(大于5个了)调用/testF,我们希望200毫秒处理完一次调用,和谐系统;
假如在统计时长内,实际请求数目>最小请求数且慢调用比例>比例阈值 ,断路器打开(保险丝跳闸)微服务不可用(Blocked by Sentinel (flow limiting)),进入熔断状态5秒;后续我停止jmeter,没有这么大的访问量了,单独用浏览器访问rest地址,断路器关闭(保险丝恢复,合上闸口),微服务恢复OK。
6.3.5 异常比例
异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%
6.3.6 测试
/**
* 新增熔断规则-异常比例
* @return
*/
@GetMapping("/testG")
public String testG()
{
System.out.println("----测试:新增熔断规则-异常比例 ");
int age = 10/0;
return "------testG,新增熔断规则-异常比例 ";
}
按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0)达到100%,调一次错一次报错error;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务熔断+服务降级,出提示Blocked by Sentinel (flow limiting)。
6.3.7 异常数
异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
6.3.7 测试
/**
* 新增熔断规则-异常数
* @return
*/
@GetMapping("/testH")
public String testH()
{
System.out.println("----测试:新增熔断规则-异常数 ");
int age = 10/0;
return "------testH,新增熔断规则-异常数 ";
}
http://localhost:8401/testH
,第一次访问绝对报错,因为除数不能为零,我们看到error窗口;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
上述配置表示,在1秒钟内最少请求2次,当异常数大于1时,会触发熔断操作断路器开启(保险丝跳闸),微服务不可用了,熔断的时长为5秒,不再报错error,而是服务降级了出提示Blocked by Sentinel (flow limiting)
7.@SentinelResource
7.1 是什么
SentinelResource是一个流量防卫组件的注解,用于标识防护资源,方便对资源进行流量控制,熔断降级等功能。
7.2 注解说明
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
//资源名称
String value() default "";
//entry类型,标记流量的方向,取值IN/OUT,默认是OUT
EntryType entryType() default EntryType.OUT;
//资源分类
int resourceType() default 0;
/**
* 处理BlockException的函数名称,函数要求:
* 1. 必须是 public
* 2.返回类型 参数与原方法一致
* 3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置blockHandlerClass ,并指定blockHandlerClass里面的方法。
*/
String blockHandler() default "";
//存放blockHandler的类,对应的处理函数必须static修饰。
Class<?>[] blockHandlerClass() default {};
/**
* 用于在抛出异常的时候提供fallback处理逻辑。 fallback函数可以针对所
* 有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
* 1. 返回类型与原方法一致
* 2. 参数类型需要和原方法相匹配
* 3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。
*/
String fallback() default "";
//存放fallback的类。对应的处理函数必须static修饰。
String defaultFallback() default "";
/**
* 用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
* 行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
* 1. 返回类型与原方法一致
* 2. 方法参数列表为空,或者有一个 Throwable 类型的参数。
* 3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。
*/
Class<?>[] fallbackClass() default {};
//需要trace的异常
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
//指定排除忽略掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
7.3 按照Rest地址限流 + 默认限流返回
7.3.1 业务类
RateLimitController
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @auther zzyy
* @create 2023-05-30 16:13
*/
@RestController
@Slf4j
public class RateLimitController
{
@GetMapping("/rateLimit/byUrl")
public String byUrl()
{
return "按rest地址限流测试OK";
}
}
7.3.2 配置
7.3.3 测试
快速点击http://localhost:8401/rateLimit/byUrl。
结果,返回Sentinel自带的,限流处理结果Blocked by Sentinel (flow limiting)。
7.4 按SentinelResource资源名称限流 + 自定义限流返回
7.4.1 RateLimitController修改
@GetMapping("/rateLimit/byResource")
@SentinelResource(value = "byResourceSentinelResource",blockHandler = "handleException")
public String byResource()
{
return "按资源名称SentinelResource限流测试OK";
}
public String handleException(BlockException exception)
{
return "服务不可用@SentinelResource启动"+"\t"+"o(╥﹏╥)o";
}
7.4.2 配置
7.4.3 测试
超过阈值,返回自定义限流信息。服务不可用@SentinelResource启动 o(╥﹏╥)o
7.5 按SentinelResource资源名称限流 + 自定义限流返回 + 服务降级处理
7.5.1 RateLimitController修改
@GetMapping("/rateLimit/doAction/{p1}")
@SentinelResource(value = "doActionSentinelResource",
blockHandler = "doActionBlockHandler", fallback = "doActionFallback")
public String doAction(@PathVariable("p1") Integer p1) {
if (p1 == 0){
throw new RuntimeException("p1等于零直接异常");
}
return "doAction";
}
public String doActionBlockHandler(@PathVariable("p1") Integer p1,BlockException e){
log.error("sentinel配置自定义限流了:{}", e);
return "sentinel配置自定义限流了";
}
public String doActionFallback(@PathVariable("p1") Integer p1,Throwable e){
log.error("程序逻辑异常了:{}", e);
return "程序逻辑异常了"+"\t"+e.getMessage();
}
7.5.2 配置
7.5.3 测试
http://localhost:8401/rateLimit/doAction/2
,达到阈值,出现sentinel配置自定义限流了。http://localhost:8401/rateLimit/doAction/0,返回自定义的服务降级处理。
7.6 小结
blockHandler:主要针对配置后出现的违规情况处理。
fallback:业务中出现的异常。
8.热点规则
8.1 介绍
何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作。
8.2 官网
https://github.com/alibaba/Sentinel/wiki/热点参数限流
8.3 业务类修改
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "dealHandler_testHotKey")
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "------testHotKey";
}
public String dealHandler_testHotKey(String p1, String p2, BlockException exception) {
return "-----dealHandler_testHotKey";
}
8.4 配置
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。
8.5 测试
http://localhost:8401/testHotKey?p1=1
http://localhost:8401/testHotKey?p1=1&p2=2
http://localhost:8401/testHotKey?p2=2。
含有参数,p1,会触发配置的限流。不含有参数p1,不会触发限流操作配置。
8.6 参数例外项
配置的热点规则,想配置,某些参数,有额外的阈值。
8.7 测试
http://localhost:8401/testHotKey?p1=5
快速刷新,无限流提醒。
http://localhost:8401/testHotKey?p1=1
快速刷新,有限流提醒。
前提条件,热点参数的类型,只能时有限的基本类型(int、double、long、float、char、byte)和String
。
9.授权规则
9.1 介绍
在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。白放行、黑禁止。
9.2 官网
https://github.com/alibaba/Sentinel/wiki/黑白名单控制
9.3 代码修改
package com.atguigu.cloud.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class EmpowerController //Empower授权规则,用来处理请求的来源
{
@GetMapping(value = "/empower")
public String requestSentinel4() {
log.info("测试Sentinel授权规则empower");
return "Sentinel授权规则";
}
}
package com.atguigu.cloud.handler;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("serverName");
}
}
重启,访问http://localhost:8401/empower
9.4 配置
9.5 测试
http://localhost:8401/empower?serverName=test
http://localhost:8401/empower?serverName=test2
无法访问,被Sentienl限流。
http://localhost:8401/empower?serverName=test1
正常访问,不被Sentienl限流。
10.规则持久化
10.1 是什么
现在,在Sentinel中配置的Sentinel规则,一旦重启微服务应用,规则会消失,需要将配置规则持久化。
10.2 怎么玩
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,对应的流控规则持续有效。
10.3 步骤
10.3.1 POM
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
10.3.2 YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service #8401微服务提供者后续将会被纳入阿里巴巴sentinel监管
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType
rule-type: flow
备注:rule-type是什么?看看源码
com.alibaba.cloud.sentinel.datasource.RuleType
/**
* flow.
*/
FLOW("flow", FlowRule.class),//流量
/**
* degrade.
*/
DEGRADE("degrade", DegradeRule.class),//熔断
/**
* param flow.
*/
PARAM_FLOW("param-flow", ParamFlowRule.class),//访问控制
/**
* system.
*/
SYSTEM("system", SystemRule.class),//系统保护
/**
* authority.
*/
AUTHORITY("authority", AuthorityRule.class),//热点
10.3.3 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,调用http://localhost:8401/rateLimit/byUrl,还是有规则。
11.OpenFeign和Sentinel集成实现fallback降级
11.1 需求说明
cloudalibaba-consumer-nacos-order83 通过OpenFeign调用 cloudalibaba-provider-payment9001
- 83 通过OpenFeign调用 9001微服务,正常访问OK
- 83 通过OpenFeign调用 9001微服务,异常访问error
- 访问者要有fallback服务降级的情况,不要持续访问9001加大微服务负担,但是通过feign接口调用的又方法各自不同,如果每个不同方法都加一个fallback配对方法,会导致代码膨胀不好管理,工程埋雷....../(ㄒoㄒ)/~~
- 3 public @interface FeignClient
- 通过fallback属性进行统一配置,feign接口里面定义的全部方法都走统一的服务降级,一个搞定即可。
- 4 9001微服务自身还带着sentinel内部配置的流控规则,如果满足也会被触发,也即本例有2个Case
- 4.1 OpenFeign接口的统一fallback服务降级处理
- 4.2 Sentinel访问触发了自定义的限流配置,在注解@SentinelResource里面配置的blockHandler方法。
11.2 编码步骤
启动Nacos和Sentinel。
11.3 修改服务提供方
cloudalibaba-provider-payment9001
11.3.1 POM
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
11.3.2 YML
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
11.3.3 主启动
无改变。
11.3.4 业务类
PayAlibabaController
@GetMapping("/pay/nacos/get/{orderNo}")
@SentinelResource(value = "getPayByOrderNo",blockHandler = "handlerBlockHandler")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo)
{
//模拟从数据库查询出数据并赋值给DTO
PayDTO payDTO = new PayDTO();
payDTO.setId(1024);
payDTO.setOrderNo(orderNo);
payDTO.setAmount(BigDecimal.valueOf(9.9));
payDTO.setPayNo("pay:"+ IdUtil.fastUUID());
payDTO.setUserId(1);
return ResultData.success("查询返回值:"+payDTO);
}
public ResultData handlerBlockHandler(@PathVariable("orderNo") String orderNo, BlockException exception)
{
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"getPayByOrderNo服务不可用," +
"触发sentinel流控配置规则"+"\t"+"o(╥﹏╥)o");
}
/*
fallback服务降级方法纳入到Feign接口统一处理,全局一个
public ResultData myFallBack(@PathVariable("orderNo") String orderNo,Throwable throwable)
{
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"异常情况:"+throwable.getMessage());
}
*/
启动9001,自测,略。
11.4 修改cloud-api-commons
11.4.1 POM
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
11.4.2 新增PayFeignSentinelApi接口
package com.atguigu.cloud.apis;
import com.atguigu.cloud.resp.ResultData;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "nacos-payment-provider", fallback = PayFeignSentinelApiFallBack.class)
public interface PayFeignSentinelApi {
@GetMapping("/pay/nacos/get/{orderNo}")
ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo);
}
11.4.3 为远程调用创建全局统一服务降级类
package com.atguigu.cloud.apis;
import com.atguigu.cloud.resp.ResultData;
import com.atguigu.cloud.resp.ReturnCodeEnum;
import org.springframework.stereotype.Component;
@Component
public class PayFeignSentinelApiFallBack implements PayFeignSentinelApi{
@Override
public ResultData getPayByOrderNo(String orderNo) {
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o");
}
}
11.5 修改服务调用方
11.5.1 POM
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.atguigu.cloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
11.5.2 YML
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(nacos微服务提供者叫什么你写什么)
service-url:
nacos-user-service: http://nacos-payment-provider
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
11.5.3 主启动
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class Main83 {
public static void main(String[] args) {
SpringApplication.run(Main83.class, args);
}
}
11.5.4 业务类
@Resource
private PayFeignSentinelApi payFeignSentinelApi;
@GetMapping(value = "/consumer/pay/nacos/get/{orderNo}")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo)
{
return payFeignSentinelApi.getPayByOrderNo(orderNo);
}
11.5.5 启动
报错:
2024-11-28T22:28:14.719+08:00 ERROR 15656 --- [nacos-order-consumer] [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'orderNacosController': Injection of resource dependencies failed
原因:SpringBoot + SpringCloud版本和Sentinel不兼容。
解决方案:
父工程的POM修改
<!-- <spring.boot.version>3.2.0</spring.boot.version>-->
<!-- <spring.cloud.version>2023.0.0</spring.cloud.version>-->
<spring.boot.version>3.0.9</spring.boot.version>
<spring.cloud.version>2022.0.2</spring.cloud.version>
再次启动成功。
11.6 测试
9001正常启动、http://localhost:9001/pay/nacos/get/100
测试通过。
83正常启动,http://localhost:83/consumer/pay/nacos/get/1024
,测试通过。
配置
http://localhost:83/consumer/pay/nacos/get/1024
频繁访问触发流控规则,{"code":"500","message":"getPayByOrderNo服务不可用,触发sentinel流控配置规则\to(╥﹏╥)o","data":null,"timestamp":1732804856992}。
关闭9001,测试服务降级效果。{"code":"500","message":"对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o","data":null,"timestamp":1732804916203}
最后一步,恢复父工程版本号。
12.GateWay和Sentinel集成实现服务限流
12.1 需求说明
cloudalibaba-sentinel-gateway9528 保护 cloudalibaba-provider-payment9001
12.2 步骤
12.2.1 建Moudle
cloudalibaba-sentinel-gateway9528
12.2.2 改POM
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
12.2.3 写YML
server:
port: 9528
spring:
application:
name: cloudalibaba-sentinel-gateway # sentinel+gataway整合Case
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
routes:
- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:9001 #匹配后提供服务的路由地址
predicates:
- Path=/pay/** # 断言,路径相匹配的进行路由
12.2.4 主启动
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class Main9528 {
public static void main(String[] args) {
SpringApplication.run(Main9528.class, args);
}
}
12.2.5 业务类
参考官网配置案例改写
https://github.com/alibaba/Sentinel/wiki/网关限流
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule;
import com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayRuleManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.SentinelGatewayFilter;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.adapter.gateway.sc.exception.SentinelGatewayBlockExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.*;
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct //javax.annotation.PostConstruct
public void doInit() {
initBlockHandler();
}
//处理/自定义返回的例外信息
private void initBlockHandler() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("pay_routh1").setCount(2).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
BlockRequestHandler handler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
Map<String,String> map = new HashMap<>();
map.put("errorCode", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());
map.put("errorMessage", "请求太过频繁,系统忙不过来,触发限流(sentinel+gataway整合Case)");
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(handler);
}
}
12.3 测试
9001访问:http://localhost:9001/pay/nacos/get/100
,加快点击频率,不会出现限流容错。
加网关访问:http://localhost:9528/pay/nacos/get/100
,加快点击频率,会出现限流容错。{"errorMessage":"请求太过频繁,系统忙不过来,触发限流(sentinel+gataway整合Case)","errorCode":"Too Many Requests"}
只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。