十九、Sentinel规则之熔断降级规则

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。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");
//设置40毫秒
rule.setCount(40);
//阈值类型为平均响应时间
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
//时间窗口设置为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
@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 "访问成功";
}
}

//Service
public interface EchoService {
String echo(String str);
}

//ServiceImpl
@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);
//时间窗口为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
//controller
@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 "访问成功";
}
}

// service
public interface EchoService {
String echo(String str, int i);
}

//serviceImpl
/**
* @author hao.ouYang
* @create 2019-10-22 11:05
*/
@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);
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×