文章目录
前言
在上一篇文章微服务之服务降级、服务熔断、服务限流三板斧中,我们已经了解了微服务下的几个基本概念:服务降级、服务熔断和服务限流,在本章中,我们主要对Alibaba下的开源组件Sentinel进行探索学习。在学习Sentinel这个组件之前,我们也学习过了一个之前的组件叫做Hystrix,主要用于服务熔断、限流降级等,不过需要进行大量的编码配置,增加代码的耦合度,并且没有一个可视化的web操作界面。Alibaba开源组件弥补这些缺点,能够独立成一个组件,减少系统和组件之间的耦合度,并且直接提供了一个细粒度统一配置界面。
Sentinel作为Alibaba的开源组件,其源码等开源到了GitHub上,下载地址:Sentinel下载 官方网站:Sentinel,其中文文档为Sentinel中文文档。
一、Sentinel介绍
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Sentinel 具有以下特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
- 完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
其主要特性如下图所示:
从上图中我们可以看到,整个Sentinel 主要分为两个部分:
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud
等框架(蓝色部分)也有较好的支持。
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
Sentinel支持在代码中配置和在Web界面中直接进行配置,实际开发中,为了减少耦合性,我们基本是在Web界面中进行配置的,其Web界面来源于Dashboard,因此,我们只需要下载相应的jar包,直接运行之后访问Web界面即可。主要是遵循一个道理:约定>配置>编码
注意:Sentinel默认使用的端口为8080,保证端口没有被占用。且Sentinel 仅支持 JDK 1.8 或者以上版本。
下载运行Sentinel Dashboard之后进入配置管理界面:
java -jar sentinel-dashboard-1.8.2.jar //运行dashboard
然后直接访问8080端口即可进入,默认用户名和密码都是sentinel,接下来,由于我们需要了解sentinel的一些配置,因此需要准备sentinel的服务才行,本次我是创建一个cloudalibaba-sentinel-service8401
的服务,然后在pom中引入相关依赖,这里我直接给出自己所有的依赖:
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--用于sentinel持久化到nacos中,sentinel中默认为临时规则-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringBoot整合Web组件+actuator -->
<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>
<!--日常通用jar包配置-->
<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>
然后在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
management: #web监控的,这里暂时没有用上
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true # 激活Sentinel对Feign的支持
这里先对上面的依赖和配置的组件进行一波解释,使用了Nacos组件,这个主要是Sentinel中配置的规则,在我们重启Sentinel Dashboard之后,之前配置的规则就失效了,因此将其持久化进Nacos中,如果不想使用Nacos中服务注册和服务发现功能,那么只需要设置spring.cloud.nacos.discovery
为false即可。而使用Feign是因为后面我们需要进行远程服务调用,这里先做配置,后面直接使用。
主启动类:
package com.dl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient //用于服务注册发现
public class SentinelService8401Application {
public static void main(String[] args) {
SpringApplication.run(SentinelService8401Application.class,args);
}
}
先在controller中随便写两个接口进行简单调用之后,就可以在Sentinel Dashboard界面中看到相应接口信息了。
@RestController
@Slf4j
@RequestMapping("/flowLimit")
public class FlowLimitController {
@GetMapping("/testA")
public String testA(){
return "testA is normally";
}
@GetMapping("/testB")
public String testB(){
return "testB is normally";
}
}
多次请求这两条接口之后,我们就能在Sentinel Dashboard中查看到相应的信息了。
接口请求地址:http://localhost:8401/flowLimit/testA
,此时刷新Dashboard界面就能看到相应的信息了:
我们可以看到右边有许多的规则可供我们进行配置,先来看看流控规则,以testA为例:
下面对这些配置属性进行解释:
- 资源名:唯一名称,默认请求路径,也可以是SentinelResource的value值,前提是要能唯一标识一条接口。
- 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)。
- 阈值类型/单机阈值:
QPS(每秒的请求量)
︰当调用该API的QPS达到阈值的时候,进行限流。线程数
:当调用该API的线程数达到阈值的时候,进行限流。
- 是否集群:本次测试使用均为单机,因此不需要集群。
- 流控模式:
直接
:API达到限流条件时,直接限流。关联
:当关联的资源达到阈值时,就限流自己,比如配置testA关联testB,当testB达到阈值时,就会限流testA。链路
:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流),这个主要是从API级别来进行来源针对。
- 流控效果:
快速失败
:直接失败,抛异常,系统默认处理。Warm up
:根据Code Factor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。排队等待
:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。
下面主要是对流控模式、流控效果进行测试。
1.1 Sentinel流控模式
1.1.1 Sentinel流控模式——直接模式
例子:如下我们配置了对testA接口的流量控制,1秒内如果QPS高于1(也就是一秒内对testA超过一次请求),那么就会直接失败,返回Sentinel的默认处理,默认处理主要是位于:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
源码中,如果不想使用默认的处理,那么我们应该有对应的fallback兜底方法。
1.1.2 Sentinel流控模式——关联模式
例子:当关联资源/flowLimit/testB
的QPS阀值超过1时,就限流/flowLimit/testA
的访问,也就是当关联资源到阈值后限制配置好的资源名。
这部分测试相对较麻烦,我们需要使用一些并发工具进行测试,测试思路为:大批量请求高并发访问testB,此时测试testA的访问,发现testA已经挂了。
具体而言,我们可以利用jmeter等工具进行测试,这里为了方便,直接使用postman的collection新建多线程集合组进行测试模拟并发集访问,
当然,不进行并发工具测试的可直接将关联资源的testB的QPS设置为0进行测试,达到演示效果。
链路模式这部分暂不进行测试
1.2 Sentinel流控效果
1.2.1 Sentinel 流控效果——Warm Up
当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)
方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
它的实现是在 Guava 的算法的基础上实现的。 Sentinel 主要用于控制每秒的 QPS,即我们满足每秒通过的 QPS 即可,我们不需要关注每个请求的间隔,换言之,我们更像一个 Token Bucket(令牌桶)。
我们用桶里剩余的令牌来量化系统的使用率。假设系统每秒的处理能力为 b,系统每处理一个请求,就从桶中取走一个令牌;每秒这个令牌桶会自动掉落b个令牌。令牌桶越满,则说明系统的利用率越低;当令牌桶里的令牌高于某个阈值之后,我们称之为令牌桶"饱和"。
当令牌桶饱和的时候,基于 Guava 的计算上,我们可以推出下面两个公式:
r
a
t
e
(
c
)
=
m
∗
c
+
c
o
l
d
r
a
t
e
rate(c)=m*c+ coldrate
rate(c)=m∗c+coldrate
其中,rate 为当前请求和上一个请求的间隔时间,而 rate 是和令牌桶中的高于阈值的令牌数量成线形关系的。cold rate 则为当桶满的时候,请求和请求的最大间隔。通常是
c
o
l
d
F
a
c
t
o
r
∗
r
a
t
e
(
s
t
a
b
l
e
)
。
coldFactor * rate(stable)。
coldFactor∗rate(stable)。
这个场景主要用于启动需要额外开销的场景,例如秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。其次就是建立数据库连接等场景。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
默认coldFactor为3,即请求QPS 从 threshold / 3开始,经预热时长逐渐升至设定的QPS阈值。
案例: 设置阀值为10以及预热时长设置为5秒。
在上面配置中,系统初始化的阀值为
10
/
3
10/ 3
10/3约等于3,即阀值刚开始为3;然后过了5秒后阀值才慢慢升高恢复到10。
这个测试就主要看点击速度控制,多次快速点击(一秒十次内三次上,各位手点速度应该不会超过十次吧)http://localhost:8401/flowLimt/testA
刚开始被限流了,后续慢慢恢复正常。
1.2.2 Sentinel流控效果——排队等待
排队等待为匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 流量控制 - 匀速器模式,具体的例子可以参见 PaceFlowDemo。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
例子:/flowLimt/testA
每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
1.3 Sentinel 熔断降级
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。这部分的官方文档:Sentinel熔断降级
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
注意:本文档针对 Sentinel 1.8.0 及以上版本。1.8.0 版本对熔断降级特性进行了全新的改进升级,请使用最新版本以更好地利用熔断降级的能力。
熔断规则配置属性如下:
在熔断策略中,Sentinel提供了三种策略:慢调用比例、异常比例和异常数
- 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 半开状态,半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
- 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
使用表格方式进行概述:
三种熔断降级规则我们已经在上面有详细的了解了,接下来我们直接进行测试查看实际效果,为了进行测试,我们在controller中增加两条接口,如下:
@GetMapping("/testC")
public String testC() throws InterruptedException {
Thread.sleep(1000);
return "testC is normally";
}
@GetMapping("/testD")
public String testD(){
int i=1/0;
return "testD is normally";
}
@GetMapping("/testE")
public String testE(@RequestParam("id")Integer id){
int i;
if(id==1) i=1/0;
return "testE is normally";
}
1.3.1 Sentinel熔断降级——慢调用比例
当我们选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入半开状态,若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
按照上面的配置,我们访问http://localhost:8401/flowLimit/testC
,根据访问频率即可看到效果,上面配置解释为:对/flowLimit/testC
设置了慢调用比例的熔断降级规则,设置其最大响应时间RT为300毫秒,当请求大于300毫秒即为慢调用,在统计时长2秒的时间内最小请求数大于1个,并且慢调用的比例大于40%,则接下来的熔断时长1秒内的请求会被自动熔断,此时会返回Sentinel默认的熔断信息Blocked by Sentinel (flow limiting)
,在熔断时长1秒之后,熔断器就会进入半开状态,此时接下来的一个请求响应时间小于设置的慢调用RT 300毫秒则结束熔断,否则会再次熔断。
1.3.2 Sentinel熔断降级——异常比例
配置熔断降级规则为异常比例的,当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长之后熔断器同样会进入半开状态,此时接下来的一个请求响应时间小于设置的慢调用RT 300毫秒则结束熔断,否则会再次熔断。
刚开始调用直接报错,这很正常
后面我们再多次快速点击调用,发现进行了服务熔断降级,返回了Sentinel的默认处理。
1.3.3 Sentinel熔断降级——异常数
配置熔断降级规则为异常数的,当单位统计时长内的异常数目超过阈值之后会自动进行熔断。
这个测试就很简单了,按照上面的配置,3秒钟内超过5个异常请求则会触发服务熔断1秒,1秒后进入半开状态,如果下一次请求正常,则恢复断开熔断器,否则闭合熔断器进入熔断状态。
- 正常请求:
http://localhost:8401/flowLimit/testE?id=2
- 异常请求:
http://localhost:8401/flowLimit/testE?id=1
1.4 Sentinel热点Key
热点Key,主要的就是热点定义,缓存中我们通常定义热点数据为我们经常查询得数据,在Sentinel中定义的热点Key其实也和缓存中定义的差不多,热点定义为经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
为了测试热点key,且之前我们发现一个问题,那就是触发服务熔断之后永远都是Sentinel给我们返回默认的信息Blocked by Sentinel (flow limiting)
,我们可以利用@SentinelResource
中的blockHandler 属性自定义兜底方法,我们增加如下接口:
//针对某个热点key进行配置服务熔断和服务降级
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKeyFallback",blockHandler = "hotKeyFallback")
public String testB(@RequestParam(value = "p1",required = false,defaultValue = "p111") String p1,
@RequestParam(value = "p2",required = false,defaultValue = "p2222") String p2){
return Thread.currentThread().getName()+"\t-----testHotKey";
}
//服务降级之后的服务端兜底方法,如果是服务熔断,则客户端应有相应的兜底方法
public String hotKeyFallback(String p1, String p2, BlockException blockException){
return "testHotKey---p1="+p1+"p2="+p2+"的兜底方法!";
}
在热点规则中,新建热点限流规则,添加资源名为@SentinelResource
注解中的value属性值,上面配置则为testHotKeyFallback这个资源名中的p1参数(索引值为0)配置热点key限流规则,1秒内请求p1参数超过1次则会进行服务限流降级,异常用了我们自己定义的兜底方法。
上述触发热点key p1限流降级之后的请求URL:
返回异常的URL为:
http://localhost:8401/testHotKey?p1=abc
http://localhost:8401/testHotKey?p1=abc&p2=33
正常应为:
http://localhost:8401/testHotKey?p2=abc
有这样一种情况,我不希望直接限制p1,当p1参数为某个特殊值的时候他的限流方案不一样,相当于更加细粒度控制,此时我们可以进行配置的添加,记得点击右边的添加按钮哦!
在上面配置中,对于普通的p1的值,其限流规则为1秒内请求超过一次则进行限流降级,而对于p1=sb
这个特殊的值时,他的限流规则将变为1秒内超过200次才进行限流。
注意:上面这个对特殊key的限流需要满足其数据类型为Java.lang.String和八大基本数据类型才可以。
1.5 Sentinel系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统保护的目的其一是保证系统不被拖垮,这是最基本的,第二是在系统稳定的前提下,保持系统的吞吐量。
系统规则支持的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的最大并发量和最小响应时间乘积即: m a x Q p s ∗ m i n R t maxQps * minRt maxQps∗minRt 估算得出。设定参考值一般是 C P U c o r e s ∗ 2.5 CPU cores * 2.5 CPUcores∗2.5。
- CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
1.6 SentinelResource详解
注意:注解方式埋点不支持 private 方法。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
1.8.0 版本开始,defaultFallback 支持在类级别进行配置。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。
注意
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。
上面我们进行自定义BlockException处理时候,直接在Controller的接口方法中编写,这样会造成一个问题,也就是造成代码臃肿,exception的处理和逻辑的耦合,因此,独立一个工具类ExceptionUtil进行BlockException的处理,在这个类中编写兜底方法。注意编写的兜底方法需要为public static,否则无法进行解析。
package com.dl.util;
import com.alibaba.csp.sentinel.slots.block.BlockException;
/**
* 将处理的BlockException和fallback放在这里,避免全部写在controller中,造成代码的耦合,注意方法定义为public static,否则无法被sentinel解析
*/
public class ExceptionUtil {
public static String handleExceptionDefault(BlockException exception){
return "ExceptionUtil---handleException-default";
}
public static String fallback1(){
return "ExceptionUtil---fallback1";
}
public static String defaultFallback(){
return "ExceptionUtil---全局的defaultFallback";
}
}
而在Controller中添加如下接口:
@GetMapping("/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
blockHandlerClass = ExceptionUtil.class,blockHandler = "handleExceptionDefault",
fallbackClass = ExceptionUtil.class,fallback = "fallback1")
public String testCustomerBlockHandler(){
return "Controller---testCustomerBlockHandler!";
}
这里新增的流控规则资源名需要为@SentinelResource
注解中的value属性,否则不会生效。
然后进行访问测试:http://localhost:8401/flowLimit/customerBlockHandler
快速点击触发服务降级返回我们自定义的兜底方法,且返回的是blockHandler处理的逻辑。
1.8 Sentinel之规则持久化
在前面相信大家经历了痛点,那就是只要进行应用重启,Sentinel中配置的规则就会失效,每次每次都要重新配置,这不是麻烦的问题了,这就是灾难,因此,我们需要将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。
Sentinel的规则持久化也是非常简单的,只需要引入持久化依赖进行持久化配置即可。
<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
datasource: #添加Nacos数据源配置,ds1为数据源名,可以进行多数据源配置如ds2、ds3
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json #数据类型
rule-type: flow #规则类型,这里使用流控规则,其余包含:flow、degrade、system、authority、param-flow
management: #actuator web端点监控
endpoints:
web:
exposure:
include: '*'
feign:
sentinel:
enabled: true # 激活Sentinel对Feign的支持
添加好配置之后,在Nacos中public命名空间下增加如下配置:
[{
"resource": "/flowLimit/customerBlockHandler",
"IimitApp": "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:是否集群。
Json数据我们可以在新建流控规则保存时候获取,操作步骤如图所示:
点击NetWork之后是没有什么请求响应的,此时点击保存按钮则可以得到规则响应,复制响应的json数据,在请求中找到服务返回的Json数据将其复制出(在json数据那排多次双击就可以全选了):
此时比较混乱,找一个json在线格式化工具进行格式化,这里提供一个JSON在线解析
拷贝data中的数据就可以了。
此时对于该资源的持久化就完成了,参照B站尚硅谷配置,但是个人觉得这种方式还是比较麻烦,新增一个资源我们就需要在Nacos中进行持久化配置,待到学习完详细的之后再来进行后续部分。
待续,关于持久化这个部分,还有很多知识未进行探索,后面探索之后估计会另起一篇文章进行记录,到时候也会在这里给出文章地址。
二、总结
本文主要结合官方文档以及B站阳哥的视频对Alibaba的开源组件Sentinel进行了探索,主要从流控、熔断、降级等规则进行介绍,最后发现Sentinel中的规则临时性问题,探索了Sentinel中的规则持久化。