一, Sentinel熔断降级

Sentinel除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

Sentinel的熔断降级和Hystrix对比, 请查看: Hystrix和Sentinel技术选型.

二, Sentinel以三种方式衡量被访问的资源是否处理稳定的状态

1 平均响应时间 (DEGRADE_GRADE_RT):当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。接下来如果持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException)。在下一个时间窗口到来时, 会接着再放入5个请求, 再重复上面的判断.

2 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。

3 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。

三, Sentinel以访问资源的平均响应时间RT作为降级策略

1 初始化降级规则, 设置RT : 200ms, 设置降级规则 : RuleConstant.DEGRADE_GRADE_RT, 设置时间窗口 : 10s

private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<DegradeRule>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(KEY);
        // set threshold rt, 200 ms
        rule.setCount(200);
        // 设置降级规则RT, 平均响应时间
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        // 设置时间窗口
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

2 设置资源耗时, 以sleeptime休眠时间模拟资源耗时, 刚开始时sleeptime:600ms, 而设置的RT阈值为200ms, 所以所有访问该资源的请求响应时间均超过了这个阈值. 

private static int              sleeptime   = 600;

当60s后, 修改sleeptime : 100ms, 模拟访问资源耗时降低, 这样所有的请求访问该资源时, 请求响应时间RT均没有超过阈值RT=200.

if (seconds == 40) {
                    System.out.println("===>修改资源耗时时间为100ms");
                    sleeptime = 100;
                }

3 启动threadCount个线程去访问该资源, 这里为了验证sentinel每个时间窗口都会放入5个请求去访问资源, 这里就设置threadCount = 1个线程, 能从后台很明显的看见每个时间窗口TimeWindow=10s会放入5个请求.  

public static void main(String[] args) throws Exception {

        tick();
        initDegradeRule();
        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        Entry entry = null;
                        try {
                            TimeUnit.MILLISECONDS.sleep(5);
                            entry = SphU.entry(KEY);
                            // token acquired
                            pass.incrementAndGet();
                            // sleep 600 ms, as rt
                            TimeUnit.MILLISECONDS.sleep(sleeptime);

                        } catch (Exception e) {
                            block.incrementAndGet();
                        } finally {
                            total.incrementAndGet();
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }

            });
            entryThread.setName("working-thread");
            entryThread.start();
        }
    }

4 后台console展示运行效果:

步骤一, 当资源的平均响应时间超过阈值, 资源进入准降级状态, 接着sentinel会放入5个请求;

步骤二, 当这5个请求的RT响应时间还是超过阈值, 那么在接下来的时间窗口(这里是10s)内, 对该资源的请求全部熔断, 默认抛出DegradeException异常;

步骤三, 会在下一个时间窗口前, 再次放入5个请求, 这样做的目的是, 当资源恢复正常时, Sentinel就能经历一个时间窗口, 自动恢复对该资源的正常调用, 不过最差的情况下, 至少需要等待一个时间窗口的时间才能恢复.

apigateway 熔断resilience4j与sentinel对比_Sentinel

5 dashboard实时监控展示

如下图所示: 每个时间窗口都会放入5个请求, 验证访问该资源的响应时间RT是否超过阈值, 至于后面的b_qps稳定在10左右, b_qps为0, 是因为在60s后, 访问该资源的sleeptime耗时编程了100ms, 所有对于该资源的请求响应时间均不超过阈值200ms, 所以没有block掉的请求, 请求全部正常通过.

apigateway 熔断resilience4j与sentinel对比_实时监控_02

 

四, Sentinel以访问资源的异常比例Exception_Ratio作为降级策略

1 初始化降级规则, 设置异常比例ExceptionRatio : 0.5(请求全部block, 偶数全部鲍一菜场), 设置降级规则 : RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO, 设置时间窗口 TimeWindow: 10s

private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<DegradeRule>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(KEY);
        // set limit exception ratio to 0.1
        // 将比例设置成0.6将全部通过, exception_ratio = 异常/通过量
        // 当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态
        rule.setCount(0.4);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

2 当访问该资源的每秒异常数/访问该资源时的通过数 >= 阈值(这里是0.5), 该资源就进入降级状态, 在接下来的时间窗口(这里是10s)内, 对该资源的访问请求抛出DegradeException异常, 执行catch降级逻辑.

