19. SpringCloud Alibaba Sentinel实现熔断与限流
概述
类比于 Hystrix
Hystrix | Sentinel |
---|---|
需要手工搭建监控平台 Hystrix Dashboard | 单独一个组建,可以独立出来 |
没有一套 web 界面可以给我们进行更加细粒度化的配置,如流量控制、速率控制、服务熔断、服务降级 | 直接界面化的细粒度统一配置 |
解决服务使用中的各种问题:
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
安装 Sentinel 控制台
Sentinel分为两个部分:
- 核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对 Dubo/Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。默认使用
8080
端口
访问sentinel管理界面:
URL 为 http://localhost:8080,登录账号密码均为 sentinel
初始化演示工程
前提:
- 启动 Nacos
- 启动 Sentinel
-
Module,cloudalibaba-sentinel-service8401
-
POM
<!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到--> <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>
-
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: endpoints: web: exposure: include: '*'
-
主启动类
-
业务类
@RestController @Slf4j public class FlowLimitController { @GetMapping("/testA") public String testA() { return "------testA"; } @GetMapping("/testB") public String testB() { log.info(Thread.currentThread().getName() + " " + "...testB"); return "------testB"; } }
-
启动微服务 8401
-
访问请求,刷新 Sentinel,可以在控制台看到此请求
Sentinel 只有在访问过一次请求后,才可以在控制台看到
流控规则
控制台中的【流控规则】
【流控规则】> >【新增流控规则】,字段说明:
- 资源名:唯一名称,默认请求路径
- 针对来源:Sentinel 可以针对调用者进行限流,填写微服务名,默认 default(不区分来源)
- 阈值类型/单机阈值:
- QPS(Queries-per-second,每秒钟的请求数量):当调用该 api 的 QPS 达到阈值的时候,进行限流
- 线程数:当调用该 api 的线程数达到阈值的时候,进行限流
- 是否集群:不需要集群
- 流控模式:
- 直接:api达到限流条件时,直接限流
- 关联:当关联的资源达到阈值时,就限流自己
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流)【api 级别的针对来源 】
- 流控效果:
- 快速失败:直接失败,抛异常
- Warm Up:根据 codeFactor(冷加載因子,默认3)的值,从阈值/codefactor,经过预热时长,才达到设置的 QPS 阈值
- 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为 QPS,否则无效
流控模式
直接(默认)
QPS、直接、快速失败
默认模式
快速刷新访问 http://localhost:8401/testA
,出现 Sentinel 提示:
Blocked by Sentinel (flow limiting)
线程数、直接
在业务代码中增加延时,之后操作如上,也会出现 Sentinel 提示
@GetMapping("/testA")
public String testA() throws InterruptedException {
// 测试 线程数 直接
TimeUnit.SECONDS.sleep(3);
log.info(Thread.currentThread().getName() + " " + "...testA");
return "------testA";
}
关联
是什么?
- 当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阈值后,就限流自己
- B惹事,A挂了
QPS、关联、快速失败
是否流控 /testA
取决于 /testB
的 QPS,如果超过阈值,访问 /testA
会出现 Sentinel 提示信息,而 /testB
不受影响。
使 /testB
QPS 超过阈值,可以使用 Postman 的 【Run Collection】 功能。
链路
多个请求调用了同一个微服务
流控效果
直接->快速失败(默认的流控处理)
直接失败,抛出异常
Blocked by Sentinel (flow limiting)
源码
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
预热
公式:阈值会经过变化,开始为阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
案例:阈值为10,预热市场 5s
系统初始化阈值为 10/3,约等于 3,即阈值开始为 3;经过 5s 后阈值慢慢升高,恢复到 10
应用场景:
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
排队等待
匀速排队,阈值必须设置为 QPS
源码:
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
降级规则
控制台中的【降级规则】
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致級联错误。当资源被降級后,在接下来的降級时间密口之内,对该资源的调用都自动熔断(默认行为是抛出 Degradeexception
)。
Sentinel 的断路器是 没有半开状态 的
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考 Hystrix
降级策略实战
-
RT(平均响应时间,秒级)
平均响应时间 超出阈值 且 在时间窗口内通过的请求 >=5 ,两个条件同时满足后触发降级
窗口期过后关闭断路器
RT 最大 4900(更大的需要通过
-Dcsp.sentinel.statistic.max.rt=XXX
オ能生效) -
异常比列(秒级)
QPS >= 5 且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
-
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
RT
平均响应时间 (DEGRADE_GRADE_RT
):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count
,以 ms 为单位),那么在接下的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException
)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx
来配置。
异常比例
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO
):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule
中的 count
)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule
中的 timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
异常数
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow
小于 60s,则结束熔断状态后仍可能再进入熔断状态。
时间窗口一定要大于等于60秒。
热点key限流
控制台中的【热点规则】
承上启下
承接 Hystrix
兜底方法
分为系统默认和客户自定义,两种
之前的case,限流出问题后,都是用 sentinel 系统默认的提示:Blocked by Sentinel (flow limiting)
我们能不能自定?类似 Hystrⅸ,某个方法出问题了,就找对应的兜底降级方法?
从 HystrixCommand
到 @SentinelResource
com.alibaba.csp.sentinel.slots.block.BlockException
@SentinelResource
只管 Sentinel 配置违规,不处理代码异常
代码
@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"; //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
}
如果使用了 @SentinelResource
,但是没有配置 blockHandler
属性,违反热点规则会出现错误页面,而不是 Sentinel 默认的错误提示
参数例外项
热点参数的注意点,参数必须是基本类型或者String
特殊情况
-
普通
超过1秒钟一个后,达到阈值1后马上被限流
-
我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
-
特例
假如当p1的值等于5时,它的阈值可以达到200
系统规则
控制台中的【系统规则】
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt
估算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
@SentinelResource
按资源名称限流+后续处理
配置资源名为 byResource
的流控规则
@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() + " 服务不可用");
}
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}
按照Url地址限流+后续处理
配置资源名为 /rateLimit/byUrl
的流控规则
没有配置 blockHandler
,返回 Sentinel 默认的提示信息
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}
上面兜底方法面临的问题
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,代码膨胀加剧。
- 全局统一的处理方法没有体现。
客户自定义限流处理逻辑
-
创建
CustomerBlockHandler
类用于自定义限流处理逻辑必须是 static 方法
public class CustomerBlockHandler { public static CommonResult handlerException2(BlockException exception) { return new CommonResult(4444, "按客戶自定义,global handlerException----2"); } }
-
业务类中使用
@GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200, "按客戶自定义", new Payment(2020L, "serial003")); }
-
测试限流
更多注解属性说明
Sentinel 主要有三个核心 API:
SphU
定义资源Tracer
定义统计ContextUtil
定义了上下文
服务熔断功能
sentinel整合ribbon+openFeign+fallback
Ribbon系列
-
Module
- 生产者:cloudalibaba-provider-payment9003、cloudalibaba-provider-payment9004
- 消费者:cloudalibaba-consumer-nacos-order84
-
测试 Ribbon 调用
## 直接调用生产者 http://localhost:9003/paymentSQL/1 http://localhost:9004/paymentSQL/1 ## 调用消费者 http://localhost:84/consumer/paymentSQL/1
-
测试
@SentinelResource
注解的fallback
和blockHandler
属性fallback
管运行异常,无blockHandler
时,也管配置违规blockHandler
管配置违规exceptionsToIgnore
忽略异常@RequestMapping("/consumer/fallback/{id}") // @SentinelResource(value = "fallback") // @SentinelResource(value = "fallback", fallback = "handlerFallback") //@SentinelResource(value = "fallback",blockHandler = "blockHandler") // @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler") // @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) 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("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.getClass().getCanonicalName() + ",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); }
## 触发 IllegalArgumentException http://localhost:84/consumer/fallback/4 ## 触发 NullPointerException http://localhost:84/consumer/fallback/11
-
@SentinelResource(value = "fallback")
出现错误页面
-
@SentinelResource(value = "fallback",fallback = "handlerFallback")
管运行异常
不会出现错误页面,加上流控规则后,违反规则后,会调用
handlerFallback
方法,但是异常类型为com.alibaba.csp.sentinel.slots.block.flow.FlowException
-
@SentinelResource(value = "fallback",blockHandler = "blockHandler")
不管运行异常,只管配置违规
-
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler")
fallback
管运行异常blockHandler
管配置违规 -
@SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class})
fallback
管运行异常,其中不管IllegalArgumentException
blockHandler
管配置违规
-
Feign系列
-
YML
feign: sentinel: enabled: true
-
Feign 服务接口
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); }
-
Feign 服务接口的 fallback 实现类
@Component public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(44444, "服务降级返回,---PaymentFallbackService", new Payment(id, "errorSerial")); } }
-
测试
关闭生产者应用,访问服务,发现返回的是 fallback 实现类中的响应
熔断框架比较
Sentinel | Hystrix | Resilience4j | |
---|---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 | 信号量隔离 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 | 基于异常比率、响应时间 |
实时统计实现 | 滑动窗口(Leaparray) | 滑动窗口(基于 RxJava) | Ring Bit Buffer |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
流量整形 | 支持预热模式、匀速器模式、预热排队模式 | 不支持 | 简单的 Rate Limiter 模式 |
系统自适应保护 | 支持 | 不支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 | 简单的监控查看 | 不提供控制台,可对接其他监控系统 |
规则持久化
一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效
- 修改cloudalibaba-sentinel-service8401
- POM
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- YML
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
- 在 Nacos 中添加配置,dataId 为 cloudalibaba-sentinel-service,groupId 为 DEFAULT_GROUP,配置内容为:
[
{
"resource": "/ratelimit/byUrl",
"limitapp": "default",
"grade": 1,
"count": 5,
"strategy": 0,
"controlBehavior": 0,
"clustermode": false
}
]
resource:资源名称;
limitapp:来源应用;
grade:阈值类型,0 表示线程数,1 表示QPS;
count:单机阈值;
strategy:流控模式,0 表示直接,1 表示关联,2 表示链路;
controlBehavior:流控效果,0 表示快速失败,1表示 Warm Up,2 表示排队等待;
clusterMode:是否集群;
- 重启 8401,访问 http://localhost:8401/rateLimit/byUrl,查看 Sentinel 流控规则中存在 Nacos 中配置的规则