文章目录
(十九)SpringCloud Alibaba Sentinel实现熔断与限流
1、Sentinel
1.1 官网
英文:https://github.com/alibaba/Sentinel
中文:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
1.2 是什么
1.3 去哪下
官方:https://github.com/alibaba/Sentinel/releases
百度网盘:
sentinel-dashboard-1.7.0.jar:
链接:https://pan.baidu.com/s/1xup3p5JI-dVIFQeWDnYxiA
提取码:0rlf
1.4 能干嘛
1.5 怎么玩
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
服务使用中的各种问题:
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
2、安装Sentinel控制台
2.1 sentinel组件由2部分组成:
- 后台
- 前台8080
2.2 安装步骤
-
下载:下载到本地sentinel-dashboard-1.7.0.jar
-
前提:
- Java8环境OK
- 8080端口不被占用
-
命令:
java -jar sentinel-dashboard-1.7.0.jar
-
访问sentinel管理界面:
- http://localhost:8080
- 登录账号密码均为sentinel
启动Sentinel时报错:
8080端口被占用
Error starting ApplicationContext. To display the conditions report re-run your application with ‘debug’ enabled.
2020-08-27 15:50:48.401 ERROR 15444 — [ main] o.s.b.d.LoggingFailureAnalysisReporter :
APPLICATION FAILED TO START
Description:
The Tomcat connector configured to listen on port 8080 failed to start. The port may already be in use or the connector may be misconfigured.
Action:
Verify the connector’s configuration, identify and stop any process that’s listening on port 8080, or configure this application to listen on another port.
2020-08-27 15:50:48.402 INFO 15444 — [ main] ConfigServletWebServerApplicationContext : Closing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@61baa894: startup date [Thu Aug 27 15:50:42 CST 2020]; root of context hierarchy
2020-08-27 15:50:48.408 INFO 15444 — [ main] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
解决:
Microsoft Windows [版本 10.0.18363.1016]
© 2019 Microsoft Corporation。保留所有权利。C:\Users\17316>netstat -ano | findstr “8080”
TCP 0.0.0.0:8080 0.0.0.0:0 LISTENING 16036
TCP [::]:8080 [::]:0 LISTENING 16036
TCP [::1]:2558 [::1]:8080 TIME_WAIT 0
TCP [::1]:2559 [::1]:8080 TIME_WAIT 0C:\Users\17316>tasklist | findstr “16036”
java.exe 16036 Console 1 364,076 KC:\Users\17316>taskkill /f /t /im java.exe
成功: 已终止 PID 12280 (属于 PID 11280 子进程)的进程。
成功: 已终止 PID 11576 (属于 PID 12944 子进程)的进程。
成功: 已终止 PID 364 (属于 PID 16036 子进程)的进程。
成功: 已终止 PID 12892 (属于 PID 13780 子进程)的进程。
成功: 已终止 PID 11280 (属于 PID 10028 子进程)的进程。
成功: 已终止 PID 12944 (属于 PID 11052 子进程)的进程。
成功: 已终止 PID 15964 (属于 PID 16128 子进程)的进程。
成功: 已终止 PID 16036 (属于 PID 10028 子进程)的进程。
成功: 已终止 PID 13780 (属于 PID 10028 子进程)的进程。
成功: 已终止 PID 15444 (属于 PID 7692 子进程)的进程。C:\Users\17316>
再次启动Sentinel:
3、初始化演示工程
3.1 启动Nacos8848成功
http://localhost:8848/nacos/#/login
3.2 Module
3.2.1 新建model
cloudalibaba-sentinel-service8401
3.2.2 POM
<dependencies>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--sentinel-datasource-nacos:后序做持久化会用到-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--spring-cloud-starter-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3.2.3 YML
application.yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
#Nacos服务注册地址
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8080
port: 8719 #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
management:
endpoints:
web:
exposure:
include: '*'
3.2.4 主启动类
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelServiceMain8401 {
public static void main(String[] args) {
SpringApplication.run(SentinelServiceMain8401.class,args);
}
}
3.2.5 业务类
FlowLimitController.java
package com.atguigu.springcloud.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
//流控
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
}
3.2.6 启动Sentinel8080
java -jar sentinel-dashboard-1.7.0
3.2.7 启动微服务8401
-
启动8401微服务后查看sentienl控制台
-
空空如也
-
Sentinel采用的懒加载说明
-
执行一次访问:
http://localhost:8401/testA
http://localhost:8401/testB
-
效果:
-
结论:
sentinel8080正在监控微服务8401
3.2.8 遇到的错误:
2020-08-27 15:49:02.625 INFO 16036 --- [5)-192.168.47.1] o.s.web.servlet.DispatcherServlet : Completed initialization in 25 ms
INFO: log base dir is: C:\Users\17316\logs\csp\
INFO: log name use pid is: false
2020-08-27 15:49:03.888 ERROR 16036 --- [7)-192.168.47.1] c.a.c.n.discovery.NacosDiscoveryClient : get service name from nacos server fail,
java.lang.IllegalArgumentException: no server available
at com.alibaba.nacos.client.naming.net.NamingProxy.reqAPI(NamingProxy.java:438) ~[nacos-client-1.1.1.jar:na]
查看:是application.yml写错了
4、流控规则
4.1 基本介绍
添加流控规则:
进一步解释说明:
4.2 流控模式
4.2.1 直接(默认)
直接–》就是快速失败(系统默认)
测试:
首先对testA新建一个流控规则,单机阈值为:1,其他的为默认
当间隔一秒访问testA,正常输出:------testA
当快速访问testA(小于1秒),显示:Blocked by Sentinel (flow limiting)
被Sentinel限流
思考:直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?
类似有一个fallback的兜底方法?
4.2.2 关联
1)是什么
-
当关联的资源达到阈值时,就限流自己
-
当与A关联的资源B达到阈值后,就限流自己
-
B惹事,A挂了
2)配置A
3)postman模拟并发密集访问testB
- 访问testB成功
- postman里新建多线程集合组
- 将访问地址添加进新线程组
-
Run
运行:testB
这时再去访问testA,会出现限流
大批量线程高并发访问B,导致A失效了
当testB访问结束,再次访问testA正常
4.2.3 链路
https://blog.csdn.net/ooyhao/article/details/102745914
4.3 流控效果
4.3.1 直接-——》快速失败(默认的流控处理)
- 直接失败,抛异常:Blocked by Sentinel (flow limiting)
- 源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
4.3.2 预热
1)说明
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
2)官网
默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值。
限流 冷启动:https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
3)源码
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
4)Warmup配置
5)多次点击http://localhost:8401/testB
刚开始不行,后续慢慢OK
6)应用场景
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,慢慢的把阀值增长到设置的阀值
4.3.3 排队等待
1)匀速排队,阈值必须设置为QPS
2)官网
3)源码
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
4)测试
编辑FlowLimitController.java
@GetMapping("/testB")
public String testB() {
System.out.println(Thread.currentThread().getName()+"\t"+"....testB");
return "------testB";
}
编辑postman,并启动
查看8401后台:
5、降级规则
5.1 官网
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
5.2 基本介绍
1)进一步说明:
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException),
2)Sentinel的断路器是没有半开状态的
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix
5.3 降级策略实战
5.3.1 RT
1)概念
平均响应时间 (DEGRADE_GRADE_RT
):当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count
,以 ms 为单位),那么在接下的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException
)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx
来配置。
2)测试
代码:
//测试RT
@GetMapping("/testD")
public String testD()
{
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("testD 测试RT");
return "------testD";
}
配置降级规则:
jmeter压测:
测试:
- JMeter压测前测试:
虽然平均响应时间超过200ms,但是并发量只是1,没有超过5,所以没有触发熔断降级。
-
JMeter压测测试:
1秒10个线程,同时访问testD,触发熔断降级
-
开启压测后:
永远一秒钟打进来10个线程(大于5个)调用/testD,我们希望200ms处理完本次任务,如果200ms还没处理完,在之后1s时间窗口内,断路器打开,微服务不可用。
3)总结:
5.3.2 异常比例
1)概念
异常比例( DEGRADE GRADE_ EXCEPTION_ RATIO):
当资源的每秒请求量>= 5
,并且每秒异常总数占通过量的比值
超过阈值( DegradeRule中
的count )之后,资源进入降级状态,即在接下的时间窗口( DegradeRule中timeWindow, 以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0] ,代表0% - 100%。
2)测试
代码:
@GetMapping("/testE")
public String testE()
{
System.out.println("testE 测试异常比例");
//模拟出错:每访问一次就是100%的错
int age = 10/0;
return "------testE";
}
配置:
JMeter:
启动JMeter后访问http://localhost:8401/testE,显示Blocked BySentenel(flow limitung),服务降级了
思考:
异常比例的概念:当资源的
每秒请求量>= 5
,并且每秒异常总数占通过量的比值
超过阈值( DegradeRule中的count )之后,资源进入降级状态,我们通过JMeter模拟,1秒访问的线程数为20个,并且每秒异常总数占通过量的比值为100%
上数条件都满足异常比例的概念,服务将会进入降级状态
停止JMEter后,直接报错:
停止后,不符合【每秒请求量>=5 】的条件,所以会直接报错
结论:
5.3.3 异常数
1)概念
异常数( DEGRADE _GRADE_ EXCEPTION_ COUNT):
当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timevindow 小于60s,则结束熔断状态后仍可能再进入熔断状态。所以时间窗口必须大于或等于60s。
2)异常数是按照分钟统计的
3)测试
代码:
@GetMapping("/testF")
public String testF()
{
System.out.println("testF 测试异常数");
int age = 10/0;
return "------testF";
}
配置:
访问:http://localhost:8401/testE,第一次访问绝对报错,因为除数不能为零,
我们看到error窗口,但是达到5次报错后,进入熔断后降级。
6、热点key限流
6.1 基本介绍
6.2 官网
https://github.com/alibaba/Sentinel/wiki/热点参数限流
6.3 承上启下复习start
兜底方法
分为系统默认和客户自定义两种
之前的case,限流出问题后,都是用sentine系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论:从HystrixCommand
到@SentinelResource
6.4 代码
com.alibaba.csp.sentinel.slots.block.BlockException
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}
//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}
启动8401,测试
http://localhost:8401/testHotKey
http://localhost:8401/testHotKey?p1=aaa
http://localhost:8401/testHotKey?p1=aaa&p2=bbb
都能正常访问
6.5 配置
- 没有配置blockHandler:@SentinelResource(value = “testHotKey”)
- 异常打到了前台用户界面看不到,不友好
- 配置了blockHandler:@SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)
- 方法testHostKey里面第一个参数只要QPS超过每秒1次,马上降级处理
- 用了我们自己定义的
6.6 测试
http://localhost:8401/testHotKey
http://localhost:8401/testHotKey?p1=aaa error
http://localhost:8401/testHotKey?p1=aaa&p2=bbb error
http://localhost:8401/testHotKey?p2=bbb right
只要个p1传递参数的都会限流
6.7 参数例外项
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
6.7.1 特殊情况
普通:超过1秒钟一个后,达到阈值1后马上被限流
我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
特例:假如当p1的值等于5时,它的阈值可以达到200
配置:
添加按钮不能忘
6.7.2 测试:
http://localhost:8401/testHotKey?p1=5
http://localhost:8401/testHotKey?p1=3
当p1等于5的时候,阈值变为200
当p1不等于5的时候,阈值就是平常的1
6.7.3 前提条件
热点参数的注意点,参数必须是基本类型或者String
6.7.4 其他
添加异常看看:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2){
int age = 10/0;
return "------testHotKey";
}
//兜底的方法
public String deal_testHotKey(String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}
7、系统规则
7.1 是什么
https://github.com/alibaba/Sentinel/wiki/系统自适应限流
7.2 各项配置参数说明
7.3 配置全局QPS
8、@SentinelResource
8.1按资源名称限流+后续处理
8.1.1 启动Nacos成功
8.1.2 启动Sentinel成功
8.1.3 添加业务类
package com.atguigu.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class RateLimitController {
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handleException")
public CommonResult byResource(){
return new CommonResult(200,"按资源名称限流测试OK",new Payment(2020L,"serial001"));
}
public CommonResult handleException(BlockException exception){
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t服务不可用");
}
}
8.1.4 配置流控规则
配置步骤:
表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流
多次点击:
测试:
1秒钟点击1下,OK
超过上述问题,疯狂点击,返回了自己定义的限流处理信息,限流发送
8.1.5 额外问题
此时关闭微服务8401看看
Sentinel控制台,流控规则消失了?????
临时/持久? 临时
8.2按照Url地址限流+后续处理
-
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
-
业务类RateLimitController
//按url限流测试OK @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") // 没有兜底的方法,会采用系统默认的 public CommonResult byUrl() { return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002")); }
-
访问http://localhost:8401/rateLimit/byUrl
-
Sentinel控制台配置
-
测试
疯狂点击http://localhost:8401/rateLimit/byUrl
8.3 上面兜底方法面临的问题
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧。
- 全局统一的处理方法没有体现。
8.4 客户自定义限流处理逻辑
-
创建customerBlockHandler类用于自定义限流处理逻辑
-
自定义限流处理类:CustomerBlockHandler.java
package com.atguigu.springcloud.myhandler; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment; //统一处理的兜底方法 public class CustomerBlockHandler { //兜底方法1 public static CommonResult handleException1(){ return new CommonResult(4444,"Customers自定义限流处理信息—--1"); } //兜底方法2 public static CommonResult handleException2(){ return new CommonResult(4444,"Customers自定义限流处理信息—--2"); } }
-
RateLimitController.java
//按照客户自定义 全局 @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException1") public CommonResult customerBlockHandler(){ return new CommonResult(200,"按照客户自定义",new Payment(2020L,"serial002")); }
-
启动微服务后先调用一次
http://localhost:8401/rateLimit/customerBlockHandler
- Sentinel控制台配置
-
再次访问
可能遇到的问题:
- 添加流控规则时按照URL进行流控,当再次以QPS每秒大于1 访问
http://localhost:8401/rateLimit/customerBlockHandler
,会触发系统默认限流 - 当修改流控规则按照资源名进行流控,当再次以QPS每秒大于1 访问,照样还是系统默认的限流方式
- 解决办法:删除按照URL限流的规则
进一步说明:
8.5 更多注解属性说明
Sentinel主要有三个核心API
- SphU定义资源
- Tracer定义统计
- ContextUtil定义了上下文
9、服务熔断功能
sentinel整合ribbon+openFeign+fallback
9.1 Ribbon系列
9.1.1 启动nacos和sentinel
9.1.2 提供者9003/9004
1)新建cloudalibaba-consumer-nacos-order84
2)POM
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3)YML
记得修改端口
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
4)主启动
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003
{
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}
5)业务类
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
public class PaymentController
{
@Value("${server.port}")
private String serverPort;
//模拟数据库
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static{
hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
return result;
}
}
6)测试
http://localhost:9003/paymentSQL/1
http://localhost:9004/paymentSQL/1
都正常
9.1.3 消费者84
1)新建model:cloudalibaba-consumer-nacos-order84
2)POM
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<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.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3)YML
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
service-url:
nacos-user-service: http://nacos-payment-provider
4)主启动类
package com.atguigu.spring;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@SpringBootApplication
@EnableFeignClients
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
5)业务类
- ApplicationContextConfig.java
package com.atguigu.spring.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
- CircleBreakerController.java
package com.atguigu.spring.controller;
...
@RestController
public class CircleBreakerController {
public static final String SERVER_URL = "http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")
public CommonResult<Payment> fallback(@PathVariable("id") Long id){
CommonResult result = restTemplate.getForObject(SERVER_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException ,非法参数异常");
}else if (result.getData() == null){
throw new NullPointerException("NullPointerException ,该ID没有对应的记录,空指针异常");
}
return result;
}
}
6)启动测试
测试1:数据库存在
9003和9004轮询
测试2:数据库中不存在,但对id=4,有处理
测试3:数据库中不存在,id也不是4的其他请求有处理
给客户error页面,不友好
所以修改代码,添加兜底方法
7)@SentinelResource中的fallback与blockHandler
第一种情况:
上面的代码为
第一种情况:没有配置
@SentinelResource(value = “fallback”)
第二种情况:只配置fallback
第二种情况:只配置fallback
@SentinelResource(value = “fallback” , fallback = “handlerFallback”)//fallback :只负责业务异常
@GetMapping("/consumer/fallback/{id}")
//第二种情况:
@SentinelResource(value = "fallback" , fallback = "handlerFallback")//fallback :只负责业务异常
public CommonResult<Payment> fallback(@PathVariable("id") Long id){
CommonResult result = restTemplate.getForObject(SERVER_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException ,非法参数异常");
}else if (result.getData() == null){
throw new NullPointerException("NullPointerException ,该ID没有对应的记录,空指针异常");
}
return result;
}
//handlerFallback
public CommonResult handlerFallback(@PathVariable Long id,Throwable e){
Payment payment = new Payment(id, "null");
return new CommonResult(444,"兜底的异常handlerFallback,exception异常的内容:"+e.getMessage(),payment);
}
重启消费者84:
测试:http://localhost:84/consumer/fallback/4
http://localhost:84/consumer/fallback/5
第三种情况:只配置blockHandler
//第三种情况:
@SentinelResource(value = "fallback" ,blockHandler = "blockHandler")//解决sentinel配置异常
。。。。。。。。。。。。。。。。。。。。。
//blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
配置sentinel:
测试:
点击一次:localhost:84/consumer/fallback/4 出现非法参数异常
连续点击:
所以当连续点击触发了sentinel配置异常,会调用blockHandler中兜底方法
第四种情况:fallback和blockHandler都配置
//第四种情况:
@SentinelResource(value = "fallback" ,fallback = "handlerFallback" ,blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable("id") Long id){
CommonResult result = restTemplate.getForObject(SERVER_URL + "/paymentSQL/" + id, CommonResult.class, id);
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException ,非法参数异常");
}else if (result.getData() == null){
throw new NullPointerException("NullPointerException ,该ID没有对应的记录,空指针异常");
}
return result;
}
//fallback
public CommonResult handlerFallback(@PathVariable Long id,Throwable e){
Payment payment = new Payment(id, "null");
return new CommonResult(444,"兜底的异常handlerFallback,exception异常的内容:"+e.getMessage(),payment);
}
//blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
重启84,然后在sentinel添加流控规则:
访问1:
-
http://localhost:84/consumer/fallback/1 点击一次
-
http://localhost:84/consumer/fallback/1 连续点击多次
访问2:
-
http://localhost:84/consumer/fallback/4 点击一次
-
http://localhost:84/consumer/fallback/4 连续点击多次
总结:若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException 时只会进入blockHandler处理逻辑。
8)忽略属性…
exceptionsToIgnore
9.2 Feign系列
9.2.1 修改84模块
9.2.2 POM
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
9.2.3 YML
#对Feign的支持
feign:
sentinel:
enabled: true
9.2.4 业务类
带@FeignClient注解的业务接口
package com.atguigu.spring.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
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 = PaymentFallbackService.class)
public interface PaymentService
{
//84调用9003
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
PaymentFallbackService实现类
package com.atguigu.spring.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
}
Controller
//=====================openFeign==================
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
return paymentService.paymentSQL(id);
}
9.2.5 主启动
添加@EnableFeignClients启动Feign的功能
9.2.6 测试
http://localhost:84/consumer/paymentSQL/1
测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死
9.3 熔断框架比较
10、规则持久化
10.1 是什么
一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化
10.2 怎么玩
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效
10.3 步骤
修改cloudalibaba-sentinel-service8401
POM
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
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
# 添加Nacos数据源配置
datasource:
#数据源1
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow #流控规则
management:
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true # 激活Sentinel对Feign的支持
添加Nacos业务规则配置
内容解析:
[
{
"resource": "/retaLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
启动8401后刷新sentinel发现业务规则有了
快速访问测试接口:http://localhost:8401/rataLimit/byUrl
停止8401再看sentinel
重新启动8401再看sentinel:
等一会之后,再查看
再次访问:http://localhost:8401/rataLimit/byUrl
重新配置出现了,持久化验证通过
查看配置存在了那:存在了数据库中