熔断降级
概述
对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
重要的是:熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
熔断降级规则参数
熔断降级规则由 DegradeRule 对象定义,其主要参数如下:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | 慢调用比例模式下,它表示以毫秒为单位的最大响应时间(RT)。 在异常比率模式中,它表示介于0.0和1.0之间的异常比率。 在异常计数模式下,它表示异常计数 |
timeWindow | 熔断时长,单位为 s | 默认为0,为0不会进入熔断 |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) | 1.0D |
熔断策略
grade 的取值有如下几种:
慢调用比例 (RuleConstant.DEGRADE_GRADE_RT值为0):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
异常比例 (RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO值为1):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
异常数 (RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT值为2):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意
异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex) 记录业务异常。示例:
Entry entry = null; try { entry = SphU.entry(resource); // Write your biz code here. // <<BIZ CODE>> } catch (Throwable t) { if (!BlockException.isBlockException(t)) { Tracer.trace(t); } } finally { if (entry != null) { entry.exit(); } }
开源整合模块,如 Sentinel Dubbo Adapter, Sentinel Web Servlet Filter 或 @SentinelResource 注解会自动统计业务异常,无需手动调用。
熔断降级事件监听
// name是监听的
EventObserverRegistry.getInstance().addStateChangeObserver("name",
(prevState, newState, rule, snapshotValue) -> {
// prevState:断路器的先前状态
// newState:断路器的新状态
// rule:关联规则
// snapshotValue:断路器打开时的触发值(如果新状态为CLOSED或HALF_OPEN,则为空,反之新出发值为OPEN则有值)
});
断路器的状态说明
- OPEN:OPEN状态所有请求都将被拒绝。直到熔断时间结速。
- HALF_OPEN:如果符合熔断规则,将进入OPEN状态,如果不符合熔断规则,则进入CLOSE状态
- CLOSE:允许所有请求。
与OpenFeign整合
添加依赖
我们在上面说的,熔断降级一般在客户端使用,我们的OpenFeign其实就是客户端,Sentinel提供了对OpenFeign的注解的支持,只需引入相关依赖即可:
<!-- 添加OpenFeign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 添加Sentinel依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 添加Sentinel DataSource Nacos依赖 -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.6</version>
</dependency>
增加配置
# 开启 Sentinel 对 Feign 的支持
feign.sentinel.enabled=true
此设置默认为false。即便设置了@FeignClient 的 fallback 也不会生效
OpenFeign 客户端示例
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "myCloud",fallback = SentinelFeignClient.Fallback.class)
public interface SentinelFeignClient {
@RequestMapping("/myCloud/conf/getCommonConf")
String getCommonConf();
@Component
class Fallback implements SentinelFeignClient{
@Override
public String getCommonConf() {
return "熔断降级";
}
}
}
此示例请求了我们 Spring Cloud alibaba 使用Nacos服务发现 一文中的服务名为 myCloud 的 Provider 集群。
SentinelFeignClient.Fallback.class 为OpenFeign的熔断处理类,其实现规则如下:
- 实现 @FeignClient 注解的接口
- 实现接口的方法即为熔断时执行的方法。
- @Component为必须的,是将Fallback 注册为Bean。
- fallback还有FallbackFactory的方式来实现(此处不做讲解了)
此处我们除了添加fallback,好像没有定义资源。在Spring Boot 和 Spring Cloud 环境下Sentinel 会自动将我们的url定义为资源,所以我们可以不用使用@SentinelResource 来定义url接口为资源。
Feign 对应的接口中的资源名策略定义:httpmethod:protocol://requesturl。@FeignClient 注解中的所有属性,Sentinel 都做了兼容。我们上面的OpenFeign示例,资源名称对应为:GET:http://myCloud/myCloud/conf/getCommonConf
初始化配置持久化Rule规则
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
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.degrade.circuitbreaker.CircuitBreaker;
import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.PropertyKeyConst;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
@Component
public class SentinelRuleInit {
@PostConstruct
public void init(){
Properties nacosPro = new Properties();
nacosPro.put(PropertyKeyConst.SERVER_ADDR,"127.0.0.1:8848");
// remoteAddress 代表 Nacos 服务端的地址
// groupId 和 dataId 对应 Nacos 中相应配置
ReadableDataSource<String, List<DegradeRule>> degradeRuleDataSource =
new NacosDataSource<>(nacosPro, "Sentinel_Demo_Group", "com.yyoo.sentinel.demo.DegradeRule",
source -> JSON.parseObject(source, new TypeReference<List<DegradeRule>>(){}));
DegradeRuleManager.register2Property(degradeRuleDataSource.getProperty());
EventObserverRegistry.getInstance().addStateChangeObserver("logging",(prevState, newState, rule, snapshotValue)->{
// prevState:断路器的先前状态
// newState:断路器的新状态
// rule:关联规则
// snapshotValue:断路器打开时的触发值(如果新状态为CLOSED或HALF_OPEN,则为空)
if(newState.equals(CircuitBreaker.State.OPEN)){
System.out.println("断路器进入熔断状态");
}else if(newState.equals(CircuitBreaker.State.HALF_OPEN)){
System.out.println("断路器进入半熔断状态");
}else if(newState.equals(CircuitBreaker.State.CLOSED)){
System.out.println("断路器进入正常状态");
}
});
}
}
Nacos中熔断规则的设置
[{
"resource" : "GET:http://myCloud/myCloud/conf/getCommonConf",
"grade": "2",
"count": "5",
"timeWindow":"10",
"statIntervalMs":"60000"
},{
"resource" : "GET:http://myCloud/myCloud/conf/getCommonConf",
"grade": "0",
"count": "2000",
"timeWindow":"10",
"statIntervalMs":"60000",
"slowRatioThreshold":"0.8"
}]
此处我们为同一个资源设置了两个熔断规则配置
- 异常数模式,异常数达到5开启熔断,熔断时长为10s,统计时间为60000ms=1分钟
- 慢调用比例模式,RT超过2000ms视为慢调用,熔断时长为10s,统计时间为1分钟,慢调用比例超过80%开启熔断。
注:此处的熔断规则为我们的测试规则,在你的实际生产代码中请按你自己的应用需求进行相关设置。
验证示例
在Provider接口中抛出相应异常或者休眠足够的时长,即可验证我们示例中的两个熔断规则。详细的验证方案此处就不再赘述了。