Sentinel实现熔断与限流
1、Sentinel简介
GitHub地址:介绍 · alibaba/Sentinel Wiki · GitHub
官网:home | Sentinel (sentinelguard.io)
Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。
Sentinel 等价对标的就是Spring Cloud Circuit Breaker
微服务生态全景图(sentinel官网)
Sentinel 的特性
-
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
-
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
-
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel的主要特性
2、Sentinel下载与安装
sentinel组件由2部分构成:
- 核心库(Java客户端)|(不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo / Spring Cloud等框架也有较好的支持。
- 控制台(Dashboard)基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。
sentinel1.8.6版本的下载地址:https://github.com/alibaba/Sentinel/releases/tag/1.8.6
下载完成后需要启动,前提是java的环境是没问题的,然后8080端口不能被占用。
java -jar sentinel-dashboard-1.8.6.jar
访问sentinel界面,登陆的账号和密码都是sentinel
现在还啥也没有
3、微服务8401整合Sentinel入门案例
先把nacos8848启动
startup.cmd -m standalone
3.1、新建微服务8401
新建微服务cloudalibaba-sentinel-service8401,我们让sentinel去监控它,出现错误该熔断熔断该限流就限流。
导入依赖
xml
<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.zm.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
yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #配置sentinel dashboard 控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
主启动类
java
@SpringBootApplication
@EnableDiscoveryClient
public class Main8401 {
public static void main(String[] args) {
SpringApplication.run(Main8401.class,args);
}
}
简简单单写一个业务类FlowLimitController
java
@RestController
public class FlowLimitController {
@GetMapping("/test1")
public String test1(){
return "--------test1----------";
}
@GetMapping("/test2")
public String test2(){
return "--------test2----------";
}
}
启动8401微服务看到nacos控制台cloudalibaba-sentinel-service服务已经入驻
刷新一下sentinel的控制台会看到并没有什么变化,还是一如既往的空。
没有任何内容是因为sentinel采用的是懒加载的方式监控,你想监控哪一个接口你得先访问一下才能监控,你访问了sentinel才开始监测
访问完之后再刷新sentinel的控制台就有信息了
4、流控规则
4.1、基本介绍
Sentinel能够对流量进行控制,主要是监控应用的QPS流量或者并发线程数等指标,如果达到指定的阈值时,就会被流量进行控制,以避免服务被瞬时的高并发流量击垮,保证服务的高可靠性。
新增流控规则说明:
资源名 | 资源的唯一名称,默认就是请求的接口路径,可以自行修改,但是要保证唯一。 |
---|---|
针对来源 | 具体针对某个微服务进行限流,默认值为default,表示不区分来源,全部限流。 |
阈值类型 | QPS表示通过QPS进行限流,并发线程数表示通过并发线程数限流。 |
单机阈值 | 与阈值类型组合使用。如果阈值类型选择的是QPS,表示当调用接口的QPS达到阈值时,进行限流操作。如果阈值类型选择的是并发线程数,则表示当调用接口的并发线程数达到阈值时,进行限流操作。 |
是否集群 | 选中则表示集群环境,不选中则表示非集群环境。 |
4.2、流控模式
4.2.1、直接
默认的流控模式,当接口达到限流条件时,直接开启限流功能。
这样配置的表示2秒钟就只能访问1次,超了就直接快速失败,报默认的错误,我们快速访问localhost:8401/test1。
它报错是报错了,但是这个报错真的很不人性化,我们想自定义也是可以的,类似有个fallback的兜底方法。
4.2.2、关联
当关联的资源达到阈值时,就限流自己,当与A关联的资源B达到阀值后,就限流A自己,B惹事了,A就挂了,张三感冒李四吃药。想象一个场景,有人刷单,在订单模块一直下单,库存模块都没有库存了,咋办,库存模块直接挂掉。
编辑原来的那个资源名为/test1的配置,关联/test2
使用压测软件jmeter让它每秒20次访问/test2,然后我们在这段时间中访问test1是不行的。
现在没启动压测,访问test1和2都是正常的,
添加请求路劲
现在启动压测,让它访问test2,然后在浏览器访问test1就没法访问了
等压测结束就好了。
4.2.3、链路
来自不同链路的请求对同一个目标访问时,实施针对性的不同限流措施,比如C请求来访问就限流,D请求来访问就是OK
修改一下cloudalibaba-sentinel-service8401的application.yml文件
yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #配置sentinel dashboard 控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路
新建FlowLimitService
java
@Service
public class FlowLimitService {
@SentinelResource(value = "common")
public void common()
{
System.out.println("------FlowLimitService come in");
}
}
修改FlowLimitController
java
//链路测试 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";
}
重启8401后访问一下testC和D,在sentinel配置
新增的规则
那么现在我们疯狂刷新testC你就会发现它被制裁了,而testD你随便
4.2、流控效果
直接,快速失败
上面的例子都是直接的快速失败案例。
4.2.1、预热WarmUP
限流冷启动
有一个公式:阈值除以冷却因子coldFactor(默认值为3),经过预热时长后才会达到阈值
官网说明:
WarmUP配置
默认coldFactor为3,即请求QPS从(threshold / 3)开始,经多少预热时长才逐渐升至设定的QPSs阈值。
单机阈值为10,预热时长设置5秒。
系统初始化的阈值为10 / 3 约等于3,即单机阈值刚开始为3(我们人工设定单机阈值是10,sentinel计算后QPS判定为3开始);
然后过了5秒后阀值才慢慢升高恢复到设置的单机阈值10,也就是说5秒钟内QPS为3,过了保护期5秒后QPS为10
然后开始疯狂刷新localhost:8401/test2前3秒会被限流,5秒后就是限流1秒10次
这个的应用场景就比如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
4.2.2、排队等待
演示案例,controller新加一个testE
java
@GetMapping("/testE")
public String testE(){
System.out.println(System.currentTimeMillis()+"-------->testE,排队等待");
return "TestE";
}
访问一次看看,再在sentinel配置,按照单机阈值,1秒通过一个请求,10秒后的请求作为超时处理,放弃。
在jmeter中添加测试,1秒上20个请求
结果进来了13个,有点误差,应该是10个
4.2.3、并发线程数
选择并发线程数的时候默认的流控规则就是直接的快速失败。
Jmeter模拟多个线程并发+循环请求
Jmeter给它打满了,大部分我们自己访问都不好使,偶尔Jmeter线程切换系统判定没访问,我们自己的点击才有点机会,偶尔才能看到正常的访问,除非你给Jmeter停了。
5、熔断规则
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
5.1、慢比例调用
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
进入熔断状态判断依据:在统计时长内,实际请求数目>设定的最小请求数 且 实际慢调用比例>比例阈值 ,进入熔断状态。
1.调用:一个请求发送到服务器,服务器给与响应,一个响应就是一个调用;
2.最大RT:即最大的响应时间,指系统对请求作出响应的业务处理时间;
3.慢调用:处理业务逻辑的实际时间>设置的最大RT时间,这个调用叫做慢调用;
4.慢调用比例:在所有调用中,慢调用占有实际的比例=慢调用次数➗总调用次数;
5.比例阈值:自己设定的 , 当慢调用比例大于比例阈值且实际请求数大于最小请求数时会触发熔断;
6.统计时长:时间的判断依据;
7.最小请求数:设置的调用最小请求数,上图比如1秒钟打进来10个线程(大于我们配置的5个了)调用被触发;
熔断状态:
- 熔断状态(保险丝跳闸断电,不可访问):在接下来的熔断时长内请求会自动被熔断;
- 探测恢复状态(探路先锋):熔断时长结束后进入探测恢复状态;
- 结束熔断(保险丝闭合恢复,可以访问):在探测恢复状态,如果接下来的一个请求响应时间小于设置的慢调用 RT,则结束熔断,否则继续熔断;
测试:
java
//新增熔断规则-慢调用比例
@GetMapping("/testF")
public String testF(){
//暂停几秒钟线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("----测试:新增熔断规则-慢调用比例 ");
return "--------testF新增熔断规则-慢调用比例-------";
}
10个线程,在一秒的时间内发送完。又因为服务器响应时长设置:暂停1秒,所以响应一个请求的时长都大于1秒综上符合熔断条件,所以当线程开启1秒后,进入熔断状态。
先正常访问一下:
上jmeter压测,1秒打10个请求,每一个都是慢调用。
上了压测直接凉凉
停止jmeter后过很长时间再试试就好了。
总结一下:
多次循环,一秒钟打进来10个线程(大于5个了)调用/testF,我们希望200毫秒处理完一次调用,和谐系统;
假如在统计时长内,实际请求数目>最小请求数且慢调用比例>比例阈值 ,断路器打开(保险丝跳闸)微服务不可用(Blocked by Sentinel (flow limiting)),进入熔断状态5秒;
后续停止jmeter,没有这么大的访问量了,单独用浏览器访问rest地址,断路器关闭(保险丝恢复,合上闸口),
5.2、异常比例
异常比例 (ERROR_RATIO):
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
在controller中新增测试内容
java
//新增熔断规则-异常数比例
@GetMapping("/testG")
public String testG(){
System.out.println("----测试:新增熔断规则-异常数比例 ");
int age =1/0;
return "-------testG,新增熔断规则-异常数比例---------";
}
现在先不配置sentinel直接访问,我们之前配置的有全局异常,会返回500错误。
如果配置了sentinel的熔断规则,就会报Blocked by Sentinel (flow limiting),但也有可能会被我们的全局异常给捕捉到,页面可能还是500.
在jmeter中1秒打20个,页面还是500,被全局异常捕捉到了。
把全局异常关掉,然后再测试
总结一下:
当我们一次一次的访问只是报错erro,开jmeter后,接受高并发请求,1秒20个都是异常,早就达到我们的熔断条件了,然后页面的返回就是直接的Blocked by Sentinel (flow limiting)
5.3、异常数
异常数 (ERROR_COUNT):
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
8401controller添加新规则
java
//新增熔断规则-异常数
@GetMapping("/testH")
public String testH(){
System.out.println("----测试:新增熔断规则-异常数 ");
int age =1/0;
return "-------testG,新增熔断规则-异常数---------";
}
重启8401,先直接访问一次,绝对一个500error
在sentinel给/testH添加熔断规则
上jmeter还是1秒20个,再访问直接熔断降级。
6、@SentinelResource注解详解
SentinelResource是一个流量防卫防护组件注解,用于指定防护资源,对配置的资源进行流量控制、熔断降级等功能。
@SentinelResource注解说明
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface SentinelResource {
//资源名称
String value() default "";
//entry类型,标记流量的方向,取值IN/OUT,默认是OUT
EntryType entryType() default EntryType.OUT;
//资源分类
int resourceType() default 0;
//处理BlockException的函数名称,函数要求:
//1. 必须是 public
//2.返回类型 参数与原方法一致
//3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置blockHandlerClass ,并指定blockHandlerClass里面的方法。
String blockHandler() default "";
//存放blockHandler的类,对应的处理函数必须static修饰。
Class<?>[] blockHandlerClass() default {};
//用于在抛出异常的时候提供fallback处理逻辑。 fallback函数可以针对所
//有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求:
//1. 返回类型与原方法一致
//2. 参数类型需要和原方法相匹配
//3. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定fallbackClass里面的方法。
String fallback() default "";
//存放fallback的类。对应的处理函数必须static修饰。
String defaultFallback() default "";
//用于通用的 fallback 逻辑。默认fallback函数可以针对所有类型的异常进
//行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求:
//1. 返回类型与原方法一致
//2. 方法参数列表为空,或者有一个 Throwable 类型的参数。
//3. 默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置fallbackClass ,并指定 fallbackClass 里面的方法。
Class<?>[] fallbackClass() default {};
//需要trace的异常
Class<? extends Throwable>[] exceptionsToTrace() default {Throwable.class};
//指定排除忽略掉哪些异常。排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。
Class<? extends Throwable>[] exceptionsToIgnore() default {};
}
6.1、按照rest地址限流+默认限流返回
通过访问的rest地址来限流,会返回Sentinel自带默认的限流处理信息,就是原来的默认的那个Blocked by Sentinel (flow limiting)。
新建一个controller,RateLimitController
java
@RestController
public class RateLimitController {
@GetMapping("/rateLimit/byUrl")
public String byUrl()
{
return "按rest地址限流测试OK";
}
}
启动8401访问一次
到sentinel中添加流控规则
疯狂刷新就能出来了,默认的,前面用的都是
6.2、按SentinelResource资源名称限流+自定义限流返回
我们之前就说不用默认的Blocked by Sentinel (flow limiting),怎么自定义?
新加一个测试
java
//自定义限流提示
@GetMapping("/rateLimit/byResource")
@SentinelResource(value = "byResourceSentinelResource",blockHandler = "diyBlockHandler")
public String byResource(){//这里没有添加参数,如果添加的有参数则diyBlockHandler方法中也要写
return "这里是自定义限流测试,此时是正常访问页面没有问题的!";
}
public String diyBlockHandler(BlockException exception){
return "你太快了大哥,慢一点,服务不可用@SentinelResource被触发了~~~~";
}
重启先访问正常的,localhost:8401/rateLimit/byResource
进sentinel添加限流规则
添加完就多刷新就出来了
6.3、按SentinelResource资源名称限流+自定义限流返回+服务降级处理
按照SentinelResource配置,点击超过限流配置返回自己自定义的限流提示和程序异常返回fallback服务降级。
java
@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正常访问-----";
}
//doActionBlockHandler自定义的限流提醒
public String doActionBlockHandler(@PathVariable("p1") Integer p1,BlockException e){
return "sentinel配置自定义被限流了";
}
//出现异常自定义服务降级
public String doActionFallback(@PathVariable("p1") Integer p1,Throwable throwable){
return "程序逻辑出现异常,这里是自定义的doActionFallback"+throwable.getMessage();
}
重启微服务访问一次试试
再添加流控规则
快速刷新能正常访问的,会被限流。
访问一个错误的带0的请求
图形配置和代码的关系
小结:
- blockHandler,主要是针对sentinel配置后出现的违规情况;
- fallback,程序异常了JVM抛出的异常服务降级;
- 这两者可以同时共存;
7、热点规则
什么是热点?
热点就是你经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的Topk数据,并对其访问进行限流或者其他操作。
比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制(爆款)
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
演示:
新加controller
java
//热点规则
@GetMapping("/testHostKey")
@SentinelResource(value = "testHostKey",blockHandler = "hostKeyHandler")
public String testHostKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
return "------testHostKey正常访问-------"+"p1:"+p1+"p2:"+p2;
}
public String hostKeyHandler(String p1,String p2,BlockException exception){
return "------hostKeyHandler限流提醒---------";
}
重启8401测试,正常访问
添加热点规则
限流模式只支持QPS模式,固定写死了。(这才叫热点)
@SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
现在配置的就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调hostKeyHandlery支持方法。
现在测试p1参数疯狂刷新就会出现限流
但是单写p2就不会限流
那么p1和p2一起写还是会限流,因为只要有p1就会被限制
参数例外项
当前是含有P1参数,超过1秒钟一个后,达到阈值1后马上被限流。但是,我们希望能给某个值开个后门,当参数等于约定的特定值时,放放水,其他的值必须1秒内只能一次,特定值你给个200次。
假如我们把666作为例外的特定值,设置它的阈值为200.
注意:热点的参数必须是基本类型或者String
测试:参数先写其他值疯狂刷新还是被限制。
参数值写666,到1秒干200次才能限流
带上p2也一样
8、授权规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单控制)的功能。来源访问控制根据资源的请求来源(origin
)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
调用方信息通过 ContextUtil.enter(resourceName, origin)
方法中的 origin
参数传入。
演示案例:
新建EmpowerController
java
@RestController
@Slf4j
public class EmpowerController { //Empower授权规则,用来处理请求的来源
@GetMapping(value = "/empower")
public String requestSentinel4(){
log.info("测试Sentinel授权规则empower");
return "Sentinel授权规则";
}
}
新建MyRequestOriginParser
java
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
return httpServletRequest.getParameter("parameter");
}
}
配置黑白名单
测试
9、规则持久化
我们现在配置的规则在微服务重启之后就会消失,生产环境需要将配置规则进行持久化。
我们需要将限流配置规则持久化进Nacos保存,只要刷新8401的某个请求地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。
修改cloudalibaba-sentinel-service8401
添加sentinel数据源
xml
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yml更改添加数据源内容
yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080 #配置sentinel dashboard 控制台服务地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
web-context-unify: false # controller层的方法对service层调用不认为是同一个根链路
datasource:
ds1: #就是自定义的key,也可以叫限流类型
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow # com.alibaba.cloud.sentinel.datasource.RuleType流控规则
rule-type是什么?
nacos添加业务规则配置
json
[
{
"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:是否集群。
添加完成后直接访问一下localhost:8401/rateLimit/byUrl是正常访问,然后把8401关闭看sentinel控制台的规则就没有了。
但是再打开8401正常的访问一下过一会儿会发现就不需要配置规则,原来的规则,和在nacos中配置的json串中的内容一样,自动回来了。
这个时候疯狂刷新限流就出来了。
10、OpenFeign和Sentinel集成实现fallback服务降级
有没有发现我们的fallback服务降级,通过fegin接口调用的那些方法里,每一个方法我们都有对应的服务降级处理,那如果接口特别多,我们这样一个一个的配显然非常非常麻烦。
怎么办?我们可以通过fallback属性进行统一的配置,feign接口里面定义的全部方法都走统一的服务降级,一个搞定即可。
9001微服务自身还带着sentinel内部配置的流控规则,如果满足也会被触发,也即本例有2个Case。
- OpenFeign接口的统一fallback服务降级处理;
- Sentinel访问触发了自定义的限流配置,在注解@SentinelResource里面配置的blockHandler方法;
10.1、案例演示
修改服务提供方cloudalibaba-provider-payment9001
添加opeign和sentinel依赖
xml
<!--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>
yml中添加sentinel配置
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扫描,直至找到未被占用的端口
业务类添加controller
java
@RestController
public class PayAlibabaController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/pay/nacos/{id}")
public String getPayInfo(@PathVariable("id") Integer id)
{
return "nacos 服务注册中心, serverPort: "+ serverPort+"\t id:"+id;
}
@GetMapping("/pay/nacos/get/{orderNo}")
@SentinelResource(value = "getPayByOrderNo",blockHandler = "handlerBlockHandler")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo){
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流控配置规则,限流了!!!");
}
/*
fallback服务降级方法纳入到Feign接口统一处理,全局一个
public ResultData myFallBack(@PathVariable("orderNo") String orderNo,Throwable throwable)
{
return ResultData.fail(ReturnCodeEnum.RC500.getCode(),"异常情况:"+throwable.getMessage());
}
*/
}
启动9001先测试一下
基本上没有问题的。
10.2、对外暴露接口,修改api
添加opeign和sentinel依赖
xml
<!--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>
为了和之前做的区别开来,新建一个接口PayFeignSentinelApi
java
@FeignClient(value = "nacos-payment-provider",fallback = PayFeignSentinelApiFallBack.class)
public interface PayFeignSentinelApi {
@GetMapping("/pay/nacos/get/{orderNo}")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo);
}
这里面的服务降级处理类PayFeignSentinelApiFallBack需要单独建一个,要实现这个接口PayFeignSentinelApi
java
@Component
public class PayFeignSentinelApiFallBack implements PayFeignSentinelApi{
@Override
public ResultData getPayByOrderNo(String orderNo) {
return ResultData.fail(ReturnCodeEnum.RC500.getCode(), "服务不可用,fallback服务降级了");
}
}
这里我们已经更改了公共API接口信息需要重新刷新坐标。
10.3、修改cloudalibaba-consumer-nacos-order83
添加openfeign和sentinel的依赖
xml
<dependency>
<groupId>com.zm.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中激活feign对sentinel的支持
yml
#激活feign对sentinel的支持
feign:
sentinel:
enabled: true
主启动类中启动Feign功能
java
@EnableFeignClients
@SpringBootApplication
@EnableDiscoveryClient
public class Main83 {
public static void main(String[] args) {
SpringApplication.run(Main83.class,args);
}
}
新加controller
java
@Resource
private PayFeignSentinelApi payFeignSentinelApi;
@GetMapping("/pay/nacos/get/{orderNo}")
public ResultData getPayByOrderNo(@PathVariable("orderNo") String orderNo){
return payFeignSentinelApi.getPayByOrderNo(orderNo);
}
9001是启动着的现在启动消费者83
问题不是写错了而是springboot+springcloud版本太高了导致和阿里巴巴Sentinel不兼容。
那就只能暂时的把版本降一下再尝试,我们把父工程的依赖版本降级。
xml
<!-- <spring.boot.version>3.2.0</spring.boot.version>-->
<!-- <spring.cloud.version>2023.0.0</spring.cloud.version>-->
<!--sentinel整合openfeign降低版本使用,案例结束还恢复原版本-->
<spring.boot.version>3.0.9</spring.boot.version>
<spring.cloud.version>2022.0.2</spring.cloud.version>
等待版本更改完成启动83消费者测试,这个时候我没有启动9001所以没有服务提供者,这个时候访问一下试试,绝对服务降级。
再把9001给启动起来,测试一个正常的,这个时候我们没有配置sentinel对其流控。
然后我们再添加流控
疯狂刷新请求,触发限流
测试完成,把springboot+springcloud版本调回去
11、GateWay和Sentinel集成实现服务限流
我们的9001是服务的直接提供者,按照我们原来的思想,服务的直接提供者的上一层需要有网关来路由,现在新建一个网关微服务cloudalibaba-sentinel-gateway9528 。
**新建module,cloudalibaba-sentinel-gateway9528,导入gateway相关依赖 **
xml
<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
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/** # 断言,路径相匹配的进行路由
主启动Main9528
java
@SpringBootApplication
@EnableDiscoveryClient
public class Main9528 {
public static void main(String[] args) {
SpringApplication.run(Main9528.class,args);
}
}
参考官网:网关限流 · alibaba/Sentinel Wiki · GitHub
Sentinel 1.6.0 引入了 Sentinel API Gateway Adapter Common 模块,此模块中包含网关限流的规则和自定义 API 的实体和管理逻辑:
GatewayFlowRule
:网关限流规则,针对 API Gateway 的场景定制的限流规则,可以针对不同 route 或自定义的 API 分组进行限流,支持针对请求中的参数、Header、来源 IP 等进行定制化的限流。ApiDefinition
:用户自定义的 API 定义分组,可以看做是一些 URL 匹配的组合。比如我们可以定义一个 API 叫my_api
,请求 path 模式为/foo/**
和/baz/**
的都归到my_api
这个 API 分组下面。限流的时候可以针对这个自定义的 API 分组维度进行限流。
业务类GatewayConfiguration
java
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
@PostConstruct //javax.annotation.PostConstruct
public void doInit() {
initBlockHandler();
}
//处理自定义返回的例外信息
private void initBlockHandler(){
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("pay_routh1").setCount(2).setIntervalSec(1));
GatewayRuleManager.loadRules(rules);
BlockRequestHandler handler=new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
HashMap<String, String> map = new HashMap<>();
map.put("erroCode", 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
正常访问一下9001的
然后使用网关9528访问
快速刷新触发限流