扩展Sentinel实现开关降级
Sentinel实现了插件功能,支持将自定义处理器插槽(ProcessorSlot)通过SPI注册到ProcessorSlotChain中,或者通过自实现SlotChainBuilder构建ProcessorSlotChain,将自定义处理器插槽注册到ProcessorSlotChain中。因此,我们可以通过自定义ProcessorSlot为Sentinel添加开关降级功能。
与限流、熔断降级等流量控制的实现一样,首先定义开关降级规则类,实现loadRulesAPI;然后提供一个Checker,由Checker判断开关是否打开,是否需要拒绝当前请求;最后自定义ProcessorSlot与SlotChainBuilder,实现拦截请求的功能。
与使用AOP实现开关降级有所不同,扩展Sentinel实现开关降级不需要在接口方法或类上添加注解,可以全部通过配置规则实现,这也是为什么选择扩展Sentinel实现开关降级功能的原因。
一个开关通常会控制很多接口,所以一个开关对应一个开关降级规则,一个开关降级规则可配置多个资源。
开关降级规则类SwitchRule的代码如下:
SwitchRule类字段说明如下:
status:开关状态,取值为open或close。
resources:开关控制的资源。
Resources内部类字段说明如下:
include:包含的资源。
exclude:排除的资源。
灵活,不仅只是不需要使用注解,还需要可以灵活地指定开关控制哪些资源,因此,配置开关控制的资源应支持以下几种情况:指定该开关只控制哪些资源(include),或控制除了哪些资源(exclude)的其他资源,或者控制全部资源。
所以,SwitchRule的资源配置与Sentinel的限流、熔断降级规则的资源配置不一样,SwitchRule支持以下3种资源配置方式:
如果不配置resources,则开关作用于全部资源。
如果配置了include,则开关作用于include指定的所有资源。
如果不配置include且配置了exclude,则除exclude指定的资源外,其他资源都受这个开关的控制。
接着实现loadRulesAPI。在Sentinel中,提供loadRulesAPI的类通常被命名为XxxRuleManager,即Xxx规则管理器,所以我们定义开关降级规则管理器的名称为SwitchRuleManager。
SwitchRuleManager的代码如下:
SwitchRuleManager提供了两个API:
loadSwitchRules:用于更新或加载开关降级规则。
getRules:获取当前生效的全部开关降级规则。
同样地,Sentinel一般会将决定规则是否达到触发开关降级的阈值由XxxRuleChecker完成,即Xxx规则检查员。所以我们定义开关降级规则检查员的名称为SwitchRuleChecker,由SwitchRuleChecker检查开关是否打开,若开关打开,则抛出SwitchException,拒绝请求。
SwitchRuleChecker的代码如下:
遍历规则,每个打开状态的开关都有可能控制当前资源,只要其中一个关联到当前资源,就拒绝当前请求。
判断开关状态,如果开关未打开,则跳过。
实现include语意,如果规则配置了include,并且include包含当前资源,则匹配成功,抛出SwitchException以拒绝当前请求。
实现exclude语意,如果规则配置了exclude,并且exclude不包含当前资源,则抛出SwitchException以拒绝当前请求。
整个checkSwitch方法实现的功能:SwitchRuleChecker从SwitchRuleManager中获取配置的开关降级规则,并遍历开关降级规则,如果开关打开,且匹配到当前资源名称被该开关控制,则抛出SwitchException。
SwitchException需继承BlockException,如果抛出的SwitchException没有被捕获,则由全局异常处理器处理。
注意:必须抛出BlockException的子类异常,否则抛出的异常会被资源指标数据统计收集,会影响到熔断降级等功能的准确性。
虽然SwitchRuleChecker使用了for循环遍历开关降级规则,但是一个项目中的开关是很少的,一般只有一个或几个,并且使用hash匹配include与exclude,时间复杂度接近O(1),所以这样实现的开关降级对性能影响并不大。
要使用开关功能,我们还需要自定义处理器插槽:SwitchSlot。
SwitchSlot类继承AbstractLinkedProcessorSlot抽象类,负责在entry方法中调用SwitchRuleChecker#checkSwitch方法,检查是否需要拒绝当前请求。
SwitchSlot类的代码如下:
现在,我们需要自定义SlotChainBuilder,即MySlotChainBuilder,将自定义的SwitchSlot添加到ProcessorSlotChain的末尾。当然,可以将SwitchSlot添加到任何位置,因为SwitchSlot没有用到资源指标数据,所以将SwitchSlot放在哪里都不会影响Sentinel的正常工作。
MySlotChainBuilder的代码如下:
如上述代码所示,MySlotChainBuilder继承DefaultSlotChainBuilder只是为了使用DefaultSlotChainBuilder#build方法。若要简化ProcessorSlotChain的构造步骤,则只需要在DefaultSlotChainBuilder构造好的链表尾部添加一个SwitchSlot即可。
但是MySlotChainBuilder还没有生效,只有将MySlotChainBuilder注册到SlotChainBuilder接口的SPI配置文件中后,MySlotChainBuilder才会生效。
具体操作为在当前项目的resources/META-INF/service资源目录下创建一个名称为com.alibaba.csp.sentinel.slotchain.SlotChainBuilder的文件,并在该文件中配置MySlotChainBuilder类的全名,代码如下:
在上述操作完成后,我们在MySlotChainBuilder#build方法中添加断点,然后启动项目,在正常情况下,程序会在该断点停下。但是因为我们并未配置开关降级规则,所以还看不到效果。
为了验证开关降级效果,我们通过硬编码方式添加一个开关配置。
创建SpringBoot配置类,并在配置类的初始化方法中调用SwitchRuleManager#loadRules API添加开关降级规则,代码如下:
上述代码配置了一个开关降级规则,配置了该规则的status字段值为open,该开关降级规则只控制接口(资源)“/v1/test/demo”。
注意:这种配置方式只适用于本地测试,在实际项目中需要通过动态配置实现。
小结
本篇主要介绍如何使用Spring AOP实现开关降级,以及扩展Sentinel实现开关降级。
使用Spring AOP实现开关降级的缺点是配置不灵活:如果让一个开关控制多个接口,则无法实现动态为开关添加或移除其管理的接口;如果让一个开关仅控制一个接口,则需要多个开关配置,不易于管理。
在不使用Sentinel的项目中,使用Spring AOP实现开关降级是一种不错的方式,而如果在项目中已经使用了Sentinel,建议采用扩展Sentinel实现开关降级,并结合动态配置,实现更为灵活的开关降级。
本文给大家讲解的内容是深度解析微服务高并发实现开关降级 :扩展Sentinel实现开关降级
下篇文章给大家讲解的内容是深度解析微服务高并发动态数据源 :实现规则动态配置的两种方式,使用Redis动态数据源
感谢大家的支持!
领取专属 10元无门槛券
私享最新 技术干货