目录
一、Sentinel介绍
1、是什么
2、Hystrix与Sentinel比较
二、Sentinel下载安装运行
安装步骤
三、初始化演示工程
新建 cloudalibaba-sentinel-service8401
1)pom.xml
2)application.yml
3)主启动类
4)controller层
5)测试
四、流控规则
1、基本介绍
1)解释说明
2)QPS和线程数的区别
3)重要属性
2、流控模式
1)直接(默认)
2)关联
3)链路
3、流控效果
1)快速失败(默认的流控处理)
2)预热
3)WarmUp配置
4)排队等待
五、降级规则
1、熔断降级概述
2、降级策略介绍
3、RT
1)介绍
2)实操
4、异常比例
1)介绍
2)实操
5、异常数
1)介绍
2)实战
六、热点key限流
1、基本介绍
2、实操
3、参数例外项
七、系统规则
1、各项配置参数说明
2、@SentinelResource配置
1)按资源名称限流+后续处理
2)按照Url地址限流+后续处理
3)上面兜底方案面临的问题
4)客户自定义限流处理逻辑
5)更多注解属性说明
3、服务熔断功能
1)Ribbon系列——提供者9003/9004
2)Ribbon系列——消费者84
①无配置
②只配置fallback
③ 只配置blockHandler
④fallback和blockHandler都配置
忽略异常
4、全局降级
5、持久化
官方Github
官方文档
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
Sentinel 的主要特性:
—句话解释,之前我们讲解过的Hystrix。
Hystrix:
Sentinel:
约定 > 配置 > 编码
都可以写在代码里面,但是我们本次还是大规模的学习使用配置和注解的方式,尽量少写代码
官方文档
服务使用中的各种问题:
Sentinel 分为两个部分:
1、下载
https://github.com/alibaba/Sentinel/releases
2、运行命令
前提:java8环境OK + 8080端口不能被占用
运行命令:
java -jar sentinel-dashboard-1.7.1.jar
由于我的8080端口被占用,我换成了8888端口
java -jar sentinel-dashboard-1.7.1.jar --server.port=8888
3、访问sentinel管理界面
http://localhost:8888,登录账号密码均为sentinel
启动Nacos8848成功
cloud2020
springcloud
1.0-SNAPSHOT
4.0.0
cloudalibaba-sentinel-service8401
com.alibaba.csp
sentinel-datasource-nacos
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
springcloud
cloud-api-commons
${project.version}
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
# 服务注册中心 # sentinel注册进nacos
server-addr: localhost:8848
sentinel:
transport:
# 配置 Sentinel Dashboard 的地址
dashboard: localhost:8888
# 默认8719 ,如果端口被占用,端口号会自动 +1,直到找到未被占用的端口,提供给 sentinel 的监控端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class,args);
}
}
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
log.info(Thread.currentThread().getName() + "\t" + "...testB");
return "------testB";
}
}
空空如也,啥都没有
Sentinel采用的懒加载说明:
执行一次访问即可:http://localhost:8401/testA、http://localhost:8401/testB
结论:sentinel8080正在监控微服务8401
Field | 说明 | 默认值 |
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
直接->快速失败:系统默认
配置及说明:表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
测试:快速点击访问:http://localhost:8401/testA
思考
直接调用默认报错信息,技术方面OK,但是否应该有我们自己的后续处理?类似有个fallback的兜底方法?
设置testA
当关联资源/testB的qps阀值超过1时,就限流/testA的Rest访问地址,当关联资源到阈值后限制配置好的资源名。
测试
访问testB成功
postman里新建多线程集合组:
Run:
20个线程,每隔0.3s访问一次
运行后发现testA挂了,点击访问:http://localhost:8401/testA
只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【API级别的针对来源】
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。详细文档可以参考 流量控制 - Warm Up 文档,具体的例子可以参见 WarmUpFlowDemo
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
默认 coldFactor 为 3,即请求QPS从(threshold / 3) 开始,经多少预热时长才逐渐升至设定的 QPS 阈值
系统初始化的阀值为10 / 3 约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10。效果为:开始访问 localhost:/testA 时每秒请求别超过10/3个才能正常访问,5秒后可以接受的请求可以达到每秒10次。
应用场景
秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒
说明:匀速排队,阈值必须设置为QPS
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考流量控制 - 匀速器模式 ,具体的例子可以参见 PaceFlowDemo。
该方式的作用如下图所示:
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
RT(平均响应时间,秒级)
异常比列(秒级)
异常数(分钟级)
进一步说明:
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)
Sentinel 的断路器是没有半开状态的。(Sentinei 1.8.0 已有半开状态)
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用;有异常则继续打开断路器不可用。具体可以参考Hystrix
平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值( count,以ms为单位),那么在接下的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException )。注意Sentinel 默认统计的RT上限是4900 ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。
注意:Sentinel 1.7.0才有平均响应时间(DEGRADE_GRADE_RT
),Sentinel 1.8.0的没有这项,取而代之的是慢调用比例 (SLOW_REQUEST_RATIO
)。
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。熔断降级 · alibaba/Sentinel Wiki · GitHub
接下来讲解Sentinel 1.7.0的。
在controller中加入textD
@GetMapping("/testD")
public String testD(){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("testD 测试RT");
return "------testD";
}
配置:
jmeter压测:永远一秒钟打进来10个线程(大于5个了)调用testD
压测时访问 /testD
停止压测后:
结论:
按照上述配置,永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了,后续停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复正常。
异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值( DegradeRule中的 count)之后,资源进入降级状态,即在接下的时间窗口( DegradeRule 中的 timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0],代表0% -100%。
注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。熔断降级 · alibaba/Sentinel Wiki · GitHub
接下来讲解Sentinel 1.7.0的。
在controller中加入textE
@GetMapping("/testE")
public String testE(){
log.info("testE 测试异常数");
int a = 10/0;
return "------testE";
}
配置
单独一次访问:http://localhost:8401/testE
jmeter压测后:
结论
按照上述配置,单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了
异常数(
DEGRADE_GRADF_EXCEPTION_COUNT
):当资源近1分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow
小于60s,则结束熔断状态后可能再进入熔断状态。说明:假设时间窗口期为10秒,设置的异常数不能超过5,统计阶段第1秒的时候有5个异常,服务熔断后进入时间窗口期,时间窗口期结束,但是一分钟还没结束,那么此时的异常数还是5个,又进入熔断状态。
注意,与Sentinel 1.8.0相比,有些不同(Sentinel 1.8.0才有的半开状态),Sentinel 1.8.0的如下:
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
接下来讲解Sentinel 1.7.0的。
异常数是按照分钟统计的,时间窗口一定要大于等于60秒。
配置:
测试:
一到五次访问会报错,第六次访问后会服务降级,直到时间窗口期结束,服务才开始报错
何为热点:热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的 Top N 数据,并对其访问进行限流或者其它操作:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。热点参数限流 · alibaba/Sentinel Wiki · GitHub
承上启下复习
兜底方法,分为 系统默认 和 客户自定义 两种。
之前的案例,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel (flow limiting)
我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论 - 从HystrixCommand到@SentinelResource
在controller中加入:
@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){
return "------testHotKty";
}
public String deal_testHotKey(String p1, String p2, BlockException exception){
return "------testHotKty,o(T~~T)o";
}
说明:
配置:
上面的抓图表示第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用deal_testHotKey支持方法。
测试:
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
特例情况:我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
特例:假如当p1的值等于5时,它的阈值可以达到200
热点参数的注意点,参数必须是基本类型或者String
测试:
说明:
pom文件增加
com.atguigu.springcloud
cloud-api-commons
${project.version}
新增controller
@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 服务不可用");
}
}
配置流控规则
簇点链路:byResource 是资源
表示1秒钟内查询次数大于1,就跑到我们自定义的兜底方法
测试
额外问题
此时关闭服务8401看看:Sentinel控制台,流控规则消失了?????临时/持久?
通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息
RateLimitController新增
@GetMapping("/rateLimit/byUrl")
public CommonResult byUrl(){
return new CommonResult(200,"按url限流测试",new Payment(2020L,"serial002"));
}
簇点链路:byUrl 是资源
测试
系统默认的,没有体现我们自己的业务要求
依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观
每个业务方法都添加一个兜底的,那代码膨胀加剧
全局统一的处理方法没有体现
创建 CustomerBlockHandler 类用于自定义限流处理逻辑
public class CustomerBlockHandler {
public static CommonResult handlerException1(BlockException exception){
return new CommonResult(4444,"按用户自定义,global handlerException.......1");
}
public static CommonResult handlerException2(BlockException exception){
return new CommonResult(4444,"按用户自定义,global handlerException.......2");
}
}
controller新增
找CustomerBlockHandler类里的handleException2方法进行兜底处理
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException2")
public CommonResult customerBlockHandler(){
return new CommonResult(200,"按用户自定义",new Payment(2020L,"serial003"));
}
测试:
对资源名称流控
对URL地址流控
在设置了 URL地址流控的前提下,设置按资源名称流控无效
URL地址流控优先级大于资源名称流控
@SentinelResource注解最主要的两个用法:限流控制和熔断降级的具体使用案例介绍完了。另外,该注解还有一些其他更精细化的配置,比如忽略某些异常的配置、默认降级函数等等,具体可见如下说明:
sentinel整合ribbon+openFeign+fallback
启动nacos和sentinel,新建cloudalibaba-provider-payment9003/9004两个一样的做法
pom.xml
cloud2020
com.atguigu.springcloud
1.0-SNAPSHOT
4.0.0
cloudalibaba-provider-payment9003
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.atguigu.springcloud
cloud-api-commons
${project.version}
application.yml
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848
management:
endpoints:
web:
exposure:
include: '*'
controller层
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
//模拟sql查询
public static HashMap hashMap = new HashMap<>();
static {
hashMap.put(1L, new Payment(1L, "xcxcxcxcxcxcxcxcxcxcxcxcxc11111111"));
hashMap.put(2L, new Payment(2L, "xcxcxcxcggggggggg2222222222222222"));
hashMap.put(3L, new Payment(3L, "xcxcxcxccxxcxcfafdgdgdsgdsgds33333"));
}
@GetMapping("/payment/get/{id}")
public CommonResult paymentSql(@PathVariable("id")Long id){
Payment payment = hashMap.get(id);
CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
return result;
}
}
新建cloudalibaba-consumer-nacos-order84
pom.xml
cloud2020
springcloud
1.0-SNAPSHOT
4.0.0
cloudalibaba-consumer-nacos-order84
com.alibaba.csp
sentinel-datasource-nacos
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.mybatis.spring.boot
mybatis-spring-boot-starter
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
com.atguigu.springcloud
cloud-api-commons
${project.version}
application.yml
由于我的8080端口被占用,我的sentinel的端口改成了8888
java -jar sentinel.jar --server.port=8888
server:
port: 84
spring:
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
#配置Sentinel dashboard地址
dashboard: localhost:8888
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
application:
name: nacos-order-consumer
主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class,args);
}
}
配置类
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
controller层
fallback管运行异常、blockHandler管配置违规
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback")//没有配置
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
}
测试:
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback")//没有配置
@SentinelResource(value = "fallback", fallback = "handleFallback") //fallback只处理业务异常
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
//兜底方法
public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
Payment payment = new Payment(id, null);
return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
}
}
测试:
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler")
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
Payment payment = new Payment(id, "null");
return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
}
}
本例sentinel需配置:
簇点链路:
服务降级:
异常超过2次后,断路器打开
测试
第一次和第二次访问还是报异常:
第三次访问
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler",fallback = "handleFallback")
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
//兜底方法
public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
Payment payment = new Payment(id, null);
return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
}
public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
Payment payment = new Payment(id, "null");
return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
}
}
本例sentinel需配置:
簇点链路:
服务降级:
异常超过2次后,断路器打开
测试
第一次和第二次访问:
第三次访问
结论:
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑
@RestController
@Slf4j
public class CircleBreakerController {
private static final String PAYMENT_URL="http://nacos-payment-provider";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler",fallback = "handleFallback",
exceptionsToIgnore = {IllegalArgumentException.class})
//exceptionsToIgnore有这个异常不走兜底方法
public CommonResult fallback(@PathVariable("id")Long id){
if(id == 4){
throw new IllegalArgumentException("IllegalArgumentException...非法参数异常");
}else if(id > 4){
throw new NullPointerException("NullPointerException...空指针异常");
}
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
//兜底方法
public CommonResult handleFallback(@PathVariable("id")Long id, Throwable e){
Payment payment = new Payment(id, null);
return new CommonResult(444, "---兜底异常handlerFallback,exception内容:"+e.getMessage(),payment);
}
public CommonResult blockHandler(@PathVariable("id")Long id, BlockException e){
Payment payment = new Payment(id, "null");
return new CommonResult(445, "blockHandler-sentinel限流,blockException:"+e.getMessage(),payment);
}
}
测试:
上面是单个进行 fallback 和 blockhandler 的测试,下面是整合 openfeign 实现把降级方法解耦。和Hystrix 几乎一摸一样!
添加open-feign依赖:
org.springframework.cloud
spring-cloud-starter-openfeign
yml 追加如下配置
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
主启动类添加注解 : @EnableFeignClients 激活open-feign
service:
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult paymentSql(@PathVariable("id")Long id);
//方法名也要和provider-payment9003/9004里的一样
}
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult paymentSql(Long id) {
return new CommonResult(44444,"服务降级返回,---PaymentFallbackService");
}
}
controllert添加:
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult paymentSQL(@PathVariable Long id) {
return paymentService.paymentSql(id);
}
测试:
为了模拟异常,在远程调用方添加延时(open-feign默认延时大于1秒才会回调),如下。
测试时发现如果只给一个微服务加延时,另一个不加,会一直调用没有延时的微服务,所以要么两个都加延时或者停掉一个。
@GetMapping("/payment/get/{id}")
public CommonResult paymentSql(@PathVariable("id")Long id) throws InterruptedException {
Payment payment = hashMap.get(id);
CommonResult result = new CommonResult(200, "from mysql, server port : " + serverPort + " ,查询成功", payment);
TimeUnit.SECONDS.sleep(6);
return result;
}
目前的sentinel 当重启以后,数据都会丢失,和 nacos 类似原理。需要持久化。它可以被持久化到 nacos 的数据库中。
pom文件
com.alibaba.csp
sentinel-datasource-nacos
application.yml
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
group: DEFAULT_GROUP
data-type: json
rule-type: flow
去nacos上创建一个dataid ,名字和yml配置的一致,json格式,内容如下:
[
{
"resource": "fallback",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
启动应用,发现存在 关于资源 "fallback" 的流控规则.
总结: 就是在 sentinel 启动的时候,去 nacos 上读取相关规则配置信息,实际上它规则的持久化,就是第三步,粘贴到nacos上保存下来,就算以后在 sentinel 上面修改了,重启应用以后也是无效。