public static void main(String[] args) throws Exception {
        tick();
        initDegradeRule();

        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {

                @Override
                public void run() {
                    int count = 0;
                    while (true) {
                        count++;
                        Entry entry = null;
                        try {
                            Thread.sleep(20);
                            entry = SphU.entry(KEY);
                            // token acquired, means pass
                            pass.addAndGet(1);
                            // 偶数抛出异常
                            if (count % 2 == 0) {
                                // biz code raise an exception.
                                throw new RuntimeException("throw runtime ");// 抛出一个业务异常,
                                                                             // 对sentinel本身的异常BlockException不生效
                            }
                        } catch (BlockException e) {
                            block.addAndGet(1);
                        } catch (Throwable t) {
                            bizException.incrementAndGet();
                            // 当使用ExceptionRatio异常比例来衡量资源是否处于稳定状态时, 需要显示的调用 Tracer.trace(t); 用于sentinel统计异常比例
                            Tracer.trace(t);
                        } finally {
                            total.addAndGet(1);
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }

            });
            entryThread.setName("working-thread");
            entryThread.start();
        }

    }

3 如下图, 展示的就是上面所描述的情况.阈值0.5, 时间窗口10s.

apigateway 熔断resilience4j与sentinel对比_List_03

4 设置异常比例阈值为ExceptionRatio : 0.6时, 则每秒异常数/每秒通过数 < 阈值(0.6)的, 所以访问该资源的请求, 全部通过, 如下图:

apigateway 熔断resilience4j与sentinel对比_实时监控_04

5 dashboard实时监控展示

如下图, 在下一个时间窗口前, 持续判断每秒异常数/每秒通过数 是否大于阈值, 是的话, 抛出DegradeException异常, 执行catch降级逻辑.

apigateway 熔断resilience4j与sentinel对比_Sentinel_05

五, Sentinel以访问资源的异常数Exception_Count作为降级策略

1 初始化降级规则, 设置时间窗口80s, 异常数大于等于4个, 就发生熔断, 该时间窗口内的请求, 抛出DegradeException异常, 执行catch降级逻辑.

private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<DegradeRule>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(KEY);
        // set limit exception count to 4
        rule.setCount(4);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
        /**
         * When degrading by {@link RuleConstant#DEGRADE_GRADE_EXCEPTION_COUNT}, time window less than 60 seconds will
         * not work as expected. Because the exception count is summed by minute, when a short time window elapsed, the
         * degradation condition may still be satisfied.
         */
        // 这里要求的时间窗口最小值最低是1m,
        // 设置时间窗口为80s, 就会判断在这个时间窗口内的异常数到了阈值, 达到该阈值就会熔断
        // 该时间窗口内的请求全部熔断, 抛出DegradeException异常, 走catch降级逻辑.
        rule.setTimeWindow(80);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

2 设置异常数阈值为4, 时间窗口80s, 下面这段代码的逻辑, 遇到偶数时就抛出一个异常

public static void main(String[] args) throws Exception {
        tick();
        initDegradeRule();

        for (int i = 0; i < threadCount; i++) {
            Thread entryThread = new Thread(new Runnable() {

                @Override
                public void run() {
                    int count = 0;
                    while (true) {
                        count++;
                        Entry entry = null;
                        try {
                            Thread.sleep(20);
                            entry = SphU.entry(KEY);
                            // token acquired, means pass
                            pass.addAndGet(1);
                            if (count % 2 == 0) {
                                // biz code raise an exception.
                                throw new RuntimeException("throw runtime "); // 抛出一个业务异常,
                            }
                        } catch (BlockException e) {
                            block.addAndGet(1);
                        } catch (Throwable t) {
                            bizException.incrementAndGet();
                            // 当使用ExceptionCount异常数来衡量资源是否处于稳定状态时, 需要显示的调用 Tracer.trace(t); 用户sentinel统计异常数
                            Tracer.trace(t);
                        } finally {
                            total.addAndGet(1);
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }
            });
            entryThread.setName("working-thread");
            entryThread.start();
        }
    }

3 后台console展示运行效果:

当该时间窗口内的异常数达到阈值4时, 直接熔断, 所有请求将抛出DegradeException异常, 执行catch降级逻辑.

apigateway 熔断resilience4j与sentinel对比_响应时间_06

4 dashboard实时监控展示

每个时间窗口80s, 都会去判断异常数是否达到了阈值4, 达到了话, 所有请求直接熔断, 所以下图的时间窗口内p_qps=0, 没有请求通过资源.

apigateway 熔断resilience4j与sentinel对比_响应时间_07

六, 总结

判断一个资源是够处于稳定状态, 1是访问该资源时的平均响应时间RT, 2是每秒异常数占通过数比例ExceptionRatio, 3是时间窗口内的异常数ExceptionCount, 根据上面3种情况, 相应的的Sentinel提供了3种方式的熔断降级策略:

1 DEGRADE_GRADE_RT, 基于响应时间的熔断降级

2 DEGRADE_GRADE_EXCEPTION_RATIO, 基于异常比例的熔断降级策略

3 DEGRADE_GRADE_EXCEPTION_COUNT, 基于异常数的熔断降级策略