Sentinel之熔断降级
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException
)。
熔断的基本认识:
在大型分布式架构中,一个用户的请求,可能是这样
如果这个时候一个服务出现异常
- 服务提供者不可用(硬件故障、程序bug、网络故障、用户请求量较大)
- 重试导致的流量过大
- 服务调用者使用同步调用,产生大量的等待线程占用系统资源,一旦线程资源被耗尽,调用者提供的服务也会变成不可用状态
就会导致请求堆集从而出现整个服务不可用的问题。用古话来讲就是:千里之堤毁于蚁穴
在复杂的分布式架构的应用程序有很多的依赖,都会不可避免的出现服务故障等问题。高并发的依赖失败时如果没有隔离措施,当前应用服务就有被拖垮的风险。
引入熔断机制:
在分布式架构中,有一种解决方法,就是熔断机制。也就是说当下游服务因为访问压力过大或者其他原因导致响应变慢的时候,上游服务为了保护自己以及系统整体的可用性,可以暂时切断对于下游服务的调用。熔断在生活中也随处可见,
- 比如“跳闸”,当电压超过负荷时,开关会自动跳闸。从而防止出现电路烧毁带来的火灾。
- 比如股票市场的熔断,对于股票设置一个熔断价格,当价格触发到熔断点之后,交易会被暂停一段时间。或者交易可以继续进行,但是报价会限制在一定的范围
那生活中的这种场景,能不能应用在架构设计中呢?大家会发现,架构是基于人的架构,所以架构的设计都是基于人对于事务的基本认识来实施的。因此越往后面学习,越能够发现很多设计思想都来自于生活。
Sentinel熔断降级:
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断。那么怎么去判断资源是否处于稳定状态呢?
- 平均响应时间 (
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 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。 - 异常数 (
DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若timeWindow
小于 60s,则结束熔断状态后仍可能再进入熔断状态。
针对这些规则,Sentinel中给出了响应的字段来设置:
在实战之前我们再来回顾以下Sentinel限流中最重要的处理链:
public class DefaultSlotChainBuilder implements SlotChainBuilder {
@Override
public ProcessorSlotChain build() {
ProcessorSlotChain chain = new DefaultProcessorSlotChain();
chain.addLast(new NodeSelectorSlot());
chain.addLast(new ClusterBuilderSlot());
chain.addLast(new LogSlot());
chain.addLast(new StatisticSlot());
chain.addLast(new SystemSlot());
chain.addLast(new AuthoritySlot());
chain.addLast(new FlowSlot());
chain.addLast(new DegradeSlot());
return chain;
}
}
可以看到最后一个Slot,就是熔断降级部分,他与限流是可以并存的。
Sentinel集成Dubbo实现服务熔断:
这个熔断是跟着Sentinel集成Dubbo实现限流部分改造,小伙伴们可以先去浏览一下上一篇博文。
1.加入pom依赖
<dependency>
<!--这里面就一个接口,可以直接写到本项目中--> <groupId>com.wuzz.demo</groupId> <artifactId>sentinel-dubbo-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo</artifactId> <version>2.7.2</version> </dependency> <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> <version>4.0.1</version> </dependency> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-dubbo-adapter</artifactId> <version>1.6.3</version> </dependency>
2.实现类:
@Service//把当前服务发布成dubbo服务
public class SentinelServiceImpl implements SentinelService {
@Override
public String sayHello(String name) {
try {
Thread.sleep(500); //为演示平均响应时间,让他睡500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("begin execute sayHello:" + name);
return "Hello World:" + name + "->timer:" + LocalDateTime.now();
}
}
3.添加Dubbo配置:
@Configuration
@DubboComponentScan("com.wuzz.demo")
public class DubboConfig {
@Bean
public ApplicationConfig applicationConfig(){
ApplicationConfig applicationConfig=new ApplicationConfig();
applicationConfig.setName("sentinel-dubbo");
applicationConfig.setOwner("wuzz");
return applicationConfig;
}
@Bean
public RegistryConfig registryConfig(){
RegistryConfig registryConfig=new RegistryConfig();
registryConfig.setAddress("zookeeper://192.168.1.101:2181");
return registryConfig;
}
@Bean
public ProtocolConfig protocolConfig(){
ProtocolConfig protocolConfig=new ProtocolConfig();
protocolConfig.setName("dubbo");
protocolConfig.setPort(20880);
return protocolConfig;
}
}
4.添加熔断拓展点:
public class DataSourceInitFunc implements InitFunc {
@Override
public void init() throws Exception {
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
//下面这个配置的意思是,当1s内持续进入5个请求,平均响应时间都超过count(10ms),
// 那么在接下来的timewindow(10s)内,对
//这个方法的调用都会自动熔断,抛出异常:degradeException.
//指定被保护的资源
rule.setResource("com.wuzz.demo.SentinelService");
//降级模式, RT(平均响应时间)、异常比例(DEGRADE_GRADE_EXCEPTION_RATIO)/异常数量
//1s内处理5个请求
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setCount(100); //阈值 5个请求平均响应时间超过100ms 触发降级
rule.setTimeWindow(10);//降级的时间单位, 单位为s
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
}
在resource/META-INF/services/com.alibaba.csp.sentinel.init.InitFunc中配置改类的全路径,这样的话sentinel在触发限流时会去调用这个initFunc来解析规则
com.wuzz.demo.DataSourceInitFunc
5.主启动类:
@SpringBootApplication
public class SentinelProviderDegradeApplication {
public static void main(String[] args) throws IOException {
initFlowRules();
SpringApplication.run(SentinelProviderDegradeApplication.class, args);
System.in.read();
}
//初始化规则
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>(); //限流规则的集合
FlowRule flowRule = new FlowRule();
flowRule.setResource("com.wuzz.demo.SentinelService:sayHello(java.lang.String)");//资源(方法名称、接口)
flowRule.setCount(1000);//限流阈值 qps=1000
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);//限流阈值类型(QPS 或并发线程数)
//流量控制手段(直接拒绝、Warm Up、匀速排队)
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
flowRule.setLimitApp("sentinel-web");//流控针对的调用来源,若为 default 则不区分调用来源
rules.add(flowRule);
FlowRuleManager.loadRules(rules);
}
}
其中qps尽量大一点,因为这里主要是想测试熔断,不然抛出来的异常就不是我们想要的了,可能被限流了。
然后启动服务端,客户端,用JMeter压测,会看到抛出异常:
我们可以捕获这个异常进行降级处理。