使用Sentinel实现熔断降级
降级策略
我们通常用以下几种方式来衡量资源是否处于稳定的状态:
- 平均响应时间 (
DEGRADE_GRADE_RT
):当资源的平均响应时间超过阈值(DegradeRule
中的count
,以 ms 为单位)之后,资源进入准降级状态。接下来如果持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule
中的timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回(抛出DegradeException
)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx
来配置。 - 异常比例 (
DEGRADE_GRADE_EXCEPTION_RATIO
):当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule
中的count
)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule
中的timeWindow
,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是[0.0, 1.0]
,代表 0% - 100%。 - 异常数 (
DEGRADE_GRADE_EXCEPTION_COUNT
):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex)
记录业务异常。
针对这些规则,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,就是熔断降级部分,他与限流是可以并存的。
实战例子:
服务类:
package com.xin.sentinel.demo.service; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.SphU; 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.xin.sentinel.demo.dao.DB; import com.xin.sentinel.demo.entity.User; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @Service public class RTUserService { public static final String USER_DEGRADERULE_RES = "userDegradeRuleResource"; public static final String USER_DEGRADERATIORULE_RES = "userDegradeRATIORuleResource"; public RTUserService(){ //initDegradeRTRule(); initDegradeRATIORule(); } private static void initDegradeRTRule() { List<DegradeRule> rules = new ArrayList<DegradeRule>(); DegradeRule rule = new DegradeRule(); rule.setResource(USER_DEGRADERULE_RES); // set threshold rt, 100 ms rule.setCount(100);//资源的平均响应时间超过阈值100 ms,进入降级 rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);//平均响应时间 (DEGRADE_GRADE_RT) rule.setTimeWindow(3);//持续降级的时间窗口3秒 rules.add(rule); DegradeRuleManager.loadRules(rules); } private static void initDegradeRATIORule() { List<DegradeRule> rules = new ArrayList<DegradeRule>(); DegradeRule rule = new DegradeRule(); rule.setResource(USER_DEGRADERATIORULE_RES); // 当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)49% 之后,资源进入降级状态 rule.setCount(0.49); rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); rule.setTimeWindow(10); rules.add(rule); DegradeRuleManager.loadRules(rules); } /** * 通过 @SentinelResource 注解定义资源并配置 blockHandler 和 fallback 函数来进行限流之后的处理 * @param id * @return */ @SentinelResource(blockHandler = "blockHandlerForGetUser", fallback = "fallbackHandlerForGetUser") public User getUserByRTDegradeRule(int id) { Entry entry = null; try { entry = SphU.entry(USER_DEGRADERULE_RES); //第5个请求开始超过阀值100ms if (id>5){ TimeUnit.MILLISECONDS.sleep(150); } // 业务代码 User user = new User(); user.setId(id); user.setName("user-" + id); DB.InsertUser(user); //长耗时的工作 return user; } catch (InterruptedException e) { e.printStackTrace(); } catch (BlockException e) { e.printStackTrace(); System.out.println(e+"[getUser] has been protected! id="+ id); return new User("block user"+id); } finally { if (entry != null) { entry.exit(); } } return null; } @SentinelResource(blockHandler = "blockHandlerForGetUser", fallback = "fallbackHandlerForGetUser") public User getUserByRATIODegradeRule(int id) { Entry entry = null; try { entry = SphU.entry(USER_DEGRADERATIORULE_RES); //第5个请求开始出现异常 if (id>5){ throw new RuntimeException("throw runtime "); } // 业务代码 User user = new User(); user.setId(id); user.setName("user-" + id); DB.InsertUser(user); //长耗时的工作 return user; } catch (Exception e) { e.printStackTrace(); System.out.println(e+"[getUser] has been protected! id="+ id); } finally { if (entry != null) { entry.exit(); } } return new User("block user"+id); } // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用 public User blockHandlerForGetUser() { return new User("block user"); } public User fallbackHandlerForGetUser() { return new User("fallback user"); } }
控制器:
package com.xin.sentinel.demo.controller; import com.xin.sentinel.demo.entity.User; import com.xin.sentinel.demo.service.RTUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; import java.util.ArrayList; import java.util.List; @Controller public class RTDemo { @Autowired private RTUserService userService; @GetMapping("/getUserByRTDegradeRule") public @ResponseBody List<User> getUserByRTDegradeRule() throws InterruptedException { List<User> usersList = new ArrayList<User>(); for (int i=0;i<50;i++){ usersList.add(userService.getUserByRTDegradeRule(i)); } return usersList; } @GetMapping("/getUserByRATIODegradeRule") public @ResponseBody List<User> getUserByRATIODegradeRule() throws InterruptedException { List<User> usersList = new ArrayList<User>(); for (int i=0;i<50;i++){ usersList.add(userService.getUserByRATIODegradeRule(i)); } return usersList; } }
可以看到,在使用getUserByRTDegradeRule规则时,第16个输出为block user,即进入了超时熔断。
项目源码
关于作者:
王昕(QQ:475660)
在广州工作生活30余年。十多年开发经验,在Java、即时通讯、NoSQL、BPM、大数据等领域较有经验。
目前维护的开源产品:https://gitee.com/475660
目前维护的开源产品:https://gitee.com/475660