注:本篇文章主要参考周阳老师讲解的cloud进行整理的!
1、Sentinel
1.1、官网
https://sentinelguard.io/zh-cn/
等价对标 Spring Cloud Circuit Breaker
1.2、是什么
https://github.com/alibaba/Sentinel/wiki
1.3、去哪下
https://github.com/alibaba/Sentinel/releases
1.4、能干嘛
从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性
1.5、怎么玩(面试题)
- 讲讲什么是缓存穿透?击穿?雪崩?如何解决?
https://www.bilibili.com/video/BV13R4y1v7sP/?p=123
1.5.1、服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
1.5.2、服务降级
服务降级,说白了就是一种服务托底方案,如果服务无法完成正常的调用流程,就使用默认的托底方案来返回数据。
例如,在商品详情页一般都会展示商品的介绍信息,一旦商品详情页系统出现故障无法调用时,会直接获取缓存中的商品介绍信息返回给前端页面。
1.5.3、服务熔断
在分布式与微服务系统中,如果下游服务因为访问压力过大导致响应很慢或者一直调用失败时,上游服务为了保证系统的整体可用性,会暂时断开与下游服务的调用连接。这种方式就是熔断。类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
服务熔断一般情况下会有三种状态:闭合、开启和半熔断;闭合状态(保险丝闭合通电OK):服务一切正常,没有故障时,上游服务调用下游服务时,不会有任何限制。
开启状态(保险丝断开通电Error):上游服务不再调用下游服务的接口,会直接返回上游服务中预定的方法。
半熔断状态:处于开启状态时,上游服务会根据一定的规则,尝试恢复对下游服务的调用。此时,上游服务会以有限的流量来调用下游服务,同时,会监控调用的成功率。如果成功率达到预期,则进入关闭状态。如果未达到预期,会重新进入开启状态。
1.5.4、服务限流
服务限流就是限制进入系统的流量,以防止进入系统的流量过大而压垮系统。其主要的作用就是保护服务节点或者集群后面的数据节点,防止瞬时流量过大使服务和数据崩溃(如前端缓存大量实效),造成不可用;还可用于平滑请求,类似秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
限流算法有两种,一种就是简单的请求总量计数,一种就是时间窗口限流(一般为1s),如令牌桶算法和漏牌桶算法就是时间窗口的限流算法。
- 服务隔离
有点类似于系统的垂直拆分,就按照一定的规则将系统划分成多个服务模块,并且每个服务模块之间是互相独立的,不会存在强依赖的关系。如果某个拆分后的服务发生故障后,能够将故障产生的影响限制在某个具体的服务内,不会向其他服务扩散,自然也就不会对整体服务产生致命的影响。
互联网行业常用的服务隔离方式有:线程池隔离和信号量隔离。
1.5.5、服务超时
整个系统采用分布式和微服务架构后,系统被拆分成一个个小服务,就会存在服务与服务之间互相调用的现象,从而形成一个个调用链。
形成调用链关系的两个服务中,主动调用其他服务接口的服务处于调用链的上游,提供接口供其他服务调用的服务处于调用链的下游。服务超时就是在上游服务调用下游服务时,设置一个最大响应时间,如果超过这个最大响应时间下游服务还未返回结果,则断开上游服务与下游服务之间的请求连接,释放资源。
2、安装Sentinel
2.1、sentinel组件由2部分构成
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
- 后台8719默认
- 前台8080开启
2.2、安装步骤
2.2.1、下载
https://github.com/alibaba/Sentinel/releases
下载到本地sentinel-dashboard-1.8.6.jar
2.2.2、运行命令
-
前提
- Java环境OK
- 8080端口不能被占用
-
命令
- java -jar sentinel-dashboard-1.8.6.jar
2.2.3、访问sentinel管理界面
- 登录账号密码均是sentinel
- http://localhost:8080
3、微服务8401整合Sentinel入门案例
3.1、启动Nacos8848成功
startup.cmd -m standalone
http://localhost:8848/nacos
3.2、启动Sentinel8080成功
java -jar sentinel-dashboard-1.8.6.jar
3.3、新建微服务8401
3.3.1、新建cloudalibaba-sentinel-service8401
- 将被哨兵纳入管控的8401微服务提供者
3.3.2、POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mui.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.mui.cloud</groupId>
<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 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.mui.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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.3.3、YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848 # Nacos服务注册中心地址
sentinel:
transport:
dashboard: localhost:8080 # 配置Sentinel dashboard控制台服务地址
port: 8719 # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
3.3.4、主启动
package com.mui.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class Main8401 {
public static void main(String[] args) {
SpringApplication.run(Main8401.class, args);
}
}
3.3.5、业务类FlowLimitController
package com.mui.cloud.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
}
- 启动微服务8401并访问
3.4、启动8401微服务后查看sentinel控制台
- 空空如也,啥都没有
3.4.1、sentinel采用的懒加载说明
- 注意
想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使Sentinel监测出响应的接口
-
执行一次访问即可
- http://localhost:8401/testA
- http://localhost:8401/testB
-
效果
4、流控规则
4.1、基本介绍
- 概述
Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。参数见最下方:
1资源名 | 资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。 |
2针对来源 | 具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。 |
3阈值类型 | QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。 |
4单机阈值 | 与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。 |
5是否集群 | 选中则表示集群环境,不选中则表示非集群环境。 |
4.2、 流控模式
4.2.1、 直接
- 默认的流控模式,当接口到限流条件时,直接开启限流功能
- 配置及说明
- 表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
- 测试
- 快速点击访问http://localhost:8401/testA
- 结果
- 思考
- 直接调用默认报错信息,技术方面OK,但是,是否应该由我们自己的后续处理?
- 类似有个fallback的兜底方法
4.2.2、关联
4.2.2.1、是什么
- 当管理的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阈值后,就限流A自己
- B惹事,A挂了
- 张三感冒,李四吃药
4.2.2.2、配置A
- 当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名,B惹事,A挂了
4.2.2.3、Jmeter模拟并发密集访问testB
-
https://jmeter.apache.org/download_jmeter.cgi(超级慢)
- Apache JMeter 5.6.3 (Requires Java 8+)
-
https://mirrors.cloud.tencent.com/apache/jmeter/binaries/ (下载速度快)
-
访问testB成功
- http://localhost:8401/testB
-
Run
-
大批量线程高并发访问B,导致A失效了
4.2.2.4、运行后发现testA挂了
- 点击访问http://localhost:8401/testA
- 结果
4.2.3、链路
- 来自不通链路的请求对同一个目标访问时,实施针对性的不通限流措施
- 比如C请求来访问就限流,D请求来访问就是OK
4.2.3.1、修改微服务cloudalibaba-sentinel-service8401
- YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
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层调用不认为是同一个根链路
- 业务类
- 新建FlowLimitService
package com.mui.cloud.service;
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");
}
}
- 修改FlowLimitController
package com.mui.cloud.controller;
import com.mui.cloud.service.FlowLimitService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
System.out.println("aaa");
return "------testA";
}
@GetMapping("/testB")
public String testB() {
System.out.println("bbb");
return "------testB";
}
/**
* 流控-链路演示demo
* C和D两个请求都访问flowLimitService.common()方法,阈值到达后对C限流,对D不管
*/
@Resource
private FlowLimitService flowLimitService;
@GetMapping("/testC")
public String testC() {
flowLimitService.common();
return "------testC";
}
@GetMapping("/testD")
public String testD() {
flowLimitService.common();
return "------testD";
}
}
4.2.3.2、sentinel配置
说明:C和D两个请求都访问flowLimitService.common()方法,对C限流,对D不管
4.2.3.3、测试
- http://localhost:8401/testC
- 超过一秒钟一次后,就发生限流
- D链路OK
4.3、流控效果
4.3.1、直接
- 快速失败(默认的流控处理)
- 直接失败,抛出异常
- Blocked by Sentinel(flow limiting)
- 直接失败,抛出异常
4.3.2、预热Warm UP
4.3.2.1、 限流-冷启动
4.3.2.2、说明
公式: 阈值除以冷却银子coldFactor(默认值为3),经过预热时长后才会达到阈值
4.3.2.3、官网
- 源码
- package com.alibaba.csp.sentinel.slots.block.flow.controller;
4.3.2.4、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
- 多次点击http://localhost:8401/testB
- 刚开始不行,后续慢慢OK
4.3.2.5、应用场景
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
4.3.3、排队等待
4.3.3.1、是什么
4.3.3.2、修改FlowLimitController
@GetMapping("/testE")
public String testE() {
System.out.println(System.currentTimeMillis() + " testE,排队等待");
return "------testE";
}
- http://localhost:8401/testE
4.3.3.3、sentinel配置
按照单机阈值,一秒钟通过一个请求,10秒后的请求作为超时处理,放弃
4.4、流控效果V2(并发线程数)
4.4.1、sentinel配置
4.4.2、Jmeter模拟多个线程并发 + 循环请求
- http://localhost:8401/testB
- Jmeter给它打满了,大部分我们自己访问都不好使,偶尔Jmeter线程切换系统判定没访问,我们自己的点击才有机会
5、熔断规则
5.1、官网
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
- 基本介绍
- Sentinel主要提供了三个熔断策略
- Sentinel主要提供了三个熔断策略
5.2、熔断规则实战
5.2.1、慢调用比例
5.2.1.1、是什么
5.2.1.2、名词解释
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。
2.最大RT:即最大的响应时间,指系统对请求作出响应的业务处理时间。
3.慢调用:处理业务逻辑的实际时间 > 设置的最大RT时间,这个调用叫做慢调用。
4.慢调用比例:在所以调用中,慢调用占有实际的比例=慢调用次数➗总调用次数
5.比例阈值:自己设定的 , 比例阈值=慢调用次数➗调用次数
6.统计时长:时间的判断依据
7.最小请求数:设置的调用最小请求数,上图比如1秒钟打进来10个线程(大于我们配置的5个了)调用被触发
5.2.1.3、触发条件 + 熔断状态
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
1.熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断
2.探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态
3.结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。
5.2.1.4、测试
- 代码
/**
* 新增熔断规则-慢调用比例
*
* @return
*/
@GetMapping("/testF")
public String testF() {
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----测试:新增熔断规则-慢调用比例 ");
return "------testF 新增熔断规则-慢调用比例";
}
-
配置
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
-
jmeter压测
-
结论
按照上述配置,熔断触发:
多次循环,一秒钟打进来10个线程(大于5个了)调用/testF,我们希望200毫秒处理完一次调用,和谐系统;_
假如在统计时长内,实际请求数目>最小请求数且慢调用比例>比例阈值 ,断路器打开(保险丝跳闸)微服务不可用(Blocked by Sentinel (flow limiting)),进入熔断状态5秒;后续我停止jmeter,没有这么大的访问量了,单独用浏览器访问rest地址,断路器关闭(保险丝恢复,合上闸口),微服务恢复OK
5.2.2、异常比例
5.2.2.1、是什么
5.2.2.2、测试
- 代码
/**
* 新增熔断规则-异常比例
*
* @return
*/
@GetMapping("/testG")
public String testG() {
System.out.println("----测试:新增熔断规则-异常比例 ");
int age = 10 / 0;
return "------testG,新增熔断规则-异常比例 ";
}
- 配置
不配置Sentinel,对于int age=10/0,调一次错一次报错error,页面报【Whitelabel Error Page】或全局异常
配置Sentinel,对于int age=10/0,如符合如下异常比例启动熔断,页面报【Blocked by Sentinel (flow limiting)】
- jemeter
- 结论
按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0)达到100%,调一次错一次报错error;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务熔断+服务降级,出提示
Blocked by Sentinel (flow limiting)。
5.2.3、异常数
5.2.3.1、是什么
5.2.3.2、测试
- 代码
/**
* 新增熔断规则-异常数
*
* @return
*/
@GetMapping("/testH")
public String testH() {
System.out.println("----测试:新增熔断规则-异常数 ");
int age = 10 / 0;
return "------testH,新增熔断规则-异常数 ";
}
-
配置
-
jemeter
-
结论
http://localhost:8401/testH,第一次访问绝对报错,因为除数不能为零,我们看到error窗口;
开启jmeter后,直接高并发干爆他发送请求,多次调用达到我们的配置条件了。
但是jmeter开工,上述配置表示,在1秒钟内最少请求2次,当异常数大于1时,会触发熔断操作断路器开启(保险丝跳闸),微服务不可用了,熔断的时长为5秒,不再报错error而是服务降级了出提示Blocked by Sentinel (flow limiting)
6、@SentinelResource注解
6.1、是什么
SentinelResource是一个流量防卫防护组件注解,用户指定防护资源,对配置的资源进行流量控制、熔断降级等功能。
- @SentinelResource注解说明
@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 {};
}
6.2、启动Nacos
startup.cmd -m standalone
6.3、启动Sentinel
java -jar sentinel-dashboard-1.8.6.jar
6.4、按照rest地址限流 + 默认限流返回
- 通过访问的rest地址来限流,会返回Sentinel自带默认的限流处理信息
- 业务类RateLimitController
package com.mui.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 RateLimitController {
@GetMapping("/rateLimit/byUrl")
public String byUrl() {
return "按rest地址限流测试OK";
}
}
-
访问一次
- http://localhost:8401/rateLimit/byUrl
-
Sentinel控制台配置
- 测试
- 疯狂点击http://localhost:8401/rateLimit/byUrl
- 结果
- 会返回Sentinel自带的限流处理结果,默认
6.5、按SentinelResource资源名称限流 + 自定义限流返回
-
不想用默认的限流提示(Blocked by Sentinel (flow limiting)),想返回自定义限流的提示
-
微服务cloudalibaba-sentinel-service8401
- 业务类RateLimitController
package com.mui.cloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class RateLimitController {
@GetMapping("/rateLimit/byUrl")
public String byUrl() {
return "按rest地址限流测试OK";
}
@GetMapping("/rateLimit/byResource")
@SentinelResource(value = "byResourceSentinelResource", blockHandler = "handleException")
public String byResource() {
return "按资源名称SentinelResource限流测试OK";
}
public String handleException(BlockException exception) {
return "服务不可用@SentinelResource启动" + "\t" + "o(╥﹏╥)o";
}
}
-
测试地址
- http://localhost:8401/rateLimit/byResource
-
配置流控规则
-
配置步骤
-
图像配置和代码关系
-
-
测试
- 1秒钟点击1下,OK
- 超过上述,疯狂点击,返回了自定义的限流处理信息,限流发生
-
sentinel默认
-
自定义限流提示
-
6.6、按SentinelResource资源名称限流 + 自定义限流返回 + 服务降级处理
- 按SentinelResource配置,点击超过限流配置返回自定义限流提示 + 程序异常返回fallback服务降级
- 微服务cloudalibaba-sentinel-service8401
- 业务类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();
}
-
http://localhost:8401/rateLimit/doAction/2
-
配置流控规则
-
图像配置和代码关系
-
表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流
-
-
测试
-
http://localhost:8401/rateLimit/doAction/2
-
1秒钟点击1下,OK
-
超过上述,疯狂点击,返回了自定义的限流处理信息,限流发生,配合了sentinel设定的规则
-
http://localhost:8401/rateLimit/doAction/0
-
p1参数为零,异常发生,返回了自定义的服务降级处理
-
小结
- blockHandler,主要针对sentinel配置后出现的违规情况处理
- fallback,程序异常JVM抛出的异常服务降级
- 两者可以同时共存
-
7、热点规则
7.1、基本介绍
是什么?何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
7.2、官网
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
7.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";
}
7.4、配置
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。
- 方法testHotKey里面第一个参数P1只要QPS超过每秒1次,马上降级处理
- http://localhost:8401/testHotKey
7.5、测试
-
error
-
http://localhost:8401/testHotKey?p1=abc
-
含有参数P1,当每秒访问的频率超过1次时,会触发Sentinel的限流操作
-
-
error
-
http://localhost:8401/testHotKey?p1=abc&p2=11
-
含有参数P1,当每秒访问的频率超过1次时,会触发Sentinel的限流操作
-
-
right
- http://localhost:8401/testHotKey?p2=abc
- 没有热点参数P1,不断访问则不会触发限流操作
- http://localhost:8401/testHotKey?p2=abc
7.6、参数例外项
-
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
-
特例情况
- 普通正常限流
- 含有P1参数,超过1秒钟一个后,达到阈值1后马上被限流
- 例外特殊限流
- 我们期望p1参数当它时某个特殊值时,到达某个约定值后【普通正常限流】规则突然例外、失效了,它的限流值和平时不一样
- 例如当p1的值等于5时,它的阈值可以达到200或其他值
- 普通正常限流
-
配置
- 添加按钮不能忘记了
-
测试
- http://localhost:8401/testHotKey?p1=5
- 超过1秒钟一个后,达到阈值200后才会被限流
- 当p1等于5的时候,阈值变为200
- http://localhost:8401/testHotKey?p1=3
- 超过1秒钟一个后,达到阈值后马上被限流
- 当p1不等于5的时候,阈值就是平常的【普通正常限流】规则
- http://localhost:8401/testHotKey?p1=5
-
前提条件
- 热点参数的注意点,参数必须是基本类型或者String
8、授权规则
8.1、授权规则概述
在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。白放行、黑禁止
8.2、官网
https://github.com/alibaba/Sentinel/wiki/%E9%BB%91%E7%99%BD%E5%90%8D%E5%8D%95%E6%8E%A7%E5%88%B6
8.3、演示授权规则,黑名单禁止
- 代码
- EmpowerController
package com.mui.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 {
@GetMapping(value = "/empower")
public String requestSentinel4(){
log.info("测试Sentinel授权规则empower");
return "Sentinel授权规则";
}
}
- MyRequestOriginParser
package com.mui.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");
}
}
- 启动8401后先访问成功
- http://localhost:8401/empower
8.4、配置
8.5、测试
-
error
- http://localhost:8401/empower?serverName=test
- http://localhost:8401/empower?serverName=test
-
error
- http://localhost:8401/empower?serverName=test2
- http://localhost:8401/empower?serverName=test2
-
说明
- 不断在浏览器中刷新http://localhost:8401/empower?serverName=test
- 不断在浏览器中刷新http://localhost:8401/empower?serverName=test2
- 上述2个rest地址,serverName=test或serverName=test2是处于黑名单的状态,无法访问,会发现无法访问,被Sentinel限流了
-
right
- http://localhost:8401/empower?serverName=abc
9、规则持久化
9.1、是什么
一旦我们重启微服务应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
9.2、怎么玩
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
9.3、步骤
- 修改cloudalibaba-sentinel-service8401
9.3.1、POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.mui.cloud</groupId>
<artifactId>cloud2024</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.mui.cloud</groupId>
<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.dashun.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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
9.3.2、YML
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
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
9.3.2.1、添加Nacos数据源配置
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
# 备注:rule-type是什么?看看源码
9.3.2.2、源代码
9.3.2.3、进一步说明
9.3.3、添加Nacos业务规则配置
9.3.3.1、内容解析
[
{
"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:是否集群。
9.3.4、快速访问测试接口
- http://localhost:8401/rateLimit/byUrl
- 上面地址访问后等待3秒
- 启动8401后刷新sentinel发现业务规则有了
- 默认
9.3.5、停止8401再看sentinel
9.3.6、重新启动8401再看sentinel
- 乍一看还是没有,稍等一会
- 多次调用
- http://localhost:8401/rateLimit/byUrl
-重新配置出现了,持久化验证成功
- http://localhost:8401/rateLimit/byUrl