赞
踩
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
Sentinel
以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
回顾以前的
Hystrix
,我们做到了服务熔断,服务降级,但是Hystrix
没有一套Web
界面可以让我们更加细粒化的配置,Sentinel
和Nacos
一样,不再需要我们手动的去创建某个微服务模块来专门实现对应功能,直接用一套Web
界面供我们进行细粒化的统一配置,管理。
Sentinel
分为两个部分:
Java
客户端)不依赖任何框架/库,能够运行于所有Java
运行时环境,同时对Dubbo
/spring Cloud
等框架也有较好的支持。Dashboard
)基于Spring Boot
开发,打包后可以直接运行,不需要额外的Tomcat
等应用容器。开始运行,保证
java8
环境正常,8080
端口不被占用(注意与Tomcat
区分开)
java -jar sentinel-dashboard-1.8.1.jar
直接访问
localhost:8080
管理界面,账号密码都是sentinel
docker pull bladex/sentinel-dashboard
#此docker镜像默认端口不是8080了,而是8858
docker run --name sentinel -d -p 8858:8858 bladex/sentinel-dashboard
浏览器访问,别忘记安全组
Sentinel
案例我没有用Docker
了,因为我发现Sentinel
能找到服务,但是无法实时监控,也就是实时监控列表为空,初步猜测是我连接的移动宽带的wifi
,是一个内网环境,我能找到Sentinel
,Sentinel
找不到服务,所以案例以本地Sentinel
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloudDemo</artifactId> <groupId>com.phz.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>SentinelService8401</artifactId> <dependencies> <dependency> <groupId>com.phz.springcloud</groupId> <artifactId>CloudAPI</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba 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.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> </project>
server: port: 8401 spring: application: name: cloudalibaba-sentinal-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 39.105.43.3:8848 sentinel: transport: #配置Sentinel dashboard地址,如果这里使用了docker的Sentinel,默认端口不再是8080,而是8858 dashboard: localhost:8080 # 和Sentinel进行数据交互的默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口 # 指定应用与Sentinel控制台交互的端口,应用程序本地会发起一个占用该端口的HttpServer port: 8719 management: endpoints: web: exposure: include: '*'
/**
* @author PengHuAnZhi
* @createTime 2021/2/22 9:36
* @projectName SpringCloudDemo
* @className SentinelMain8401.java
* @description TODO
*/
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelMain8401 {
public static void main(String[] args) {
SpringApplication.run(SentinelMain8401.class, args);
}
}
/** * @author PengHuAnZhi * @createTime 2021/2/22 9:40 * @projectName SpringCloudDemo * @className FlowLimitController.java * @description TODO */ @RestController @Slf4j public class FlowLimitController { @GetMapping("/testA") public String testA() { return "----testA"; } @GetMapping("/testB") public String testB() { return "----testB"; } }
开启Sentinel
java -jar sentinel-dashboard-1.8.1.jar
开启服务
Sentinel
由于采用了懒加载机制,监控的服务需要被访问才会被加载,所以先执行业务方法,再刷新Sentinel
面板
打开
Sentinel
面板,可以新增流控规则
也可以打开簇点链路指定某资源名新增流控,如图
资源名:唯一名称。默认请求路径
针对来源:Sentinel
可以针对调用者进行限流,填写微服务名,默认default
(不区分来源)
阈值类型/单机阈值
QPS
(每秒钟的请求数量):当调用该api
的QPS
达到阈值的时候,进行限流。api
的线程数达到阈值的时候,进行限流是否集群:不需要集群
流控模式:
api
达到限流条件时,直接限流api
级别的针对来源流控效果:
codeFactor
(冷加载因子,默认3
)的值,从阈值/codeFactor
,经过预热时长,才达到设置的QPS
阈值给
testA
配置一个流控规则,表示每秒请求数量超过一个直接失败,也就是疫苗只能通过一此A
请求
连续快速点击,效果如下,报出
Block By Sentinel(flow limiting)
提示
修改流控规则,阈值类型修改为线程数,这个线程数是什么意思呢,类比前一个
QPS
,他是每一秒钟只能有指定的请求数量才能访问,只要满足要求,就执行请求,如果线程数量指定为1
,那么就意味着,这个资源同时只能被一个线程所占用,其他的请求来了,但是前一个请求还没有结束,便不能获取资源。
给
testA
方法添加一个延时语句
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
执行访问
关联也就是其他的资源出问题了,限流自己,比如在微服务中,支付服务出现问题,就限流下订单服务。
使用
PostMan
模拟密集访问testB
,记得去掉上面的延时语句
然后点击运行,访问
testA
修改
testB
的流控规则
访问
公式:阈值除以
coldFactor
(默认值为3
),经过预热时长后才会阈值,即请求QPS
从阈值/3
开始,经预热时长逐渐升到设定的QPS
阈值
匀速排队,让请求以均匀的速度通过,阈值类型必须为
QPS
,否则无效,修改testB
流控规则,表示一秒一次请求,超过就排队,等待的超时时间为2000ms
testB
加一行打印语句,postman
每一秒访问两次
观察控制台,可以看到,10次请求并不是按照预先设定的那样一秒两次,而是一秒一次,当一秒钟出现了两次请求时,在处理了其中一个请求后,另一个并不会直接报错销毁,而是进入排队等待。
RT(平均响应时间,秒级)
>=5
,两个条件同时满足后触发降级窗口期过后关闭断路器4900
(更大的需要通过-Dcsp.sentinel.statistic.max.rt=XXXX
才能生效)异常比列(秒级)
QPS >= 5
且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
Sentinel
熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException
)
我们通常用以下几种方式来衡量资源是否处于稳定的状态:
DEGRADE_GRADE_RT
):当1s
内持续进入5
个请求,对应时刻的平均响应时间(秒级)均超过阈值( count
,以ms
为单位),那么在接下的时间窗口(DegradeRule
中的 timewindow
,以s
为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException
)。注意Sentinel
默认统计的RT
上限是4900 ms
,超出此阈值的都会算作4900 ms
,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx
来配置。DEGRADE_GRADE_EXCEPTION_RATIo
):当资源的每秒请求量>=5
,并且每秒异常总数占通过量的比值超过阈值(DegradeRule
中的 count
)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule
中的 timewindow
,以s
为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[e.0,1.0]
,代表0%- 100%
。DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近1
分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timewindow
小于60s
,则结束熔断状态后仍可能再进入熔断状态。取消注释
testA
的延时语句,一次请求睡一秒,新增降级规则
然后开启
Jmeter
压力测试,一秒十个线程访问
再次访问
testA
,就访问不了了
由于设置的熔断时长为
60s
,当线程组执行完毕后,要等待60s
后再次访问才能正常
注释
testA
延时语句,新增一条异常语句,如10/0
,然后修改降级规则。
Jmeter
在1S
内同时发送10
条请求,然后执行访问,不再是以往的页面显示一个500
的错误,而是一个Sentinel
的自定义提示,前提一定是要注意一个地方,那就是一秒内请求数量一定要超过5
个,不然还是会报500
等待线程执行结束,然后等
5s
(熔断时长),再次访问
修改降级规则
Jmeter
继续发请求
等待线程结束且
5s
后
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的
Top K
数据,并对其访问进行限制。比如:
ID
为参数,统计一段时间内最常购买的商品 ID
并进行限制ID
为参数,针对一段时间内频繁访问的用户 ID
进行限制热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
新增
hostKey
方法,其中一条@SentinelResource
注解混个眼熟,后面会单独介绍,blockHandler
属性必须指定,否则Sentinel
面板如果设置了热点规则,这里又不配,将直接返回一个500
的对于用户不友好的错误页面,而且它只帮我们处理Sentinel添加的配置的违规情况,如果是程序本身的错误也就是RuntimeException
,是不会进入兜底方法,这个后面也会说
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey")//注意这个value不一定和mapping一致,只要是唯一即可,在Sentinel面板添加热点规则的时候应该指定这个值,注意没有斜杠,blockHandler对比Hystrix的HystrixCommand注解的fallbackMethod属性,指定了兜底方法。
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {//两个参数都指定可选
return "----testHotKey";
}
//兜底方法
public String deal_testHotKey(String p1, String p2, BlockException exception) {
return "----deal_testHotKey, o(╥﹏╥)o"; // sentinel的默认提示都是: Blocked by Sentinel (flow limiting)
}
新增热点规则,参数索引
0
表示当第一个参数也就是p1
所携带的值在相同的情况下一秒只能有一个QPS
,否则就会进入自定义兜底方法
狂发一条带有
p1
参数的请求,可以看到提示信息就是我们自己定义的兜底方法
我们前面配置了
p1
的热点规则,但是我们希望对于p1
的某一个值比如5
的时候,他的阈值能够到200
而不是1
呢,所以就可以配置参数例外项来达到需求
再次测试就会发现,狂发携带参数
p1=5
的请求,一切正常了。
Linux/Unix-like
机器生效):系统的 load1
作为启发指标,进行自适应系统保护。当系统 load1
超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR
阶段)。系统容量由系统的 maxQps * minRt
估算得出。设定参考值一般是 CPU cores * 2.5
。1.5.0+
版本):当系统 CPU
使用率超过阈值即触发系统保护(取值范围 0.0-1.0
),比较灵敏。RT
达到阈值即触发系统保护,单位是毫秒。QPS
达到阈值即触发系统保护。不做测试,有兴趣自己试试,这个一般用的不多
便于演示,新建一个
Controller
/** * @author PengHuAnZhi * @createTime 2021/2/24 16:27 * @projectName SpringCloudDemo * @className RateLimitController.java * @description TODO */ @RestController @Slf4j public class RateLimitController { @GetMapping("/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException") public CommonResult byResource() { return new CommonResult(200, "按照资源名称限流测试", new Payment(2020L, "serial001")); } public CommonResult handleException(BlockException exception) { return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用"); } }
这个上面已经说了,不在赘述
添加新的方法
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")//没有再指定兜底方法了
public CommonResult byUrl() {
return new CommonResult(200, "按照byUrl限流测试", new Payment(2020L, "serial002"));
}
新增流控规则
QPS
超过1
便会进入Sentinel
给我们默认的兜底方法
又回到了Hystrix
的时候出现的问题了
/** * @author PengHuAnZhi * @createTime 2021/2/24 16:47 * @projectName SpringCloudDemo * @className CustomerBlockHandler.java * @description TODO */ public class CustomerBlockHandler { public static CommonResult handlerException1(BlockException exception) { return new CommonResult(444, "按照客户自定义的Glogal 全局异常处理 ---- 1", new Payment(2020L, "serial003")); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(444, "按照客户自定义的Glogal 全局异常处理 ---- 2", new Payment(2020L, "serial003")); } }
//CustomerBlockHandler
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2")//可以看出blockHandler可以指定自定义blockHandler中的指定兜底方法
public CommonResult customerBlockHandler() {
return new CommonResult(200, "按照客户自定义限流测试", new Payment(2020L, "serial003"));
}
测试一下,可以看到进入了我们自己定义的兜底方法了
属性名 | 是否必填 | 说明 |
---|---|---|
value | 是 | 资源名称 。(必填项,需要通过 value 值找到对应的规则进行配置) |
entryType | 否 | entry类型,标记流量的方向,取值IN/OUT,默认是OUT |
blockHandler | 否 | 处理BlockException的函数名称(可以理解为对Sentinel的配置进行方法兜底)。函数要求: 1.必须是 public 修饰 2.返回类型与原方法一致 3. 参数类型需要和原方法相匹配,并在最后加 BlockException 类型的参数。 4. 默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 blockHandlerClass ,并指定blockHandlerClass里面的方法。 |
blockHandlerClass | 否 | 存放blockHandler的类。 对应的处理函数必须 public static 修饰,否则无法解析,其他要求:同blockHandler。 |
fallback | 否 | 用于在抛出异常的时候提供fallback处理逻辑(可以理解为对Java异常情况方法兜底)。 fallback函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。函数要求: 1.返回类型与原方法一致 2.参数类型需要和原方法相匹配,Sentinel 1.6开始,也可在方法最后加 Throwable 类型的参数。 3.默认需和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定fallbackClass里面的方法。 |
fallbackClass | 否 | 存放fallback的类。 对应的处理函数必须static修饰,否则无法解析,其他要求:同fallback。 |
defaultFallback | 否 | 用于通用的 fallback 逻辑。 默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,以fallback为准。函数要求: 1.返回类型与原方法一致 2.方法参数列表为空,或者有一个 Throwable 类型的参数。 3.默认需要和原方法在同一个类中。若希望使用其他类的函数,可配置 fallbackClass ,并指定 fallbackClass 里面的方法。 |
exceptionsToIgnore | 否 | 指定排除掉哪些异常。 排除的异常不会计入异常统计,也不会进入fallback逻辑,而是原样抛出。 |
exceptionsToTrace | 否 | 需要trace的异常 |
以上提到的方法接下来会用到一部分
新建两个
Module
名称为Sentinel-provider-payment9003/9004
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloudDemo</artifactId> <groupId>com.phz.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>Sentinel-provider-payment9004</artifactId> <dependencies> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.phz.springcloud</groupId> <artifactId>CloudAPI</artifactId> <version>1.0-SNAPSHOT</version> </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.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> </project>
server: port: 9003 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: 39.105.43.3:8848 management: endpoints: web: exposure: include: '*'
/**
* @author PengHuAnZhi
* @createTime 2021/2/24 17:27
* @projectName SpringCloudDemo
* @className PaymentMain9003.java
* @description TODO
*/
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}
/** * @author PengHuAnZhi * @createTime 2021/2/24 17:24 * @projectName SpringCloudDemo * @className PaymentController.java * @description TODO */ @RestController public class PaymentController { @Value("${server.port}") private String serverPort; public static HashMap<Long, Payment> map = new HashMap<>(); //模拟service层调用数据库 static { map.put(1L, new Payment(1L, "1111")); map.put(1L, new Payment(2L, "2222")); map.put(1L, new Payment(3L, "3333")); } @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) { Payment payment = map.get(id); CommonResult<Payment> result = new CommonResult<>(200, "from mysql,serverPort: " + serverPort, payment); return result; } }
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>SpringCloudDemo</artifactId> <groupId>com.phz.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>Sentinel-consumer-nacos-order8004</artifactId> <dependencies> <!-- SpringCloud ailibaba nacos--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- SpringCloud ailibaba sentinel--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.phz.springcloud</groupId> <artifactId>CloudAPI</artifactId> <version>1.0-SNAPSHOT</version> </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.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> </project>
server: port: 8004 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: 39.105.43.3:8848 sentinel: transport: dashboard: localhost:8080 port: 8719 #消费者将去访问的微服务名称 server-url: nacos-user-service: http://nacos-payment-provider
/**
* @author PengHuAnZhi
* @createTime 2021/2/24 17:34
* @projectName SpringCloudDemo
* @className OrderMain8004.java
* @description TODO
*/
@EnableDiscoveryClient
@SpringBootApplication
public class OrderMain8004 {
public static void main(String[] args) {
SpringApplication.run(OrderMain8004.class, args);
}
}
ApplicationContextConfig
/** * @author PengHuAnZhi * @createTime 2021/2/24 17:36 * @projectName SpringCloudDemo * @className ApplicationContextConfig.java * @description TODO */ @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }
Controller
/** * @author PengHuAnZhi * @createTime 2021/2/24 17:37 * @projectName SpringCloudDemo * @className CircleBreakerController.java * @description TODO */ @RestController @Slf4j public class CircleBreakerController { public static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") @SentinelResource(value = "fallback") public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } }
@RequestMapping("/consumer/fallback/{id}") // @SentinelResource(value = "fallback")//不配置 @SentinelResource(value = "fallback", fallback = "handlerFallback")//仅配置fallback public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "异常handlerFallback,exception内容: " + e.getMessage(), payment); }
测试
@RequestMapping("/consumer/fallback/{id}") // @SentinelResource(value = "fallback")//不配置 // @SentinelResource(value = "fallback", fallback = "handlerFallback")//仅配置fallback @SentinelResource(value = "fallback", blockHandler = "blockHandler") public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } // public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { // Payment payment = new Payment(id, "null"); // return new CommonResult(444, "异常handlerFallback,exception内容: " + e.getMessage(), payment); // } public CommonResult blockHandler(@PathVariable Long id, BlockException e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "blockHandler-sentinel 限流,BlockException: " + e.getMessage(), payment); }
新增降级规则
单独访问错误方法直接
500
连续点击才能触发降级
@RequestMapping("/consumer/fallback/{id}") // @SentinelResource(value = "fallback")//不配置 // @SentinelResource(value = "fallback", fallback = "handlerFallback")//仅配置fallback // @SentinelResource(value = "fallback", blockHandler = "blockHandler")//仅配置blockHandler @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler") public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgument ,非法参数异常..."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "异常handlerFallback,exception内容: " + e.getMessage(), payment); } public CommonResult blockHandler(@PathVariable Long id, BlockException e) { Payment payment = new Payment(id, "null"); return new CommonResult(444, "blockHandler-sentinel 限流,BlockException: " + e.getMessage(), payment); }
测试
也可以看出,优先级肯定是控制台的降级规则优先级更高,也就是只会进入
blockHandler
处理
当再SentinelResource中加入以个exceptionsToIgnore属性,对于指定的异常类型进行忽略,本例也就是查找4的时候不会抛出
IllegalArgumentException
异常
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = IllegalArgumentException.class)
feign
一般用在消费端,这里修改8004
新增依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
新增配置
feign:
sentinel:
# 开启feign对sentinel的支持
enabled: true
添加以个注解
@EnableFeignClients
添加
Feign
接口类
/**
* @author PengHuAnZhi
* @createTime 2021/2/24 18:12
* @projectName SpringCloudDemo
* @className PaymentService.java
* @description TODO
*/
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
添加
Feign
接口实现类
/**
* @author PengHuAnZhi
* @createTime 2021/2/24 18:16
* @projectName SpringCloudDemo
* @className paymentFallbackService.java
* @description TODO
*/
@Component
public class PaymentFallbackService implements PaymentService{
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服务降级返回,---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);
}
停掉
9003
和9004
,测试通过
在我们前面配置各种限流和降级等等规则的时候,如果我们把服务重启了,对应的配置规则便消失了,我们能否将配置好的规则持久化呢?
我们可以将限流配置规则持久化进
Nacos
保存,只要刷新8401
某个rest
地址,sentinel
控制台的流控规则就能看到,只要Nacos
里面的配置不删除,针对8401
上sentinel
上的流控规则持续有效
在
POM
文件新增以个依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
server: port: 8401 spring: application: name: cloudalibaba-sentinal-service cloud: nacos: discovery: #Nacos服务注册中心地址 server-addr: 39.105.43.3:8848 sentinel: transport: #配置Sentinel dashboard地址,如果这里使用了docker的Sentinel,默认端口不再是8080,而是8858 dashboard: localhost:8080 # 和Sentinel进行数据交互的默认8719端口,假如被占用了会自动从8719端口+1进行扫描,直到找到未被占用的 端口 # 指定应用与Sentinel控制台交互的端口,应用程序本地会发起一个占用该端口的HttpServer port: 8719 datasource: ds1: nacos: server-addr: 39.105.43.3:8848 #nacos dataId: ${spring.application.name} groupId: DEFAULT_GROUP data-type: json rule-type: flow management: endpoints: web: exposure: include: '*'
JSON
串为
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
0
表示线程数量,1
表示QPS
0
表示直接,1
表示关联,2
表示链路0
表示快速失败,1
表示warm Up
,2
表示排队等待重启
8401
,观察sentinel
控制台
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。