熔断、限流和降级:Sentinel
什么是熔断、限流和降级

技术实现对比
Sentinel | Hystrix | Resilience4j | |
---|---|---|---|
隔离策略 | 信号隔离量(并发线程数限流) | 线程池隔离/信号隔离量 | 信号隔离量 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 | 基于异常比率、响应时间 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于RxJava) | Ring Bit Buffer |
动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
扩展性 | 多个扩展点 | 插件 | 接口 |
基于注解的支持 | 支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
流量整形 | 预热模式、匀速器模式、预热排队模式 | 不支持 | Rate Limiter |
系统自适应保护 | 支持 | 不支持 | 不支持 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 简单的监控查看 | 无控制台,需对接其他监控系统 |
引入依赖
Sentinel的集成&开发
相对比较麻烦一点。
首先引入Maven依赖。
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- sentinel核心包 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<!-- 从nacos中拉取数据,实现策略持久化,还可以使用其他数据源,如mysql -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<!-- 将应用接入到控制台 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
<!-- 使用@SentinelResource -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
</dependency>
仍然以funcun-payment-center
和funcun-order-center
这两个Maven子模块项目为例来说明。
添加配置
funcun-payment-center
和funcun-order-center
的配置除了应用名称和相关服务的端口号不一样之外,其他都是相同的。
spring:
profiles:
active: dev
application:
name: funcun-payment-center
cloud:
nacos:
discovery:
# nacos服务注册中心地址
server-addr: localhost:8848
config:
# nacos服务配置中心地址
server-addr: localhost:8848
file-extension: yaml
sentinel:
app:
# 标识网关项目
type: 1
# 取消控制台懒加载
eager: true
transport:
# 跟控制台交流的端口,可以随便写,指定一个未被使用的端口即可
port: 9999
# 指定sentinel控制台服务的地址
dashboard: 127.0.0.1:8080
enabled: true
# nacos配置持久化
datasource:
ds-flow:
nacos:
# nacos服务配置中心地址
server-addr: localhost:8848
# 在nacos中创建名为${spring.application.name}-flow-rules的流控规则,并同步到sentinel
data-id: ${spring.application.name}-flow-rules
group-id: DEFAULT_GROUP
data‐type: json
# 流控限速规则
rule‐type: flow
ds-degrade:
nacos:
# nacos服务配置中心地址
server-addr: localhost:8848
# 在nacos中创建名为${spring.application.name}-degrade-rules的降级规则,并同步到sentinel
data-id: ${spring.application.name}-degrade-rules
group-id: DEFAULT_GROUP
data‐type: json
# 熔断降级规则
rule‐type: degrade
spring.cloud.sentinel.datasource.ds-flow
配置流控规则
。
spring.cloud.sentinel.datasource.ds-degrade
配置降级规则
。
因为使用Nacos作为Sentinel配置的数据源,所以会在其中创建两个相应的文件。
${spring.application.name}-flow-rules.json
:流控文件。${spring.application.name}-degrade-rules.json
:降级文件。
配置项中的rule‐type(规则类型)
包括下面这些。
flow流控规则
:FLOW("flow", FlowRule.class)
。degrade降级规则
:DEGRADE("degrade", DegradeRule.class)
。param-flow热点规则
:PARAM_FLOW("param-flow", ParamFlowRule.class)
。system系统规则
:SYSTEM("system", SystemRule.class)
。authority授权规则
:AUTHORITY("authority", AuthorityRule.class)
。gateway-flow网关限流规则
:GW_FLOW("gw-flow", "com.alibaba.csp.sentinel.adapter.gateway.common.rule.GatewayFlowRule")
。用户自定义的API分组规则
:GW_API_GROUP("gw-api-group", "com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition")
,可以把它看做是一些URL
匹配的组合。
流量控制通用模板
https://github.com/alibaba/Sentinel/wiki/流量控制官方说明。
[
{
"app": "consumer-order",
# 集群配置,当有集群时才会配置此项
"clusterConfig": {
"acquireRefuseStrategy": 0,
"clientOfflineTime": 2000,
"fallbackToLocalWhenFail": true,
"resourceTimeout": 2000,
"resourceTimeoutStrategy": 0,
"sampleCount": 10,
"strategy": 0,
"thresholdType": 0,
"windowIntervalMs": 1000
},
"clusterMode": false, # 集群模式,一般不使用次参数
"resource": "config", # 资源名
"grade": 1, # 限流阈值类型,1:QPS;0:并发线程数
"controlBehavior": 0, # 流控效果,0:快速失败;1:Warm Up(预热模式);2:排队等待
"count": 1.0, # 阈值
"warmUpPeriodSec": 10, # 预热时间,单位秒,预热模式需要此参数
"maxQueueingTimeMs": 500, # 超时时间,排队等待模式需要此参数
"limitApp": "default", # 针对来源,若为default则不区分调用来源
"strategy": 0 # 流控策略,0:直接;1:关联;2:链路
}
]
流量控制简单模板
模板中的内容一项都不能少,否则配置无法生效。
[
{
"resource": "config", # 资源名
"clusterMode": false, # 是否集群模式
"controlBehavior": 0, # 流控效果,0:快速失败;1:Warm Up(预热模式);2:排队等待
"grade": 1, # 限流阈值类型,1:QPS;0:并发线程数
"count": 2, # 限流阈值
"warmUpPeriodSec": 10, # 预热时间,单位秒,预热模式需要此参数
"maxQueueingTimeMs": 500, # 超时时间,排队等待模式需要此参数
"strategy": 0 # 流控策略,0:直接;1:关联;2:链路
}
]
熔断降级简单模板
https://github.com/alibaba/Sentinel/wiki/熔断降级官方说明。
模板中的内容一项都不能少,否则配置无法生效。
[
{
"resource": "degrade3", # 资源名称,在网关应用中指scg的routes:id
"clusterMode": false, # 是否集群模式
"grade": 2, # 熔断策略,0:慢调用比例,不设置此为默认值;1:异常比例;2:异常数
"count": 2, # 当熔断策略为慢调用,此值为最大RT,单位毫秒;当熔断是策略为异常比例,此值为异常比例,值是0.0-1.0之间的小数;当熔断策略是异常数,值为异常数
"timeWindow": 5, # 熔断时长,单位是秒
"minRequestAmount": 2, # 最小请求数,熔断时必须达到的最小请求数量
"slowRatioThreshold": 0.2, # 比例阈值,熔断策略是慢调用时存在,单位是0.0-1.0的小数
"statIntervalMs": 10000 # 统计时长,熔断规则所统计的时间范围,单位毫秒
}
]
Order服务
修改funcun-order-center
子项目中的OrderController.java
,加入测试熔断
和限流
的接口。
怎样通过多种方式使用Sentinel,以及有哪些需要注意的问题,都在代码的注释里。
package com.funcun.order.center.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.funcun.order.center.entity.User;
import com.funcun.order.center.service.PaymentFeignService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* 订单接口
*
*/
@RestController
// 在控制器类加入@RefreshScope注解使当前类下的配置支持Nacos的动态刷新功能
@RefreshScope
public class OrderController {
@Resource
private PaymentFeignService paymentFeignService;
/**
* 需要在nacos控制台中创建一个dataId为${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}的yaml配置文件
* 其中每部分内容:${spring.application.name}、${spring.profile.active}、${spring.cloud.nacos.config.file-extension}要完全和bootstrap.yml、application.yml中的一致
* 然后以读取本都配置的方式读取nacos的配置
* 也就是说,bootstrap.yml和application.yml负责"拼装"出指向nacos的"路标"信息
* 具体配置信息再从nacos里面读取
*
*/
@Value("${config.info}")
private String config_info;
/**
* 定义熔断、限流方法
*
*/
private static final String RESOURCE_NAME_HELLO = "hello";
private static final String RESOURCE_NAME_USER = "user";
private static final String RESOURCE_NAME_DEGRADE1 = "degrade1";
private static final String RESOURCE_NAME_DEGRADE2 = "degrade2";
private static final String RESOURCE_NAME_DEGRADE3 = "degrade3";
/**
* 使用sentinel的第一种方式:
* 1、如果没有使用nacos持久化配置,但没有配置流控规则,可以直接在sentinel的流控规则中添加规则,添加后立即生效
* 2、如果使用了nacos持久化配置,但没有配置流控规则,可以直接在nacos的配置列表中添加规则,添加后在sentinel的流控规则中刷新可立即看到
* 3、只能持久化流控规则,不能持久化熔断规则
* TODO:从nacos中读取sentinel配置(流控限速已实现)
* {
* "resource": "config",
* "clusterMode": false,
* "limitApp": "default",
* "controlBehavior": 0,
* "grade": 1,
* "count": 2,
* "strategy": 0,
*
* }
*
* TODO 本接口虽然是从nacos中读取的sentinel配置,但
* 本接口自身调用 http://127.0.0.1:9001/config SUCCESS
* 网关调用 http://127.0.0.1:9527/consumer-order/config SUCCESS
* 网关+feign调用 http://127.0.0.1:9527/provider-payment/config SUCCESS
* 纯feign调用 http://127.0.0.1:9002/config SUCCESS
*
*/
@GetMapping("/config")
public String config() {
Entry entry = null;
String retVal;
try {
entry = SphU.entry("config", EntryType.IN);
return "nacos配置中心的配置信息为:" + config_info;
} catch (BlockException e) {
retVal = "blocked";
} finally {
if (entry != null) {
entry.exit();
}
}
return retVal;
}
/**
* 使用Sentinel的第二种方式:通过代码配置规则实现流控
*
*/
@RequestMapping("/hello")
public String hello() {
Entry entry = null;
try {
// 1.sentinel针对资源进行限制的
entry = SphU.entry(RESOURCE_NAME_HELLO);
// 被保护的业务逻辑
String str = "hello world";
return str;
} catch (BlockException e1) {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
return "系统限流";
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常
Tracer.traceEntry(ex, entry);
} finally {
if (entry != null) {
entry.exit();
}
}
return null;
}
/**
* degrade1通过代码配置实现降级
*
*/
@RequestMapping("/degrade1")
@SentinelResource(value = RESOURCE_NAME_DEGRADE1, entryType = EntryType.IN, blockHandler = "blockHandlerForFb")
public User degrade1(String id) throws InterruptedException {
// 异常数比例
throw new RuntimeException("异常");
// // 慢调用比例
// TimeUnit.SECONDS.sleep(1);
// return new User("正常");
}
/**
* degrade2通过控制台配置实现降级
*
*/
@RequestMapping("/degrade2")
@SentinelResource(value = RESOURCE_NAME_DEGRADE2, entryType = EntryType.IN, blockHandler = "blockHandlerForFb")
public User degrade2(String id) throws InterruptedException {
// 异常数比例
throw new RuntimeException("异常");
}
/**
* TODO:从nacos中读取sentinel配置(熔断降级已实现)
* 模板中的内容一个都不能少,否则配置无法生效
* {
* "resource": "degrade3",
* "clusterMode": false,
* "limitApp": "default",
* "grade": 2,
* "count": 2,
* "timeWindow": 5,
* "minRequestAmount": 2,
* "statIntervalMs": 10000
* }
* TODO 问题:
* 1、从nacos中读取sentinel配置后,在代码中设置的流控和降级规则就会失效
* 2、只能实现从nacos到sentinel的"推模式",而不能实现sentinel主动从nacos中读取的"拉模式"
* 也就是只有在nacos主动修改了配置之后才能使配置才生效,而不是在sentinel启动时就从nacos中读取
* 3、本接口自身调用 http://127.0.0.1:9001/degrade3 SUCCESS
* 网关调用 http://127.0.0.1:9527/consumer-order/degrade3 SUCCESS
* 网关+feign调用 http://127.0.0.1:9527/provider-payment/degrade3 FAILURE
* 纯feign调用 http://127.0.0.1:9002/degrade3 FAILURE
* FIXME
* 解决问题1:既然在代码中设置的流控和降级规则会失效,所以代码中是不能写流控和熔断规则的
* ====================================================================================================
* 解决问题2:
* 本质上还是流程问题:
* 1、启动nacos -> 启动sentinel
* 2、在nacos中创建不同服务的流控配置和熔断配置
* consumer-order-flow-rules
* consumer-order-degrade-rules
* provider-payment-flow-rules
* provider-payment-degrade-rules
* 3、启动应用服务
* 4、访问几次流控和熔断接口,此时流控和熔断机制还未生效
* 5、刷新sentinel的【流控规则】和【熔断规则】菜单
* 此时可能还是看到需要流控和熔断的接口列表,这是因为sentinel是动态获取需要流控和熔断的接口的,而此时要从nacos拉取配置,因此需要一定的时间
* 所以此时要不断重复第4步、第5步的操作,直到能看到之前配置的接口列表为止
* 6、出现需要被流控和熔断的接口列表
* 7、此时访问接口就会触发流控和熔断机制
* ====================================================================================================
* 解决问题3:
* 出现问题3是因为nacos的配置中只有 consumer-order-flow-rules 和 consumer-order-degrade-rules
* 增加 provider-payment-flow-rules 和 provider-payment-degrade-rules 配置后解决,这算是变相解决bug
*
*/
@RequestMapping("/degrade3")
@SentinelResource(value = RESOURCE_NAME_DEGRADE3, entryType = EntryType.IN, blockHandler = "blockHandlerForFb")
public User degrade3(String id) throws InterruptedException {
// 异常数比例
throw new RuntimeException("异常");
}
public User fallbackHandleForFb(String id, Throwable e) {
return new User("fallback限流处理");
}
public User blockHandlerForFb(String id, BlockException ex) {
return new User("熔断降级");
}
/**
* @SentinelResource 改善接口中资源定义和被流控降级后的处理方法,使用:
* 1.添加依赖<artifactId>sentinel-annotation-aspectj</artifactId>
* 2.配置bean:SentinelResourceAspect
* value 定义资源
* fallback 当接口出现了异常,就可以交给fallback指定的方法进行处理(也可以在其他类中处理fallback,但是方法必须是static的)
* blockHandler 设置流控降级后的处理方法(默认该方法声明在同一个类中,也可以在其他类中处理blockHandler,但是方法必须是static的)
*
* blockHandler 如果和fallback同时指定了,则blockHandler优先级更高
* exceptionsToIgnore 排除哪些异常不处理
*
*/
@RequestMapping("/user")
@SentinelResource(value = RESOURCE_NAME_USER,
fallback = "fallbackHandleForGetUser",
/*exceptionsToIgnore = {ArithmeticException.class},*/
/*blockHandlerClass = User.class,*/
blockHandler = "blockHandlerForGetUser")
public User getUser(String id) {
int a = 1 / 0;
return new User("湘王");
}
public User fallbackHandleForGetUser(String id, Throwable e) {
return new User("fallback限流处理");
}
/**
* 注意:
* 1. 方法修饰符一定要是public
* 2. 返回值一定要和源方法保证一致,包含源方法的参数
* 3. 可以在参数最后添加BlockException,可以区分是什么规则的处理方法
*
*/
public User blockHandlerForGetUser(String id, BlockException ex) {
return new User("blockHandler流控处理");
}
/**
* 定义流量控制规则
* 通过@SentinelResource来定义资源并配置降级和流控的处理方法
* spring的初始化方法
*
*/
@PostConstruct
private void initFlowRules() {
// 流控规则集
List<FlowRule> rules = new ArrayList<>();
// 创建流控
FlowRule rule1 = new FlowRule();
// 对 /hello 资源进行流控
rule1.setResource(RESOURCE_NAME_HELLO);
// 设置流控规则(QPS表示每秒请求的次数)
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule1.setCount(1);
rules.add(rule1);
// 创建流控
FlowRule rule2 = new FlowRule();
// 对 /user 资源进行流控
rule2.setResource(RESOURCE_NAME_USER);
// 设置流控规则(QPS表示每秒请求的次数)
rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值
rule2.setCount(1);
rules.add(rule2);
// 加载配置好的流控规则
FlowRuleManager.loadRules(rules);
}
/**
* 定义熔断降级规则
* spring的初始化方法
*
*/
@PostConstruct
private void initDegradeRule() {
// 降级规则,异常
List<DegradeRule> degradeRules = new ArrayList<>();
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource(RESOURCE_NAME_DEGRADE1);
// 设置规则策略:异常数
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
// 触发熔断异常数:2
degradeRule.setCount(2);
// 触发熔断最小请求数:2,也就是说必须要请求两次,在这两次里面有两次异常,就会进行熔断
degradeRule.setMinRequestAmount(2);
// 统计时长:在多长时间段内请求的两次,触发了两次异常,触发熔断,单位:ms
// 时间太短不好测
degradeRule.setStatIntervalMs(60 * 1000);
// 一分钟内: 执行了2次,出现了2次异常,就会触发熔断
// 熔断持续时长:单位秒
// 一旦触发了熔断, 再次请求对应的接口就会直接调用降级方法
// 10秒过了后——半开状态:恢复接口请求调用,如果第一次请求就异常,再次熔断,不会根据设置的条件进行判定
degradeRule.setTimeWindow(10);
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);
}
/**
* 服务调用者
*
*/
@GetMapping("/ping1")
public String ping1(String id) {
return "consumer-order 通过 openfeign 调用 payment-provider 的接口 /ping1,结果是:" + paymentFeignService.ping1(id);
}
@GetMapping("/ping2/{id}")
public String ping2(@PathVariable("id")String id) {
return "consumer-order 通过 openfeign 调用 payment-provider 的接口 /ping2/{id},结果是:" + paymentFeignService.ping2(id);
}
@RequestMapping("/ping3")
public String ping3() {
return "consumer-order 通过 openfeign 调用 payment-provider 的接口 /ping3,结果是:" + paymentFeignService.ping3();
}
@GetMapping("/ping4")
public String ping4() {
return "consumer-order 通过 openfeign 调用 payment-provider 的接口 /ping4,结果是:" + paymentFeignService.ping4();
}
@GetMapping("/ping5")
public String ping5(String name) {
return "consumer-order 通过 openfeign 调用 payment-provider 的接口 /ping5,结果是:" + paymentFeignService.ping5(name);
}
@PostMapping("/ping6")
public String ping6(String username) {
return "consumer-order 通过 openfeign 调用 payment-provider 的接口 /ping6,结果是:" + paymentFeignService.ping6(username);
}
}
感谢支持
更多内容,请移步《超级个体》。