该文章都是在spring cloud alibaba 的基础上运用的
如果您的应用使用了 Maven,则在 pom.xml 文件中加入以下代码即可:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-coreartifactId>
dependency>
完成后,项目即可被sentinel服务端所监控
spring cloud 项目所依赖内容如下:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-coreartifactId>
dependency>
dependencies>
application.yml文件内容:
server:
port: 8401 #服务端口
spring:
application:
name: cloudalibaba-sentinel-service #服务名称
cloud:
nacos:
discovery:
server-addr: localhost:8848 # nacos服务地址
sentinel:
transport:
# 默认8719端口,键入被占用会自动从8719+1,直到找到未被占用的端口
port: 8719
# 配置Sentinel dashboard地址
dashboard: localhost:8080
management:
endpoints:
web:
exposure:
include: '*'
创建DataController为外部提供访问,内容如下
@RestController
public class FlowLimitController {
@GetMapping("flowLimit1")
public String flowLimit1(){
return "-----flowLimit1";
}
@GetMapping("flowLimit2")
public String flowLimit2(){
return "-----flowLimit2";
}
}
启动后,先直接访问flowLimit1 和flowLimit2 两个接口,
添加有两种方式,可以直接在流控规则选项中添加,也可以在簇点链路中添加,一般会采取第二种方式
这里的意思就是我们现在单机阈值设定为1,代表的是当前这个接口只能被1秒访问一次,超过这个阈值,就会被Sentinel阻塞,现在默认为直接失败,也就是会在前台有一个体现
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
public class FlowLimitController {
@GetMapping("/flowLimit3")
public String flowLimit3(){
//暂停5秒
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "-----flowLimit3";
}
}
注意:这个时候虽然效果一致,但是是两种完全不同的规则,一种是QPS,一种是并发线程,这点大家一定要分清!
官方解释: 当关联的资源达到阈值时, 就限制自己
同属的来说,比如我们的程序,现在有flowLimit1和flowLimit2 接口,当1关联的资源2达到阈值时,就限制自己,也就是2达到阈值,限制1本身。就好像我们家的孩子在外面打架,家长来处理一样,换做电商系统中,支付系统达到阀值,就限流下单系统。
当关联资源**/flowLimit1的qps阈值超时1时,就限流/flowLimit2**的Rest访问地址,当关联资源到阈值后限制配置好的资源名,首先我们先把FlowLimitController接口恢复原样
@RestController
public class FlowLimitController {
@GetMapping("/flowLimit1")
public String flowLimit1(){
return "-----flowLimit1";
}
@GetMapping("/flowLimit2")
public String flowLimit2(){
return "-----flowLimit2";
}
}
启动后进行访问flowLimit1接口则会返回“Blocked by Sentinel (flow limiting)”字样的信息
链路流控模式指的是,当从一个借口过来的资源达到限流条件时,开启限流,它的功能有点类似于针对来源配置项,区别在于:针对来源是对上一级微服务,而链路流控是针对上级借口,也就是说它的颗粒度更细。
比如在一个微服务中,两个接口都调用同一个Service中的方法,并且该方法用SentinelResource(用于定义资源) 注解标注了,然后对该注解的资源(方法)进行配置,则可以选择链路模式。
首先编写一个Service
首先我们编写一个Service
//service.TestService
@Service
public class TestService {
// 定义限流资源
@SentinelResource("common")
public String common(){
return "common";
}
}
然后更改接口调用这个Service方法
@RestController
public class FlowLimitController {
@Autowired
TestService testService;
@GetMapping("/flowLimit1")
public String flowLimit1(){
return testService.common();
}
@GetMapping("/flowLimit2")
public String flowLimit2(){
return testService.common();
}
}
接下来配置流控规则:
这里要注意不要对/flowLimit1或者/flowLimit2进行限流规则的配置,要给用SentinelResource注解标注的资源进行配置限流规则,这里的意思为当我们用入口资源访问被SentinelResource注解标注的资源方法时,当超过阈值就会被限流,但是此时实际效果是没有效果。
没有效果的原因是因为我们还需要添加配置,让Sentinel 源码中 CommonFilter 中的 WEB_CONTEXT_UNIFY 参数为 false,将其配置为 false 即可根据不同的URL 进行链路限流,如果不配置将不会生效。
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
# 配置Sentinel dashboard地址
dashboard: localhost:8080
# 默认8719端口,键入被占用会自动从8719+1,直到找到未被占用的端口
port: 8719
# 配置为false
web-context-unify: false
最后这个时候我们再来频繁的访问/flowLimit2接口,就会出现异常的情况,这也是流量效果快速失败在链路上的体现,是直接抛出异常。
官网手册地址:https://sentinelguard.io/zh-cn/docs/flow-control.html
概念:Warm Up方式,即预热/冷启动方式。该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。
预热公式:阈值/coldFactor(默认值为3),经过预热时间后才会达到阈值。
冷启动的过程系统允许通过的QPS曲线如下图:
简单理解:
使用场景:一般秒杀系统中会有这样的流控设置,为了防止秒杀瞬间造成系统崩溃。
案例
默认coldFactor为3,当发起请求即请求QPS从(阈值/3)开始,经过多长预热时长才逐步升至设定的QPS阈值,当前阈值设置为10,预热时长设置为5秒。
最终的效果,系统初始化时阈值/3约等于3,即阈值在此时为3,经过5秒后阈值才慢慢升高到10
首先我们先来设置流控效果:
测试,我们用最简单的方法进行测试,直接在浏览器上手动刷新,然后我们来看Sentinel的实时监控
官方文档:https://sentinelguard.io/zh-cn/docs/flow-control.html
概念:匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求(削峰填谷)。
例图:
它的中心思想是,以固定的间隔时间让请求通过。当请求到来的时候,如果当前请求距离上个通过的请求通过的时间间隔不小于预设值,则让当前请求通过。否则,计算当前请求的预期通过时间,如果该请求的预期通过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来通过(排队等待处理);若预期的通过时间超出最大排队时长,则直接拒接这个请求。
Sentinel 匀速排队等待策略是漏桶算法结合虚拟队列等待机制实现的。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
流控规则:
为了看到效果,我们在代码中进行打印,更改8401微服务中的FlowLimitController
package com.example.cloudalibabasentinel8401.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.example.cloudalibabasentinel8401.service.TestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
public class FlowLimitController {
@Autowired
TestService testService;
@GetMapping("flowLimit1")
public String flowLimit1(){
log.info(Thread.currentThread().getName()+":testA");
return testService.common();
}
@GetMapping("flowLimit2")
public String flowLimit2(){
return testService.common();
}
}
最后我们可以通过Postman来进行测试,发送请求时没有延迟,同时发送10条请求,然后我们会发现就是排队效果1秒执行一个请求,同时我们在Idea中也可以看到打桩效果
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
Sentinel 提供了一下几种熔断策略:
慢调用比例 (SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
Sentinel在1.8.0版本对熔断降级做了大的调整,可以定义任意时长的熔断时间,引入了半开启恢复支持。下面梳理下相关特性。
熔断状态有三种状态,非别为OPEN、HALF_OPEN、CLOSED
状态 | 说明 |
---|---|
OPEN | 表示熔断开启,拒绝所有请求 |
HALF_OPEN | 探测恢复状态,如果接下来的一个请求顺利通过则表示结束熔断,否则继续熔断 |
CLOSE | 表示熔断关闭,请求顺利通过 |
熔断降级规则包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
官方文档网址:https://sentinelguard.io/zh-cn/docs/circuit-breaking.html
概念:选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
换而言之: 统计时长内线程数>最小请求数
且 每个线程响应时间>最大RT的线程数
且 占比 需要>比例阀值 才会出发熔断
,且时间为熔断时长的值
。
简单理解:
首先我们先添加一个控制器方法:
//FlowLimitController.java
@GetMapping("flowLimit3")
public String flowLimit3(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "----flowLimit3";
}
设置熔断策略,1QPS>5 并且这些请求的RT>300 并且大于比例阈值触发熔断
测试
通过JMeter测试,1秒钟发起10个线程请求/testC,此时就会触发熔断效果,停止测试以后,10秒钟以后恢复正常
概念:异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
换而言之: 统计时长内线程数>最小请求数
且 线程发生错误的占比 > 比例阀值
则会出发熔断机制,且时间为熔断时长的值
。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。
案例
编写测试接口
//FlowLimitController
@GetMapping("/flowLimit4")
public String flowLimit4(Integer id){
if(id != null && id > 1){
throw new RuntimeException("异常比例测试");
}
return "------------flowLimit4";
}
设置熔断策略异常比例
测试
我们通过JMeter来测试,设定HTTP请求地址
1秒钟发送10个请求
当启动JMeter的时候,就会触发熔断,因为此时我们1秒钟发送10个请求超过了最小请求数5,同时超过了阈值,满足了两个条件,当熔断时长过后就会恢复正常。
概念:异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。
编写接口
//FlowLimitController
@GetMapping("/flowLimit4")
public String flowLimit4(Integer id){
if(id != null && id > 1){
throw new RuntimeException("异常比例测试");
}
return "------------flowLimit4";
}
设置异常数策略,当1秒钟内请求超过5并且异常数大约5个的时候触发熔断
测试
1秒钟发送10个请求
概念
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效
官网:https://sentinelguard.io/zh-cn/docs/parameter-flow-control.html
这里还有相对应的高级选项,我们这里先了解基本规则。
其实这个热点限流其实就是更加细粒度的流控规则,那么如果想使用它就必须要配合对应SentinelResource注解。
Sentinel 提供了 @SentinelResource 注解用于定义资源,它有很多的参数,我们这里主要关注两个参数:
案例讲解
@SentinelResource(value=“xxx”)
那现在我们要完成以上图中的效果,这个时候我们首先要编写代码,在FlowLimitController中编写代码
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "handler_HotKey")
public String testHotKey(@RequestParam(value = "hot1",required = false) String hot1,
@RequestParam(value = "hot2",required = false) String hot2,
@RequestParam(value = "hot3",required = false) String hot3){
if("6".equals(hot1)){
throw new RuntimeException("运行时异常");
}
return "----testHotKey";
}
然后再来配置热点规则
这里要说明一下,参数索引0实际上代表的就是我们设置的hot1参数
测试,此时如果我们传入参数hot1,并且超过阈值,就会出现限流,但是此时的限流效果为报错,显示不友好
@SentinelResource(value=“xxx”,blockHandler=“xxx”)
刚才的演示中,我们明显发现这种限流方法的提示效果非常不友好,所以如果我们需要能够得到友好的提示,我们就需要使用@SentinelResource注解提供的另外一个参数blockHandler,这个参数是可以指定当出现异常时的处理方法,具体操作如下:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "handler_HotKey")
public String testHotKey(@RequestParam(value = "hot1",required = false) String hot1,
@RequestParam(value = "hot2",required = false)String hot2,
@RequestParam(value = "hot13",required = false) String hot3){
return "----testHotKey";
}
//处理异常方法,方法签名要和对应的接口方法保持一致
public String handler_HotKey(String hot1, String hot2,String hot3,BlockException exception){
return "系统繁忙稍后重试。。";
}
然后热点规则不变,我们最终的到的限流效果如下:
参数例外项
其实参数例外项就是可以达到更加细粒度的控制,比如我们当前的例子中,目前hot1参数在访问时超过阈值就会被限流,但是我们可以通过参数例外项设置hot1具体等于特殊的某个值的时候,触发不同的限流效果。假如hot1的值等于5时,它的阈值可以达到200。
**注意:**参数例外项中的参数类型仅支持一下7种数据类型
案例演示
当前我们需要让hot1的值为5的时候阈值可以达到200,首先Sentinel页面中修改对应热点规则(在这之前,先演示传递一个参数,否则配置失败)
此时的规则为:如果当前hot1值为除5以外的其他值,都会走普通的阈值规则,但是如果一旦hot1的值为5的时候,将会走参数例外项,此时的阈值为200,我们通过浏览器测试,当hot1的值等于5是只要阈值不超过200就不会出现限流。
注意:题我们到现在代码中使用了@SentinelResource注解,此注解处理的是Sentinel控制台配置的异常,通过blockHandler属性设置对应方法来处理和程序本身异常无关。
所以以下程序中如果hot1的值等于6还是会出现RuntimeException。
@SentinelResource(value = "testHotKey",blockHandler = "handler_HotKey")
public String testHotKey(@RequestParam(value = "hot1",required = false) String hot1,
@RequestParam(value = "hot2",required = false) String hot2,
@RequestParam(value = "hot3",required = false) String hot3){
if("6".equals(hot1)){
throw new RuntimeException("运行时异常");
}
return "-----testHotKey";
}
**概念:**fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
Throwable
类型的参数用于接收对应的异常。fallbackClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析。其实通过官网上提供的概念,我们不难看出这个属性类似于blockHandler,但是各位一定要注意他们有本质的不同。
**注意:**fallback属性和blockHandler属性的本质不同在于他们作用的异常不同:
案例演示
上节课我们已经完成环境的搭建,那我们就直接在8084项目的DemoController中编写对应代码
首先我们先来设置异常规则
@RestController
@Slf4j
public class DemoController {
//服务提供者URL
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
public JsonResult<String> fallback(@PathVariable Long id){
if(id<=3){
//通过Ribbon发起远程访问,访问9003/9004
JsonResult<String> result = restTemplate.getForObject(SERVICE_URL+"/info/"+id,JsonResult.class);
System.err.println(result.getData());
return result;
}else{
throw new NullPointerException("没有对应的数据记录");
}
}
}
此时我们任务添加了异常,此时如果我们访问http://localhost:8084/consumer/fallback/4(id非法)地址时,就会出现对应的显示效果:
明显此时显示效果非常不好,我们就可以通过@SentinelResource注解的fallback属性来解决这种java异常,给出友好提示
@RestController
@Slf4j
public class DemoController {
//服务提供者URL
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
//添加SentinelResource注解的fallback属性,同时设置方法来解决Java异常
@SentinelResource(value = "falllback",fallback = "fallbackHandler")
public JsonResult<String> fallback(@PathVariable Long id){
if(id<=3){
//通过Ribbon发起远程访问,访问9003/9004
JsonResult<String> result = restTemplate.getForObject(SERVICE_URL+"/info/"+id,JsonResult.class);
System.err.println(result.getData());
return result;
}else{
throw new NullPointerException("没有对应的数据记录");
}
}
//保证方法签名基本保持一致,但是要添加异常类型参数
public JsonResult<String> fallbackHandler(Long id,Throwable e){
JsonResult<String> result = new JsonResult<>(444,"出现未知商品id");
return result;
}
}
到这里为止,我们就很清楚的知道了fallback属性的作用,同时它和blockHandler属性类似,也可以设置fallbackClass属性,来指定对应类型,来处理对应的Java异常,当然要注意和blockHandlerClass属性一样,也需要让所有的方法都必需为 static 函数,否则无法解析。
通过上述的内容,我们很清楚的知道了fallback属性的作用,但是大家现在想一个问题,如果我们在使用@SentinelResource属性的时候,同时设置blockHandler属性和fallback属性时,并且同时出现了Sentinel异常和Java异常,这个时候会执行哪个方法那。
我们还是回顾一下blockHandler属性的概念:
blockHandler
/ blockHandlerClass
: blockHandler
对应处理 BlockException
的函数名称,可选项。blockHandler 函数访问范围需要是 public
,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException
。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,否则无法解析。案例演示
我们现在同时在DemoController中设置fallback属性和blockHandler属性
@RestController
@Slf4j
public class DemoController {
//服务提供者URL
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
//同时添加SentinelResource注解的fallback和blockHandler属性
@SentinelResource(value = "falllback",fallback = "fallbackHandler",blockHandler = "blockHandler")
public JsonResult<String> fallback(@PathVariable Long id){
if(id<=3){
//通过Ribbon发起远程访问,访问9003/9004
JsonResult<String> result = restTemplate.getForObject(SERVICE_URL+"/info/"+id,JsonResult.class);
System.err.println(result.getData());
return result;
}else{
throw new NullPointerException("没有对应的数据记录");
}
}
//处理Java异常
public JsonResult<String> fallbackHandler(Long id,Throwable e){
JsonResult<String> result = new JsonResult<>(444,"NullPointerException异常");
return result;
}
//处理Sentinel限流
public JsonResult<String> blockHandler(Long id, BlockException e){
JsonResult<String> result = new JsonResult<>(445,"BlockException限流");
return result;
}
}
此时我们来设置Sentinel配置,我们通过熔断规则中的异常数来演示(当然也可以用其他的)
规则:在一秒内超过最小访问次数5次,并且异常数超过2的时候,就会触发熔断规则。
此时我们来访问http://localhost:8084/consumer/fallback/6看效果:
exceptionsToIgnore
(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。@RestController
@Slf4j
public class DemoController {
//服务提供者URL
@Value("${service-url.nacos-user-service}")
private String SERVICE_URL;
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/fallback/{id}")
//同时添加SentinelResource注解的fallback和blockHandler属性
@SentinelResource(value = "falllback",fallback = "fallbackHandler",blockHandler = "blockHandler",
exceptionsToIgnore = {NullPointerException.class})//被标注的异常将会被 原样抛出
public JsonResult<String> fallback(@PathVariable Long id){
if(id<=3){
//通过Ribbon发起远程访问,访问9003/9004
JsonResult<String> result = restTemplate.getForObject(SERVICE_URL+"/info/"+id,JsonResult.class);
System.err.println(result.getData());
return result;
}else{
throw new NullPointerException("没有对应的数据记录");
}
}
//处理Java异常
public JsonResult<String> fallbackHandler(Long id,Throwable e){
JsonResult<String> result = new JsonResult<>(444,"NullPointerException异常");
return result;
}
//处理Sentinel限流
public JsonResult<String> blockHandler(Long id, BlockException e){
JsonResult<String> result = new JsonResult<>(445,"BlockException限流");
return result;
}
}
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量,比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持一下的模式:
maxQps(秒级统计的最大QPS) * minRt(秒级统计的最小响应时间)
估算得出。设定参考值一般是 CPU cores * 2.5
。这里我们只通过入口QPS来进行测试,直接设置规则
最后测试效果不管现在我们访问那个接口只要超过阈值就会被限流
Sentinel 提供了@SentinelResource注解用于定义资源,并提供了AspectJ的扩展用于自定义资源,处理BlockException等。
之前我们用过这个注解,同时了解了它的两个属性:
我们之前利用这个注解完成了热点规则的学习,同时做了一个案例,我们简单复习一下,这个案例的核心思想就是我们传递一个指定参数,然后通过注解@SentinelResource注解标注资源进行限流,当出现限流以后,通过blockHandler属性设置限流以后的解决方法。
其实这个注解不仅仅可以用到热点规则上,还可以用到流控上,我们可以做一个资源的流控和一个请求的流控,通过此注解来解决限流之后问题。
**核心点:**使用@SentinelResource注解的blockHandler属性,定义出现限流效果时的解决方法。
编写一个新的控制器类型SentinelResourceTestController,使用@SentinelResource注解同时使用blockHandler属性
@GetMapping("/byResource")
@SentinelResource(value = "byResource",blockHandler = "handler_resource")
public String byResource(){
return "-----byResource";
}
public String handler_resource(BlockException exception){
return "系统繁忙";
}
这里要注意一定要给byResource资源添加流控
测试,测试我们去快速访问http://localhost:8401/byResource,就会出现我们使用@SentinelResource注解中blockHandler属性提供的解决限流异常的方法。
**核心点:**使用@SentinelResource注解,但是不使用blockHandler属性,系统会调用默认限流异常处理方法。
其实这个注解,我们还可以更换请求地址为资源,比如我们在新建一个测试接口方法
@GetMapping("/byRest")
@SentinelResource(value = "byRest")
public String byRest(){
return "-----byRest";
}
给这个接口地址添加流控
此时如果没有自己定义限流处理方法,会走系统默认的
其实我们在使用@SentinelResource注解这两种方案的时候,会出现一些问题:
解决:@SentinelResource除了blockHandler可以设置自定义限流处理逻辑方法以外,还提供另外一个属性来设置限流处理逻辑类型blockHandlerClass属性,此属性中设置的方法必需为 static 函数,否则无法解析。
第一步
创建CustomerBlockHandler类型用于处理自定义限流处理逻辑,首先创建myhandler.CustomerBlockHandler
/**
* 此类型用来处理限流自定义逻辑
*/
public class CustomerBlockHandler {
public static String handlerException1(BlockException exception){
return "handlerException1:系统异常,请稍后重试!";
}
public static String handlerException2(BlockException exception){
return "handlerException2:网络崩溃了,请稍后重试!";
}
}
第二步
我们在SentinelResourceTestController类型中添加一个接口方法,同时设置@SentinelResource注解和blockHandlerClass属性对应的类型和这个类型中对应的处理方法
/**
* 此方法用到了自定义限流处理类型CustomerBlockHandler
* 中的handlerException1方法来处理限流逻辑。
*/
@GetMapping("/bycustomer")
@SentinelResource(value = "bycustomer",
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException1")
public String bycustomer(){
return "-----bycustomer";
}
第三步
测试:给bycustomer资源添加限流规则,然后来测试在超过限流阈值时处理方法是否为CustomerBlockHandler中handlerException1来进行处理。
添加流控规则以后,我们再来频繁访问http://localhost:8401/bycustomer,就会看见是CustomerBlockHandler类型的handlerException1方法来处理自定义限流逻辑