(学习笔记)
QPS(Query Per Second):即每秒查询率,是对⼀个特定的查询服务器在规定时间内所处理流量多少的衡量标准。QPS = req/sec = 请求数/秒,即每秒的响应请求数,也即是最⼤吞吐能⼒。
1、介绍
(1)官网:https://sentinelguard.io/zh-cn/
(2)是什么?
主页 · alibaba/Sentinel Wiki · GitHub
(3)能干什么
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
需要知道的相关概念:服务雪崩、服务降级、服务熔断、服务限流、服务隔离、服务超时
2、下载安装运行
下载地址:Releases · alibaba/Sentinel · GitHub
sentinel组件由2部分构成:后台8719默认、前台8080开启
运行命令
前提条件:Java环境,8080端口没被占用
java -jar sentinel-dashboard-1.8.8.jar
登录账号密码均为sentinel
访问地址:http://localhost:8080
3、实操案例
微服务8401整合Sentinel入门案例
前提:启动Nacos8848成功,启动Sentinel8080成功
3.1、新建微服务cloudalibaba-sentinel-service8401
(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.atguigu.cloud</groupId><artifactId>springcloud2024</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 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><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
(2)application.yml
server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848 #Nacos服务注册中心地址sentinel:transport:dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
(3)主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class MainSentinel8401 {public static void main(String[] args) {SpringApplication.run(MainSentinel8401.class,args);}
}
(4)业务类
@RestController
public class FlowLimitController {@GetMapping("/testA")public String testA() {return "------testA";}@GetMapping("/testB")public String testB() {return "------testB";}
}
3.2、启动8401微服务后查看sentienl控制台
没有数据?
Sentinel采用的懒加载说明
注意:想使用Sentinel对某个接口进行限流和降级等操作,一定要先访问下接口,使Sentinel检测出相应的接口
执行一次访问即可 ,案例接口为:
http://localhost:8401/testA
http://localhost:8401/testB
4、流控规则
Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。参数见最下方:
1资源名 | 资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。 |
2针对来源 | 具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。 |
3阈值类型 | QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。 |
4单机阈值 | 与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。 |
5是否集群 | 选中则表示集群环境,不选中则表示非集群环境。 |
4.1、直接
默认的流控模式,当接口达到限流条件时,直接开启限流功能。
(1)配置及说明
表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
(2)测试
快速点击访问http://localhost:8401/testA
结果:Blocked by Sentinel (flow limiting)
4.2、关联
当关联的资源达到阈值时,就限流自己
(1)配置说明
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名,B惹事,A挂了
(2)测试
Jmeter模拟并发密集访问testB
Jmeter下载地址:Apache JMeter - Download Apache JMeter
启动Jmeter
添加测试
4.3、链路
来自不同链路的请求对同一个目标访问时,实施针对性的不同限流措施,比如C请求来访问就限流,D请求来访问就可以
(1)修改微服务cloudalibaba-sentinel-service8401的 yaml
server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud: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层调用不认为是同一个根链路
(2)新建FlowLimitService
package com.atguigu.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");}
}
(3)修改FlowLimitController
@RestController
public class FlowLimitController {@Resourceprivate FlowLimitService flowLimitService;@GetMapping("/testA")public String testA() {return "------testA";}@GetMapping("/testB")public String testB() {return "------testB";}@GetMapping("/testC")public String testC() {flowLimitService.common();return "------testC";}@GetMapping("/testD")public String testD() {flowLimitService.common();return "------testD";}
}
(4)sentinel配置
(5) 测试
http://localhost:8401/testC,超过一秒钟一次后,就发生限流
http://localhost:8401/testD
4.4、流控效果
4.4.1、快速失败(默认的流控处理)
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
4.4.2、预热WarmUp
网址:限流 冷启动 · alibaba/Sentinel Wiki · GitHub
限流 冷启动
公式:阈值除以冷却因子coldFactor(默认值为3),经过预热时长后才会达到阈值
官网:流量控制 · alibaba/Sentinel Wiki · GitHub
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 |
应用场景:
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
4.4.3、排队等待
(1)修改FlowLimitController
@GetMapping("/testE")
public String testE()
{System.out.println(System.currentTimeMillis()+" testE,排队等待");return "------testE";
}
(2)配置
测试:
按照单机阈值,一秒钟通过一个请求,10秒后的请求作为超时处理,放弃
4.5、阈值类型
4.5.1、QPS
4.5.2、并发线程数
(1)配置
(2)测试:Jmeter模拟多个线程并发+循环请求
5、熔断规则
官网:熔断降级 · alibaba/Sentinel Wiki · GitHub
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛DegradeException)。
5.1、慢调用比例
5.1.1、介绍
- 慢调用比例 (
SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
1、熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断
2、探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态
3、结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断。
名词解释:
1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用。
2.最大RT:即最大的响应时间,指系统对请求作出响应的业务处理时间, 时间单位为 ms。
3.慢调用:处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用。
4.慢调用比例:在所以调用中,慢调用占有实际的比例=慢调用次数➗总调用次数
5.比例阈值:自己设定的 , 比例阈值=慢调用次数➗调用次数
6.统计时长:时间的判断依据
7.最小请求数:设置的调用最小请求数,上图比如1秒钟打进来10个线程(大于我们配置的5个了)调用被触发
5.1.2、实操案例
(1)微服务8401添加controller
/*** 新增熔断规则-慢调用比例** @return*/@GetMapping("/testF")public String testF() {//暂停几秒钟线程try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("----测试:新增熔断规则-慢调用比例 ");return "------testF 新增熔断规则-慢调用比例";}
(2)sentinel配置
设置说明:1s内的请求数量大于5,且有超过10%的响应时间大于200ms,服务熔断5s后进入HALF-OPEN
状态,如果下一次请求的响应时间超过200ms,服务会再次熔断。
(3)jmeter压测
结论:
按照上述配置,熔断触发:
多次循环,一秒钟打进来10个线程(大于5个了)调用/testF,我们希望200毫秒处理完一次调用,和谐系统;^_^
假如在统计时长内,实际请求数目>最小请求数且慢调用比例>比例阈值 ,断路器打开(保险丝跳闸)微服务不可用(Blocked by Sentinel (flow limiting)),进入熔断状态5秒;后续我停止jmeter,没有这么大的访问量了,单独用浏览器访问rest地址,断路器关闭(保险丝恢复,合上闸口),
微服务恢复OK
5.2、异常比例
5.2.1、介绍
- 异常比例 (
ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。
5.2.2、实操案例
(1)微服务8401添加controller
/*** 新增熔断规则-异常比例** @return*/@GetMapping("/testG")public String testG() {System.out.println("----测试:新增熔断规则-异常比例 ");int age = 10 / 0;return "------testG,新增熔断规则-异常比例 ";}
(2)配置
不配置Sentinel,对于int age=10/0,调一次错一次报错error,页面报【Whitelabel Error Page】或全局异常
配置Sentinel,对于int age=10/0,如符合如下异常比例启动熔断,页面报【Blocked by Sentinel (flow limiting)】
设置说明:1s内的请求数量大于5,且有超过20%的异常请求,服务熔断5s后进入HALF-OPEN
状态,如果下一次还是异常请求,服务会再次熔断。
(3)测试
按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0)达到100%,调一次错一次报错error;
jmeter
开启jmeter后,直接高并发发送请求 ,多次调用达到配置条件。断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务熔断+服务降级,出提示Blocked by Sentinel (flow limiting)。
测试之后还是之前的报错页面,是因为被全局异常捕捉
注释掉全局异常处理,进行测试
5.3、异常数
5.3.1、介绍
- 异常数 (
ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
5.3.2、实操案例
(1)微服务8401添加controller
/*** 新增熔断规则-异常数** @return*/@GetMapping("/testH")public String testH() {System.out.println("----测试:新增熔断规则-异常数 ");int age = 10 / 0;return "------testH,新增熔断规则-异常数 ";}
(2)配置
设置说明:1s内的请求数量大于2,且有异常请求数量大于1,服务熔断5s后进入HALF-OPEN
状态,如果下一次请求正常,熔断结束;否则继续熔断。
(3)测试
第一次请求:
jmeter 测试
6、@SentinelResource注解
概念:SentinelResource是一个流量防卫防护组件注解,用于指定防护资源,对配置的资源进行流量控制、熔断降级等功能。
注解说明:
重点(value,blockHandler,fallback)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {//资源名称String value() default "";//entry类型,标记流量的方向,取值IN/OUT,默认是OUTEntryType 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.1、按照rest地址限流+默认限流返回
通过访问的rest地址来限流,会返回Sentinel自带默认的限流处理信息
(1)微服务8401添加接口RateLimitController
@RestController
public class RateLimitController {@GetMapping("/rateLimit/byUrl")public String byUrl() {return "按rest地址限流测试OK";}
}
(2)Sentinel控制台配置
(3)测试,多次连续点击 ,会返回Sentinel自带的限流处理结果,默认
6.2、按SentinelResource资源名称限流+自定义限流返回
不想用默认的限流提示(Blocked by Sentinel (flow limiting)),想返回自定义限流的提示。
(1)微服务8401(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";}
(2)配置和代码关系
(3)测试结果
6.3、按SentinelResource资源名称限流+自定义限流返回+服务降级处理
按照SentinelResource配置,点击超过限流配置返回自定义限流提示+程序异常返回fallback服务降级
(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();}
(2)图形配置和代码关系
(3)测试
连续点击 http://localhost:8401/rateLimit/doAction/2,调用了 blockHandler
点击 http://localhost:8401/rateLimit/doAction/0,调用了fallback
(4)总结
blockHandler,主要针对sentinel配置后出现的违规情况处理
fallback,程序异常了JVM抛出的异常服务降级
两者可以同时共存
7、热点规则(热点参数限流)
官网:热点参数限流 · alibaba/Sentinel Wiki · GitHub
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
7.1、实操案例
(1)业务类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";}
(2)配置
配置说明:方法testHotKey里面第一个参数P1只要QPS超过每秒1次,马上降级处理
(2)测试
7.2、参数例外项
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流。
特例情况:
普通正常限流:含有P1参数,超过1秒钟一个后,达到阈值1后马上被限流
例外特殊限流:
p1参数当它是某个特殊值时,到达某个约定值后【普通正常限流】规则突然例外、失效了,它的限流值和平时不一样
(1)添加配置
(2)测试
8、授权规则
官网:黑白名单控制 · alibaba/Sentinel Wiki · GitHub
在某些场景下,需要根据调用接口的来源判断是否允许执行本次请求。此时就可以使用Sentinel提供的授权规则来实现,Sentinel的授权规则能够根据请求的来源判断是否允许本次请求通过。
在Sentinel的授权规则中,提供了 白名单与黑名单 两种授权类型。白放行、黑禁止
8.1、实操案例
演示授权规则,黑名单禁止
(1)添加业务代码
@Slf4j
@RestController
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 {@Overridepublic String parseOrigin(HttpServletRequest request) {return request.getParameter("serverName");}
}
(2)配置
(3)测试
(4)总结
上述2个rest地址,serverName=test或serverName=test2是处于黑名单的状态,无法访问,会发现无法访问,被Sentinel限流了
9、规则持久化
问题:一旦重启微服务应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
解决方法:
将限流配置规则持久化保存到Nacos,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
9.1、案例实操
修改cloudalibaba-sentinel-service8401
(1)修改微服务8401的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.atguigu.cloud</groupId><artifactId>springcloud2024</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><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
(2)修改微服务8401的yaml
server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud: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:8848dataId: ${spring.application.name}groupId: DEFAULT_GROUPdata-type: jsonrule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType
备注:rule-type是什么?看看源码
进一步说明:
(3)添加Nacos业务规则配置
配置说明:请求接口:/rateLimit/byUrl,请求次数超过QPS=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:是否集群。
10、OpenFeign和Sentinel集成实现fallback服务降级
10.1、需求说明
cloudalibaba-consumer-nacos-order83 通过OpenFeign调用 cloudalibaba-provider-payment9001
1 83 通过OpenFeign调用 9001微服务,正常访问OK
2 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方法。
10.2、案例实操
编码步骤
10.2.1、修改服务提供者cloudalibaba-provider-payment9001
(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.atguigu.cloud</groupId><artifactId>springcloud2024</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloudalibaba-provider-payment9001</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><!--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><!--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><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
(2)写yaml
server:port: 9001spring:application:name: nacos-payment-providercloud:nacos:discovery:server-addr: localhost:8848 #配置Nacos地址sentinel:transport:dashboard: localhost:8080 #配置Sentinel dashboard控制台服务地址port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
(3)主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class Payment9001Application {public static void main(String[] args) {SpringApplication.run(Payment9001Application.class, args);}
}
(4)业务类
@RestController
public class PayAlibabaController {@Value("${server.port}")private String serverPort;@GetMapping(value = "/pay/nacos/{id}")public String getPayInfo(@PathVariable("id") Integer id) {return "nacos registry, serverPort: " + serverPort + "\t id" + id;}//openfeign+sentinel 进行服务降级和流量控制的整合处理case@GetMapping("/pay/nacos/get/{orderNo}")@SentinelResource(value = "getPayByOrderNo", blockHandler = "handlerBlockHandler")public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo) {//模拟从数据库查询出数据并赋值给DTOPayDTO 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());}*/
}
10.2.2、修改公共模块cloud-api-commons
(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.atguigu.cloud</groupId><artifactId>springcloud2024</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloud-api-commons</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><!--alibaba-sentinel--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><!--openfeign--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--web + actuator--><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><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency></dependencies></project>
(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);
}
(3)为远程调用新建全局统一服务降级类
fallback = PayFeignSentinelApiFallBack.class
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 {@Overridepublic ResultData getPayByOrderNo(String orderNo) {return ResultData.fail(ReturnCodeEnum.RC500.getCode(), "对方服务宕机或不可用,FallBack服务降级o(╥﹏╥)o");}
}
10.2.3、修改cloudalibaba-consumer-nacos-order83
(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.atguigu.cloud</groupId><artifactId>springcloud2024</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloudalibaba-consumer-nacos-order83</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><!-- 引入自己定义的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><!--nacos-discovery--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!--loadbalancer--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--web + actuator--><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><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
(2)写yaml
server:port: 83spring:application:name: nacos-order-consumercloud:nacos:discovery:server-addr: localhost:8848
#消费者将要去访问的微服务名称(nacos微服务提供者叫什么你写什么)
service-url:nacos-user-service: http://nacos-payment-provider# 激活Sentinel对Feign的支持
feign:sentinel:enabled: true
(3)主启动
package com.atguigu.cloud;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class Order83Application {public static void main(String[] args) {SpringApplication.run(Order83Application.class,args);}
}
(4)业务类
@RestController
public class OrderNacosController {@Resourceprivate RestTemplate restTemplate;@Resourceprivate PayFeignSentinelApi payFeignSentinelApi;@Value("${service-url.nacos-user-service}")private String serverURL;@GetMapping("/consumer/pay/nacos/{id}")public String paymentInfo(@PathVariable("id") Integer id) {String result = restTemplate.getForObject(serverURL + "/pay/nacos/" + id, String.class);return result + "\t" + " 我是OrderNacosController83调用者。。。。。。";}//---------------------------------------@GetMapping(value = "/consumer/pay/nacos/get/{orderNo}")public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo) {return payFeignSentinelApi.getPayByOrderNo(orderNo);}
}
(5)启动
故障现象
导致原因:
springboot+springcloud版本太高导致和阿里巴巴Sentinel不兼容
解决方案:总体父工程,boot+cloud降低版本
< 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> |
10.2.4、sentinel配置
10.2.5、测试
(1)频繁访问后触发了Sentinel的流控规则
blockHandler起效
(2) 测试83调用9001,此时故意关闭9001微服务提供者,看83消费侧自动降级,不会被耗死
11、GateWay和Sentinel集成实现服务限流
恢复父工程版本号
<spring.boot.version>3.2.0</spring.boot.version>
<spring.cloud.version>2023.0.0</spring.cloud.version>
需求说明:
cloudalibaba-sentinel-gateway9528 保护 cloudalibaba-provider-payment9001
11.1、新建Module cloudalibaba-sentinel-gateway9528
(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.atguigu.cloud</groupId><artifactId>springcloud2024</artifactId><version>1.0-SNAPSHOT</version></parent><artifactId>cloudalibaba-sentinel-gateway9528</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><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></project>
(2)yaml
server:port: 9528spring:application:name: cloudalibaba-sentinel-gateway # sentinel+gataway整合Casecloud:nacos:discovery:server-addr: localhost:8848gateway:routes:- id: pay_routh1 #pay_routh1 #路由的ID(类似mysql主键ID),没有固定规则但要求唯一,建议配合服务名uri: http://localhost:9001 #匹配后提供服务的路由地址predicates:- Path=/pay/** # 断言,路径相匹配的进行路由
(3)主启动
@SpringBootApplication
@EnableDiscoveryClient
public class Main9528 {public static void main(String[] args) {SpringApplication.run(Main9528.class, args);}
}
(4)业务类
参考官网源码:网关限流 · alibaba/Sentinel Wiki · GitHub
Spring Cloud Gateway
从 1.6.0 版本开始,Sentinel 提供了 Spring Cloud Gateway 的适配模块,可以提供两种资源维度的限流:
- route 维度:即在 Spring 配置文件中配置的路由条目,资源名为对应的 routeId
- 自定义 API 维度:用户可以利用 Sentinel 提供的 API 来自定义一些 API 分组
使用时需引入以下模块(以 Maven 为例):
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-spring-cloud-gateway-adapter</artifactId><version>x.y.z</version> </dependency>使用时只需注入对应的
SentinelGatewayFilter
实例以及SentinelGatewayBlockExceptionHandler
实例即可(若使用了 Spring Cloud Alibaba Sentinel,则只需按照文档进行配置即可,无需自己加 Configuration)。比如:@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(Ordered.HIGHEST_PRECEDENCE)public GlobalFilter sentinelGatewayFilter() {return new SentinelGatewayFilter();} }
Demo 示例:sentinel-demo-spring-cloud-gateway
@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
public void doInit() {
initCustomizedApis();
initGatewayRules();
}private void initCustomizedApis() {
Set<ApiDefinition> definitions = new HashSet<>();
ApiDefinition api1 = new ApiDefinition("some_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/ahas"));
add(new ApiPathPredicateItem().setPattern("/product/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
ApiDefinition api2 = new ApiDefinition("another_customized_api")
.setPredicateItems(new HashSet<ApiPredicateItem>() {{
add(new ApiPathPredicateItem().setPattern("/**")
.setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX));
}});
definitions.add(api1);
definitions.add(api2);
GatewayApiDefinitionManager.loadApiDefinitions(definitions);
}private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("aliyun_route")
.setCount(10)
.setIntervalSec(1)
);
rules.add(new GatewayFlowRule("aliyun_route")
.setCount(2)
.setIntervalSec(2)
.setBurst(2)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
)
);
rules.add(new GatewayFlowRule("httpbin_route")
.setCount(10)
.setIntervalSec(1)
.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)
.setMaxQueueingTimeoutMs(600)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER)
.setFieldName("X-Sentinel-Flag")
)
);
rules.add(new GatewayFlowRule("httpbin_route")
.setCount(1)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("pa")
)
);
rules.add(new GatewayFlowRule("httpbin_route")
.setCount(2)
.setIntervalSec(30)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("type")
.setPattern("warn")
.setMatchStrategy(SentinelGatewayConstants.PARAM_MATCH_STRATEGY_CONTAINS)
)
);rules.add(new GatewayFlowRule("some_customized_api")
.setResourceMode(SentinelGatewayConstants.RESOURCE_MODE_CUSTOM_API_NAME)
.setCount(5)
.setIntervalSec(1)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM)
.setFieldName("pn")
)
);
GatewayRuleManager.loadRules(rules);
}
}
业务代码
package com.atguigu.cloud.config;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();}@PostConstructpublic 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 blockRequestHandler = new BlockRequestHandler() {@Overridepublic Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {Map<String, String> map = new HashMap<>();map.put("errorCode", HttpStatus.TOO_MANY_REQUESTS.getReasonPhrase());map.put("errorMessage", "请求太过频繁,系统忙不过来,触发限流(sentinel+gateway整合Case)");return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(map));}};GatewayCallbackManager.setBlockHandler(blockRequestHandler);}}