除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException
)。
降级策略
我们通常用以下几种方式来衡量资源是否处于稳定的状态:
- 平均响应时间 (
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 限流降级本身的异常(BlockException
)不生效。为了统计异常比例或异常数,需要通过 Tracer.trace(ex)
记录业务异常。示例:
降级演示
平均响应时间RT
初始化降级规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public static void main(String[] args) { SpringApplication.run(SentinelDegradeRuleApplication.class, args); initDegradeRuleForRT(); }
public static void initDegradeRuleForRT(){ List<DegradeRule> rules = new ArrayList<> (); DegradeRule rule = new DegradeRule(); rule.setResource("echo"); rule.setCount(40); rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); rule.setTimeWindow(5); rules.add(rule); DegradeRuleManager.loadRules(rules); }
|
程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| @RestController public class EchoController { @Autowired private EchoService echoService; @GetMapping("/echo/{str}") public String echo(@PathVariable String str){ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); for (int i = 1; i <= 10; i++) { try { String echo = echoService.echo(str); System.out.println("第"+i+"次: echo:"+echo+" | 时间:"+dateFormat.format(new Date())); TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } return "访问成功"; } }
public interface EchoService { String echo(String str); }
@Service public class EchoServiceImpl implements EchoService { @Override @SentinelResource(value = "echo",blockHandler = "handleBlockException") public String echo(String str) { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } return "返回值:"+str; }
public String handleBlockException(String str, BlockException ex){ return " 服务降级处理:"+str+" 异常为:"+ex.getClass().getSimpleName(); } }
|
访问:
1 2 3 4 5 6 7 8 9 10
| 第1次: echo:返回值:123 | 时间:2019-10-23 14:13:02:809 第2次: echo:返回值:123 | 时间:2019-10-23 14:13:02:859 第3次: echo:返回值:123 | 时间:2019-10-23 14:13:02:910 第4次: echo:返回值:123 | 时间:2019-10-23 14:13:02:960 第5次: echo:返回值:123 | 时间:2019-10-23 14:13:03:010 第6次: echo: 服务降级处理:123 异常为:DegradeException | 时间:2019-10-23 14:13:03:049 第7次: echo: 服务降级处理:123 异常为:DegradeException | 时间:2019-10-23 14:13:03:049 第8次: echo: 服务降级处理:123 异常为:DegradeException | 时间:2019-10-23 14:13:03:050 第9次: echo: 服务降级处理:123 异常为:DegradeException | 时间:2019-10-23 14:13:03:050 第10次: echo: 服务降级处理:123 异常为:DegradeException | 时间:2019-10-23 14:13:03:050
|
通过程序可以看出,平均响应时间(RT
)是先计算前5次的请求的平均处理时间,如果超过了预定的阈值时间(count
),那么在接下来的时间范围/窗口(timeWindow
)后直接进行服务降级,抛出DegradeException
. 等待timeWindow
过后,又会重新计算RT。
我们看一下实时监控:
异常比例
初始化规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void initDegradeRuleForExceptionRatio(){ List<DegradeRule> rules = new ArrayList<>(); DegradeRule rule = new DegradeRule(); rule.setResource("echo"); rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); rule.setCount(0.5); rule.setTimeWindow(5); rules.add(rule); DegradeRuleManager.loadRules(rules); }
|
测试程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| @RestController public class EchoController {
@Autowired private EchoService echoService;
@GetMapping("/echo/{str}") public String echo(@PathVariable String str) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); for (int i = 1; i <= 10; i++) { String echo = echoService.echo(str, i); System.out.println("第" + i + "次: echo:" + echo + " | 时间:" + dateFormat.format(new Date())); } try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } return "访问成功"; } }
public interface EchoService { String echo(String str, int i); }
@Service public class EchoServiceImpl implements EchoService {
@Override @SentinelResource(value = "echo",blockHandler = "handleBlockException",fallback = "fallbackFun") public String echo(String str,int i) { try { TimeUnit.MILLISECONDS.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } if ( i % 2 == 1){ throw new IRuleException(); } return "返回值:"+str; }
public String handleBlockException(String str,int i, BlockException ex){ return " 服务降级处理:"+str+" 参数i:"+i+" 异常为:"+ex.getClass().getSimpleName(); }
public String fallbackFun(String str,int i, Throwable throwable){ return " 服务降级处理:"+str+" 参数i:"+i+" 异常为:"+throwable.getClass().getSimpleName(); } }
|
测试结果:
总结:一秒内要保证访问数量超过5次,否则不会出发异常比例的服务熔断降级。比例超过预定的0.5之后就会触发降级
异常数
异常数是统计1分钟时间的内,所以当时间窗口包含在统计时间内,可能一分钟之后又会立即进入统计时间范围中 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void initDegradeRuleForExceptionCount(){ List<DegradeRule> rules = new ArrayList<>(); DegradeRule degradeRule = new DegradeRule(); degradeRule.setCount(5); degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); degradeRule.setResource("echo"); degradeRule.setTimeWindow(10); rules.add(degradeRule); DegradeRuleManager.loadRules(rules); }
|