目录
十四、SpringCloud Alibaba Sentinel实现熔断与限流
1.简介
2.作用
3.下载安装
4.微服务 8401 整合 Sentinel 入门案例
5.流控规则
(1)基本介绍
(2)流控模式
Ⅰ. 直接
Ⅱ. 关联
Ⅲ. 链路
(3) 流控效果
Ⅰ. 直接
Ⅱ. 预热WarmUp
Ⅲ. 排队等待
Ⅳ. 并发线程数
6.熔断规则
(1)慢调用比例
(2)异常比例
(3)异常数
7.@SentinelResource注解
(1)介绍
(2)实战
Ⅰ. 按照rest地址限流 + 默认限流返回
Ⅱ. 按SentinelResource资源名称限流 + 自定义限流返回
Ⅲ. 按SentinelResource资源名称限流 + 自定义限流返回 + 服务降级处理
8.热点规则
(1)介绍
(2) 实战
Ⅰ. 常规情况
Ⅱ. 特殊情况(参数例外项)
9.授权规则
10.规则持久化
11.OpenFeign和Sentinel集成实现fallback服务降级
(1)需求说明
(2)编码步骤
Ⅰ. 修改服务提供方cloudalibaba-provider-payment9001
Ⅱ. 修改cloud-api-commons
Ⅲ. 修改cloudalibaba-consumer-nacos-order83
(3)测试验证
12.GateWay和Sentinel集成实现服务限流
(1)需求说明
(2)编码步骤
十四、SpringCloud Alibaba Sentinel实现熔断与限流
1.简介
官网:home | Sentinel
等价于 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: 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扫描,直至找到未被占用的端口
④ 修改主启动类
@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不管*/@Resourceprivate 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,默认是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 {};
}
(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
{@Overridepublic 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: 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
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: 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扫描,直至找到未被占用的端口
③ 修改业务类 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){//模拟从数据库查询出数据并赋值给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());}*/
}
④ 启动 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
{@Overridepublic 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: 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
③ 修改主启动类,激活 feign 的功能
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients //启动feign的功能
public class Main83
{public static void main(String[] args){SpringApplication.run(Main83.class,args);}
}
④ 修改业务类 OrderNacosController
@RestController
public class OrderNacosController
{...@Resourceprivate 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: 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/** # 断言,路径相匹配的进行路由
注意:
如果这里你想使用动态路由(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);}
}
⑤ 配置
参考官网配置说明案例改写:网关限流 · alibaba/Sentinel Wiki · GitHubhttps://github.com/alibaba/Sentinel/wiki/%E7%BD%91%E5%85%B3%E9%99%90%E6%B5%81#spring-cloud-gateway
新建配置类 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.PostConstructpublic 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() {@Overridepublic 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:加快点击频率,出现限流容错
http://localhost:9528/pay/nacos/333