目录
十四、SpringCloud Alibaba Sentinel实现熔断与限流
Ⅱ. 按SentinelResource资源名称限流 + 自定义限流返回
Ⅲ. 按SentinelResource资源名称限流 + 自定义限流返回 + 服务降级处理
11.OpenFeign和Sentinel集成实现fallback服务降级
Ⅰ. 修改服务提供方cloudalibaba-provider-payment9001
Ⅲ. 修改cloudalibaba-consumer-nacos-order83
十四、SpringCloud Alibaba Sentinel实现熔断与限流
1.简介
等价于 Spring Cloud Circuit Breaker
2.作用
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel 从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性!
3.下载安装
下载地址: Releases · alibaba/Sentinel
Sentinel 组件由两部分组成,后台默认端口:8719,前台默认端口:8080
所以,启动时千万要注意,8080 端口不能被占用!
安装目录打开 cmd 窗口,运行命令:java -jar sentinel-dashboard-1.8.8.jar
访问 sentinel 管理界面:http://localhost:8080(登录账号密码均为 sentinel)
4.微服务 8401 整合 Sentinel 入门案例
步骤:
Ⅰ. 启动 Nacos8848 成功
命令:startup.cmd -m standalone
网址:http://localhost:8848/nacos/#/login
Ⅱ. 启动Sentinel8080成功
Ⅲ. 新建微服务8401(将被哨兵纳入管控的 8401微服务提供者)
① 新建 module(cloudalibaba-sentinel-service8401)
② 导入依赖
<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.mihoyo.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>
③ 修改 application.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扫描,直至找到未被占用的端口
④ 修改主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class Main8401
{
public static void main(String[] args)
{
SpringApplication.run(Main8401.class,args);
}
}
⑤ 编写业务类(FlowLimitController)
@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
return "------testB";
}
}
⑥ 启动微服务 8401 并访问
Ⅳ. 启动 8401 微服务后查看 sentienl 控制台
一开始,控制台是空的:
Sentinel采用的懒加载说明:
想使用 Sentinel 对某个接口进行限流和降级等操作,一定要先访问下接口,使 Sentinel 检测出相应的接口
访问地址:
http://localhost:8401/testA
http://localhost:8401/testB
访问后,sentinel 控制台效果:
5.流控规则
(1)基本介绍
Sentinel 能够对流量进行控制,主要是监控应用的 QPS 流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。
参数如下:
1 |
资源名
|
资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。
|
2 |
针对来源
|
具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。
|
3 |
阈值类型
|
QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。
|
4 |
单机阈值
|
与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。
|
5 |
是否集群
|
选中则表示集群环境,不选中则表示非集群环境。
|
(2)流控模式
Ⅰ. 直接
说明:默认的流控模式是,当接口达到限流条件时,直接开启限流功能。
配置说明:表示 1 秒钟内查询 2 次就是OK,若超过次数 2,就直接 - 快速失败,报默认错误
测试:快速点击访问 http://localhost:8401/testA
当 1 秒钟点击超过 2次,就会触发限流!
但是,直接显示报错信息并不是我们想要的,我们需要通过一个 fallback 方法进行服务降级。
Ⅱ. 关联
说明:当关联的资源达到阈值时,就限流自己 --> 当与 A 关联的资源 B 达到阀值后,就限流A 自己(B惹事,A挂了)
配置说明:当关联资源 /testB 的 qps 阀值超过 1 时,就限流 /testA 的 Rest 访问地址
(当关联资源到阈值后限制配置好的资源名,B惹事,A挂了)
使用 Jmeter 模拟并发密集访问 testB:
执行 1 次,4s 内发 80 个请求
正常情况下,testA正常访问:
打开 JMeter,让大批量线程高并发访问 testB
此时再访问被关联的 testA,就会触发限流
Ⅲ. 链路
说明:来自不同链路的请求对同一个目标访问时,实施针对性的不同限流措施
(比如 C 请求来访问就限流,D 请求来访问就是 OK)
准备:
① 新建 FlowLimitService
@Service
public class FlowLimitService
{
@SentinelResource(value = "common")
public void common()
{
System.out.println("------FlowLimitService come in");
}
}
注意:@SentinelResource注解后续将会详细介绍
② 修改 FlowLimitController
@RestController
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
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";
}
}
③ 修改 application.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层调用不认为是同一个根链路
细节:
web-context-unify表示上下文的隔离性。当它设置为 false 之后,表示每个请求的上下文都是独立的。因此,虽然 testC 和 testD 调用(共享)同一个 service 方法,但在 Sentinel 的监控和流量控制下,每一个请求都是独立的!所以如果某一请求的负载过大,Sentinel 只会对该请求的上下文进行限流,而不会影响到其他请求。
web-context-unify: true 结果如下:
web-context-unify: false 结果如下:
配置说明:当 common 方法由 testC 链路调用,qps 阀值超过 1 时,就触发限流
此时,每秒访问一次 testC 和 testD 都是正常的。
但是,如果 testC 每秒超过一次,就会触发限流,但 testD 不会!
抛出的异常被全局异常捕捉机制捕捉到!
(3) 流控效果
Ⅰ. 直接
快速失败(默认的流控处理)--> 直接失败,抛出异常:Blocked by Sentinel (flow limiting)
Ⅱ. 预热WarmUp
限流 / 冷启动:
公式:阈值除以冷却因子 coldFactor(默认值为3),经过预热时长后才会达到阈值
源码:
配置说明:
默认 coldFactor 为 3,即请求 QPS 从(threshold / 3)开始,经多少预热时长才逐渐升至设定的 QPS 阈值。
|
案例: 单机阈值为 10,预热时长设置 5 秒。 系统初始化的阈值为 10 / 3 约等于 3,即单机阈值刚开始为 3(我们人工设定单机阈值是 10,sentinel计算后QPS 判定为 3 开始);
然后过了 5 秒后阀值才慢慢升高恢复到设置的单机阈值 10。
|
此时,testB 的访问结果如下:
应用场景:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
Ⅲ. 排队等待
修改 FlowLimitController:
@RestController
public class FlowLimitController
{
...
//流控效果--排队等待
@GetMapping("/testE")
public String testE()
{
System.out.println(System.currentTimeMillis()+" testE,排队等待");
return "------testE";
}
}
配置说明:按照单机阈值,一秒钟通过一个请求,10 秒后的请求作为超时处理,放弃
打开 JMeter 进行测试,1s 内向 testE 发送 20 个请求
细节:这里接收了 11 个请求,因为会有一定时间上的误差,让第 11 个请求挤了进来。
Ⅳ. 并发线程数
配置说明:testB 的并发数量仅为 1,其余的请求只能等待线程结束,再抢占线程
打开 JMeter 进行测试,100 个线程同时访问 testB,无限下去
此时,这 100 个线程不断循环往复的去访问 testB,也就是唯一的那 1 个并发线程,几乎一直处于被抢占状态。
这时,如果我们浏览器访问 testB,基本上是打不开的,因为抢不到线程。
当然,也有极小概率刚好抢到这唯一的线程,但极大可能是抢不到的!
6.熔断规则
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,
让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
(1)慢调用比例
举例:
效果:
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
名词解释 | ||
---|---|---|
1 | 调用 | 一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。 |
2 | 最大RT | 即最大的响应时间,指系统对请求作出响应的业务处理时间。 |
3 | 慢调用 | 处理业务逻辑的实际时间 > 设置的最大RT时间,这个调用叫做慢调用。 |
4 | 慢调用比例 | 在所以调用中,慢调用占有实际的比例=慢调用次数➗总调用次数 |
5 | 比例阈值 | 自己设定的 , 比例阈值 = 慢调用次数➗调用次数 |
6 | 统计时长 | 时间的判断依据 |
7 | 最小请求数 | 设置的调用最小请求数,上图比如 1 秒钟打进来 10 个线程(大于我们配置的 5 个了)调用被触发 |
熔断状态 | ||
---|---|---|
1 | 熔断状态(保险丝跳闸断电,不可访问) | 在接下来的熔断时长内请求会自动被熔断 |
2 | 探测恢复状态(探路先锋) | 熔断时长结束后进入探测恢复状态 |
3 | 结束熔断(保险丝闭合恢复,可以访问) | 在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。 |
修改 FlowLimitController:
@RestController
public class FlowLimitController
{
...
//新增熔断规则-慢调用比例
@GetMapping("/testF")
public String testF()
{
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("----测试:新增熔断规则-慢调用比例 ");
return "------testF 新增熔断规则-慢调用比例";
}
}
说明:
10 个线程,在 1s 内发送完。又因为服务器响应时长设置:暂停 1s,而 RT = 200ms,所以响应一个请求的时长都大于 1s。
综上,符合熔断条件,所以当线程开启1秒后,进入熔断状态!
配置:
打开 JMeter 进行测试,1s 内 10个线程访问 testF,使之触发熔断状态
此时,浏览器访问 testF,必然访问失败
解释:
多次循环,一秒钟打进来10个线程(大于 5 个了)调用 /testF,我们希望 200 毫秒处理完一次调用,和谐系统;
因为在统计时长内,实际请求数目>最小请求数 且 慢调用比例>比例阈值 ,断路器打开(保险丝跳闸)微服务不可用(Blocked by Sentinel (flow limiting)),进入熔断状态 5 秒;
后续我们停止 jmeter,没有这么大的访问量了,单独用浏览器访问 rest 地址,断路器关闭(保险丝恢复,合上闸口),微服务恢复OK
(2)异常比例
举例:
效果:
修改 FlowLimitController:
@RestController
public class FlowLimitController
{
...
//新增熔断规则-异常比例
@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)】
注意:为了能够更好的展示效果,务必关闭全局异常处理
否则普通异常和熔断异常显示的页面都一样,如下:
配置:
打开 JMeter 进行测试,1s 内 20 个线程访问 testG,使之触发熔断状态
此时,浏览器访问 testG,触发熔断异常
(3)异常数
举例:
效果:
修改 FlowLimitController:
@RestController
public class FlowLimitController
{
...
//新增熔断规则-异常数
@GetMapping("/testH")
public String testH()
{
System.out.println("----测试:新增熔断规则-异常数 ");
int age = 10/0;
return "------testH,新增熔断规则-异常数 ";
}
}
正常访问:
配置说明:1s 内最小请求数为 5,只要有 1 次异常,就会触发熔断状态
打开 JMeter 进行测试,1s 内 20 个线程访问 testH,使之触发熔断状态
此时,浏览器访问 testH,触发熔断异常
7.@SentinelResource注解
(1)介绍
@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 {};
}
(2)实战
在使用 @SentinelResource 注解之前,请确保 nacos 和 sentinel 均已启动成功。
Ⅰ. 按照rest地址限流 + 默认限流返回
说明:通过访问的 rest 地址来限流,会返回 Sentinel 自带默认的限流处理信息
步骤:
① 新增业务类RateLimitController
@RestController
@Slf4j
public class RateLimitController
{
@GetMapping("/rateLimit/byUrl")
public String byUrl()
{
return "按rest地址限流测试OK";
}
}
② 访问地址:http://localhost:8401/rateLimit/byUrl,让其在 sentinel 中检测到
③ Sentinel控制台配置
此时,浏览器疯狂刷新,访问:http://localhost:8401/rateLimit/byUrl
结果:会返回 Sentinel 自带的限流处理结果,默认
解释:
虽然我们的 rest 方法没有加 @SentinelResource 注解,但由于其 Rest 地址是唯一的,所以也可以触发 Sentinel 的限流
Ⅱ. 按SentinelResource资源名称限流 + 自定义限流返回
说明:不想用默认的限流提示(Blocked by Sentinel (flow limiting)),想返回自定义限流的提示
步骤:
① 修改业务类RateLimitController
@RestController
@Slf4j
public class 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";
}
}
② 访问地址:http://localhost:8401/rateLimit/byResource,让其在 sentinel 中检测到
③ Sentinel 控制台配置
关系说明:
此时,浏览器疯狂刷新,访问:http://localhost:8401/rateLimit/byResource
结果:返回了自定义的限流处理信息,限流发生
Ⅲ. 按SentinelResource资源名称限流 + 自定义限流返回 + 服务降级处理
说明:按SentinelResource配置,点击超过限流配置返回自定义限流提示+程序异常返回fallback服务降级
步骤:
① 修改业务类 RateLimitController
@RestController
@Slf4j
public class 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();
}
}
注意:blockHander 方法 和 fallback 方法的形参要包含源 rest方法的所有参数
② 访问地址:http://localhost:8401/rateLimit/doAction/2,让其在 sentinel 中检测到
③ Sentinel 控制台配置
关系说明:
Question:blockHander 方法 和 fallback 方法都是兜底方法?会不会冲突呢?
浏览器疯狂刷新,访问:http://localhost:8401/rateLimit/doAction/2
返回了自定义的限流处理信息(blockHander 方法),限流发生 !
这是否意味着 fallback 方法无效?两者冲突了?
别急,我们访问地址: http://localhost:8401/rateLimit/doAction/0
我们发现,异常发生,返回了自定义的服务降级处理(fallback 方法)!
总结:
blockHander 方法 和 fallback 方法两者可以共存!
- blockHander 方法:主要针对 sentinel 配置后出现的违规情况处理
- fallback方法:程序异常了,JVM 抛出的异常服务降级
8.热点规则
(1)介绍
热点:即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作。
(2) 实战
Ⅰ. 常规情况
步骤:
① 修改业务类 RateLimitController
@RestController
@Slf4j
public class RateLimitController
{
...
@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";
}
}
② 访问地址:http://localhost:8401/testHotKey,让其在 sentinel 中检测到
③ Sentinel 控制台配置
关系说明:
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource 注解的方法参数索引,0 代表第一个参数,1 代表第二个参数,以此类推。
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
上面的图表示:第一个参数 p1 有值的话,只要 QPS 超过 1 秒内 1次,就马上限流,限流后调用 dealHandler_testHotKey 方法。
④ 测试:
✖error:http://localhost:8401/testHotKey?p1=abc
含有参数P1,当每秒访问的频率超过1次时,会触发Sentinel的限流操作
✖error:http://localhost:8401/testHotKey?p1=abc&p2=33
含有参数P1,当每秒访问的频率超过1次时,会触发Sentinel的限流操作
✔right:http://localhost:8401/testHotKey?p2=33
没有热点参数P1,不断访问则不会触发限流操作
Ⅱ. 特殊情况(参数例外项)
普通正常限流:
含有 P1 参数,超过1秒钟一个后,达到阈值 1 后马上被限流
例外特殊限流:
我们期望 p1 参数,当它是某个特殊值时(到达某个约定值后),【普通正常限流】规则突然例外(失效了),它的限流值和平时不一样
(假如当 p1 的值等于 5 时,它的阈值可以达到 200 或其它值)
配置:
注意:
① 添加按钮不能忘记点!!!
② 热点参数的注意点,参数必须是基本类型或者 String
测试:
✖error:http://localhost:8401/testHotKey?p1=3
超过1秒钟一个后,达到阈值后马上被限流
✔right:http://localhost:8401/testHotKey?p1=5
超过1秒钟一个后,达到阈值 200 后才会被限流
效果:
- 当p1等于5的时候,阈值变为200
- 当p1不等于5的时候,阈值就是平常的【普通正常限流】规则
9.授权规则
在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel 的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。(白放行、黑禁止)
步骤:
① 新建业务类 EmpowerController
@RestController
@Slf4j
public class EmpowerController //Empower授权规则,用来处理请求的来源
{
@GetMapping(value = "/empower")
public String requestSentinel4(){
log.info("测试Sentinel授权规则empower");
return "Sentinel授权规则";
}
}
② 新建 MyRequestOriginParser 类,用于定义放行规则
@Component
public class MyRequestOriginParser implements RequestOriginParser
{
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("serverName");
}
}
细节:
MyRequestOriginParser 类实现了 RequestOriginParser 接口
我们通过重写 parseOrigin 方法,告诉资源地址,我们发送的请求需要携带什么参数(serverName),再通过后续的 sentinel 配置,设置哪些值为白名单,哪些值为黑名单。
③ 访问地址:http://localhost:8401/empower,让其在 sentinel 中检测到
④ Sentinel 控制台配置
我们设置了黑名单为:test1 和 test2
也就是,当请求传入的参数有 serverName,且它的值为 test1 或 test2 时,禁止访问!
如果不带 serverName 参数,或者它的值为其他值时,放行。
⑤ 测试
✖error:http://localhost:8401/empower?serverName=test1
✖error:http://localhost:8401/empower?serverName=test2
✔right:http://localhost:8401/empower?serverName=test3
✔right:http://localhost:8401/empower
10.规则持久化
问题:
虽然我们在 Sentinel 控制台配置了很多规则,但肯定有人已经发现:
一旦我们重启微服务应用,sentinel 规则将消失!
所以,生产环境需要将配置规则进行持久化。
解决办法:
将限流配置规则持久化进 Nacos 保存,只要刷新 8401 某个 rest 地址,sentinel 控制台的流控规则就能看到,只要 Nacos 里面的配置不删除,针对 8401 上 sentinel 上的流控规则持续有效。
步骤(修改 8401):
① 导入依赖
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
② 修改 application.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
Question:rule-type是什么?看看源码
我们现在只配置了一个流控规则,我们也可以同时配置多个规则
③ 添加 Nacos 业务规则配置
注意:配置格式是 json,不是 yaml
参数说明:
resource | 资源名称 |
limitApp | 来源应用 |
grade | 阈值类型,0 表示线程数,1 表示QPS |
count | 单机阈值 |
strategy | 流控模式,0 表示直接,1 表示关联,2 表示链路 |
controlBehavior | 流控效果,0 表示快速失败,1 表示Warm Up,2 表示排队等待 |
clusterMode | 是否集群 |
④ 测试
重启 8401,访问地址:http://localhost:8401/rateLimit/byUrl
刷新 sentinel,发现新增了流控规则
这时我们停止 8401,再刷新 sentinel
我们发现刚才新增的流控规则又消失了,难道持久化失败了?
重启 8401,再度刷新 sentinel 查看
我们发现 sentinel 中还是空空如也,难道真的失败了吗?
别急,我们访问一下地址:http://localhost:8401/rateLimit/byUrl,多刷新几次
我们发现,该 rest 地址被限流了,这说明,流控规则还在,他生效了!
再次刷新 sentinel 查看
没错,刚才新增的流控规则又回来了,持久化验证成功!
这是因为,sentinel 采用懒加载的方式,即需要的时候才会出现,不用的时候不会打扰。
11.OpenFeign和Sentinel集成实现fallback服务降级
(1)需求说明
① 83 通过OpenFeign调用 9001微服务,正常访问OK
② 83 通过OpenFeign调用 9001微服务,异常访问error
访问者要有 fallback 服务降级的情况,不要持续访问 9001 加大微服务负担,但是通过 feign 接口调用的又方法各自不同。
如果每个不同方法都加一个fallback配对方法,会导致代码膨胀不好管理,工程埋雷
③ 9001 微服务自身还带着 sentinel 内部配置的流控规则,如果满足也会被触发。
所以,为了统一进行管理
public @interface FeignClient
通过 fallback 属性进行统一配置,feign 接口里面定义的全部方法都走统一的服务降级,一个搞定即可。
即本例有 2 个Case:
- OpenFeign 接口的统一 fallback 服务降级处理
- Sentinel 访问触发了自定义的限流配置,在注解 @SentinelResource 里面配置的 blockHandler 方法。
(2)编码步骤
前提:nacos服务器 和 Sentinel 启动成功
Ⅰ. 修改服务提供方cloudalibaba-provider-payment9001
① 导入依赖
<!--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>
② 修改 application.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扫描,直至找到未被占用的端口
③ 修改业务类 PayAlibabaController
@RestController
public class PayAlibabaController
{
...
//openfeign+sentinel进行服务降级和流量监控的整合处理case
@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 测试,访问地址:http://localhost:9001/pay/nacos/get/ord1024
Ⅱ. 修改cloud-api-commons
① 导入依赖(openfeign 的依赖之前已经引入过,所以只需要引入 sentinel 的依赖)
<!--alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
② 新增 PayFeignSentinelApi 接口
@FeignClient(value = "nacos-payment-provider",fallback = PayFeignSentinelApiFallBack.class)
public interface PayFeignSentinelApi
{
@GetMapping("/pay/nacos/get/{orderNo}")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo);
}
③ 新建全局统一服务降级类(fallback = PayFeignSentinelApiFallBack.class)
@Component
public class PayFeignSentinelApiFallBack implements PayFeignSentinelApi
{
@Override
public ResultData getPayByOrderNo(String orderNo)
{
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o");
}
}
④ 通用 api 模块修改完成后,maven 需要刷新一下
Ⅲ. 修改cloudalibaba-consumer-nacos-order83
① 导入依赖
<!-- 引入自己定义的api通用包 -->
<dependency>
<groupId>com.mihoyo.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>
② 修改 application.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
③ 修改主启动类,激活 feign 的功能
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients //启动feign的功能
public class Main83
{
public static void main(String[] args)
{
SpringApplication.run(Main83.class,args);
}
}
④ 修改业务类 OrderNacosController
@RestController
public class OrderNacosController
{
...
@Resource
private PayFeignSentinelApi payFeignSentinelApi;
@GetMapping(value = "/consumer/pay/nacos/get/{orderNo}")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo)
{
return payFeignSentinelApi.getPayByOrderNo(orderNo);
}
}
(3)测试验证
① 启动 9001 和 83,访问地址:http://localhost:83/consumer/pay/nacos/get/1024
83 通过 feign 成功调用 9001,测试成功!
细节:
视频中采用的 springboot 和 springcloud 版本过高,而 springcloud alibaba 的版本由于没来的及更新,所以不兼容。
我这里 springcloud alibaba 的版本为 2023.0.0.0-RC1,已经完成兼容,所以不会出现问题。
② Sentinel 控制台配置
③ 测试限流:
访问地址:http://localhost:83/consumer/pay/nacos/get/1024,不停刷新
触发了 sentinel 的流控配置规则,blockHandler 方法生效!
④ 测试服务降级:
模拟场景:9001宕机了,83 继续通过 feign 调用 9001
关闭 9001 服务,继续访问地址: http://localhost:83/consumer/pay/nacos/get/1024
触发服务降级,调用 fallback 方法!
12.GateWay和Sentinel集成实现服务限流
(1)需求说明
(2)编码步骤
前提:nacos服务器 和 Sentinel 启动成功
① 新建 module(cloudalibaba-sentinel-gateway9528)
② 导入依赖
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
③ 修改 application.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/** # 断言,路径相匹配的进行路由
注意:
如果这里你想使用动态路由(uri: lb://nacos-payment-provider) ,9528 网关必须导入 nacos 和 loadbalancer 的相关依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
nacos 依赖:它负责让网关 9528 可以通过 Nacos 注册中心 动态获取服务列表。
loadbalancer 依赖:网关通过 lb://
协议访问服务时,负载均衡器需要决定将请求发送到哪一个具体的服务实例。
Question:可能会有人注意到,之前 9527 使用动态路由的时候,只是导入了 consul 的相关依赖,并没有导入 loadbalancer 的依赖啊?
Spring Cloud Gateway 依赖
spring-cloud-loadbalancer
提供负载均衡能力,但这一能力的生效依赖于网关能够获取服务实例列表。而服务实例列表的来源依赖于服务注册发现组件,比如:
- Eureka、Consul、Zookeeper(Spring Cloud 原生支持)。
- Nacos(Spring Cloud Alibaba 生态)。
对于 Spring Cloud Gateway:
- 如果使用的是 Spring Cloud 原生的服务注册发现组件(如 Consul),网关的
spring-cloud-starter-gateway
中已经隐式集成了负载均衡相关功能,无需显式添加额外依赖。- 如果使用的是 Spring Cloud Alibaba 的服务注册发现组件(如 Nacos),需要显式引入
spring-cloud-starter-loadbalancer
,因为它不默认包含。总结:
9527
网关(基于 Consul):
- Spring Cloud Gateway 和 Spring Cloud Consul 是原生兼容的,
lb://
的负载均衡能力通过默认配置生效。
9528
网关(基于 Nacos):
- Spring Cloud Gateway 和 Nacos 不直接兼容,需要显式引入
spring-cloud-starter-loadbalancer
,否则lb://
无法正常解析。
④ 修改主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class Main9528
{
public static void main(String[] args)
{
SpringApplication.run(Main9528.class,args);
}
}
⑤ 配置
新建配置类 GatewayConfiguration:
//使用时只需注入对应的 SentinelGatewayFilter 实例以及 SentinelGatewayBlockExceptionHandler 实例即可
@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) //QPS为1s内2次
);
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);
}
}
⑥ 启动 9001 和 9528,进行测试
原生url:http://localhost:9001/pay/nacos/333
http://localhost:9001/pay/nacos/333
加网关:http://localhost:9528/pay/nacos/333
sentinel+gateway:加快点击频率,出现限流容错