1. 程式人生 > >sentinel之三種熔斷降級情況的驗證

sentinel之三種熔斷降級情況的驗證

熔斷降級是指當資源處於不穩定的情況下,在接下來的時間視窗之內,對該資源的呼叫都自動熔斷(預設行為是丟擲DegradeException)。
通常用三種方式來衡量資源是否處於穩定的狀態:

  • 平均響應時間 (DEGRADE_GRADE_RT):當資源的平均響應時間超過閾值(DegradeRule 中的 count,以 ms 為單位)之後,資源進入準降級狀態。接下來如果持續進入 5 個請求,它們的 RT 都持續超過這個閾值,那麼在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這個方法的呼叫都會自動地返回(丟擲 DegradeException)。

  • 異常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):當資源的每秒異常總數佔通過量的比值超過閾值(DegradeRule 中的 count)之後,資源進入降級狀態,即在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這個方法的呼叫都會自動地返回。異常比率的閾值範圍是 [0.0, 1.0],代表 0% - 100%。

  • 異常數 (DEGRADE_GRADE_EXCEPTION_COUNT):當資源近 1 分鐘的異常數目超過閾值之後會進行熔斷。

注意:為了統計異常比例或異常數,需要通過 Tracer.trace(ex) 記錄業務異常。


下面對這三種情況做簡單的驗證

  • 匯入Maven依賴
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.3.0-GA</version>
</dependency>

點選這裡可查詢最新的sentinel-core版本
1、驗證平均響應時間

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU; 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.util.TimeUtil; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * 降級是在資源處於不穩定狀態時使用的,這些資源將在下一個定義的時間視窗內降級。 * 有三種方法衡量資源是否穩定: * 平均響應時間 (DEGRADE_GRADE_RT):當資源的平均響應時間超過閾值(DegradeRule 中的 count,以 ms 為單位)之後, * 資源進入準降級狀態。接下來如果持續進入 5 個請求,它們的 RT 都持續超過這個閾值, * 那麼在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內, * 對這個方法的呼叫都會自動地返回(丟擲 DegradeException) * **異常比率,見 {@link ExceptionRatioDegradeDemo}. * **異常統計,見 {@link ExceptionCountDegradeDemo}. */ public class RtDegradeDemo { private static final String KEY = "平均響應時間"; private static AtomicInteger pass = new AtomicInteger(); private static AtomicInteger block = new AtomicInteger(); private static AtomicInteger total = new AtomicInteger(); private static volatile boolean stop = false; private static final int threadCount = 100; private static int seconds = 60; public static void main(String[] args) throws Exception { tick(); initDegradeRule();// 設定資源降級規則 for (int i = 0; i < threadCount; i++) { Thread entryThread = new Thread(new Runnable() { int j = 0; @Override public void run() { while (true) { Entry entry = null; try { TimeUnit.MILLISECONDS.sleep(5); entry = SphU.entry(KEY); // 令牌已獲得 pass.incrementAndGet();// 先+1,然後在返回值,相當於++i // sleep 600 ms, as rt TimeUnit.MILLISECONDS.sleep(600); } catch (Exception e) { // System.out.println("異常:"+e); // 降級計數 block.incrementAndGet();// 先+1,然後在返回值,相當於++i } finally { // 記錄總數 total.incrementAndGet();// 先+1,然後在返回值,相當於++i if (entry != null) { entry.exit(); } } } } }); entryThread.setName("working-thread"); entryThread.start(); } } /** * 當資源的平均響應時間超過閾值(DegradeRule 中的 count,以 ms 為單位)之後,資源進入準降級狀態。 * 接下來如果持續進入 5 個請求,它們的 RT 都持續超過這個閾值, * 那麼在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內, * 對這個方法的呼叫都會自動地返回(丟擲 DegradeException)。 */ private static void initDegradeRule() { List<DegradeRule> rules = new ArrayList<DegradeRule>(); DegradeRule rule = new DegradeRule(); rule.setResource(KEY);// 設定資源名,資源名是限流規則的作用物件 rule.setCount(10);// 設定平均響應時間閾值為10ms rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);// 設定降級模式,根據平均響應時間降級 rule.setTimeWindow(10);// 設定降級的時間,以s為單位 rules.add(rule); DegradeRuleManager.loadRules(rules); } private static void tick() { Thread timer = new Thread(new TimerTask()); timer.setName("sentinel-timer-task"); timer.start(); } static class TimerTask implements Runnable { @Override public void run() { long start = System.currentTimeMillis(); System.out.println("統計開始......"); long oldTotal = 0; long oldPass = 0; long oldBlock = 0; SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String nowTime = ""; while (!stop) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { } long globalTotal = total.get(); long oneSecondTotal = globalTotal - oldTotal; oldTotal = globalTotal; long globalPass = pass.get(); long oneSecondPass = globalPass - oldPass; oldPass = globalPass; long globalBlock = block.get(); long oneSecondBlock = globalBlock - oldBlock; oldBlock = globalBlock; nowTime = formatter.format(new java.util.Date(TimeUtil.currentTimeMillis())); System.out.println(nowTime + ", total:" + oneSecondTotal + ", pass:" + oneSecondPass + ", block:" + oneSecondBlock); if (seconds-- <= 0) { stop = true; } } long cost = System.currentTimeMillis() - start; System.out.println("耗時:" + cost + " ms"); System.out.println("總計數:" + total.get() + ", 通過數:" + pass.get() + ", 降級數:" + block.get()); System.out.println("統計結束!"); System.exit(0); } } }

執行結果如下:

統計開始......
2018-12-10 16:42:26, total:2387, pass:104, block:2288
2018-12-10 16:42:27, total:15446, pass:0, block:15442
2018-12-10 16:42:28, total:17507, pass:0, block:17506
2018-12-10 16:42:29, total:17939, pass:0, block:17940
2018-12-10 16:42:30, total:17731, pass:0, block:17730
2018-12-10 16:42:31, total:17800, pass:0, block:17800
2018-12-10 16:42:32, total:17973, pass:0, block:17975
2018-12-10 16:42:33, total:17814, pass:0, block:17812
2018-12-10 16:42:34, total:18000, pass:0, block:18000
2018-12-10 16:42:35, total:17832, pass:0, block:17832
2018-12-10 16:42:36, total:14944, pass:100, block:14944
2018-12-10 16:42:37, total:9700, pass:4, block:9600
2018-12-10 16:42:38, total:18063, pass:0, block:18059
2018-12-10 16:42:39, total:17828, pass:0, block:17828
2018-12-10 16:42:40, total:18000, pass:0, block:18000
2018-12-10 16:42:41, total:17800, pass:0, block:17800
2018-12-10 16:42:42, total:17900, pass:0, block:17900
2018-12-10 16:42:43, total:17800, pass:0, block:17800
2018-12-10 16:42:44, total:17890, pass:0, block:17890
2018-12-10 16:42:45, total:17908, pass:0, block:17908
2018-12-10 16:42:46, total:17868, pass:0, block:17871
2018-12-10 16:42:47, total:7830, pass:100, block:7827
2018-12-10 16:42:48, total:16856, pass:4, block:16752
2018-12-10 16:42:49, total:17963, pass:0, block:17965
2018-12-10 16:42:50, total:17541, pass:0, block:17539
2018-12-10 16:42:51, total:17600, pass:0, block:17600
2018-12-10 16:42:52, total:17770, pass:0, block:17770
2018-12-10 16:42:53, total:17400, pass:0, block:17400
2018-12-10 16:42:54, total:17753, pass:0, block:17755
2018-12-10 16:42:55, total:17642, pass:0, block:17640
2018-12-10 16:42:56, total:17796, pass:0, block:17796
2018-12-10 16:42:57, total:17884, pass:0, block:17887
2018-12-10 16:42:58, total:6872, pass:104, block:6769
2018-12-10 16:42:59, total:17720, pass:0, block:17716
2018-12-10 16:43:00, total:17889, pass:0, block:17892
2018-12-10 16:43:01, total:17611, pass:0, block:17608
2018-12-10 16:43:02, total:17851, pass:0, block:17851
2018-12-10 16:43:03, total:17900, pass:0, block:17900
2018-12-10 16:43:04, total:17800, pass:0, block:17800
2018-12-10 16:43:05, total:17800, pass:0, block:17800
2018-12-10 16:43:06, total:17900, pass:0, block:17900
2018-12-10 16:43:07, total:17800, pass:0, block:17800
2018-12-10 16:43:08, total:11600, pass:100, block:11600
2018-12-10 16:43:09, total:12876, pass:4, block:12772
2018-12-10 16:43:10, total:17896, pass:0, block:17896
2018-12-10 16:43:11, total:17981, pass:0, block:17983
2018-12-10 16:43:12, total:17819, pass:0, block:17817
2018-12-10 16:43:13, total:17800, pass:0, block:17800
2018-12-10 16:43:14, total:17976, pass:0, block:17977
2018-12-10 16:43:15, total:17707, pass:0, block:17708
2018-12-10 16:43:16, total:17881, pass:0, block:17883
2018-12-10 16:43:17, total:17832, pass:0, block:17828
2018-12-10 16:43:18, total:17900, pass:0, block:17900
2018-12-10 16:43:19, total:6974, pass:104, block:6892
2018-12-10 16:43:20, total:17497, pass:0, block:17476
2018-12-10 16:43:21, total:17666, pass:0, block:17665
2018-12-10 16:43:22, total:18000, pass:0, block:18000
2018-12-10 16:43:23, total:17900, pass:0, block:17900
2018-12-10 16:43:24, total:17800, pass:0, block:17800
2018-12-10 16:43:25, total:17894, pass:0, block:17894
2018-12-10 16:43:26, total:17765, pass:0, block:17765
耗時:61080 ms
總計數:1014072, 通過數:624, 降級數:1013448
統計結束!

Process finished with exit code 0

2、驗證異常比例

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
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.util.TimeUtil;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 降級是在資源處於不穩定狀態時使用的,這些資源將在下一個定義的時間視窗內降級。
 * 有三種方法衡量資源是否穩定:
 * 異常比率:當異常計數/秒與成功的比率qps大於或等於閾值時,對資源的訪問將被阻塞在即將到來的時間視窗。
 * **異常統計,見 {@link ExceptionCountDegradeDemo}.
 * **平均響應時間, 見 {@link RtDegradeDemo}.
 */
public class ExceptionRatioDegradeDemo {
    private static final String KEY = "異常比率";

    private static AtomicInteger total = new AtomicInteger();
    private static AtomicInteger pass = new AtomicInteger();
    private static AtomicInteger block = new AtomicInteger();
    private static AtomicInteger bizException = new AtomicInteger();

    private static volatile boolean stop = false;
    private static final int threadCount = 1;
    private static int seconds = 60 + 40;

    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);
                            // 獲得令牌表示通過
                            pass.addAndGet(1);// 先+n,然後在返回值
                            if (count % 2 == 0) {
                                // 業務程式碼引發異常
                                throw new RuntimeException("throw runtime exception");
                            }
                        } catch (BlockException e) {
                            // 降級計數
                            block.addAndGet(1);// 先+n,然後在返回值
                        } catch (Throwable t) {
                            // 記錄業務異常
                            bizException.incrementAndGet();// 先+1,然後在返回值,相當於++i
                            // 為了統計異常比例或異常數,需要通過 Tracer.trace(ex) 記錄業務異常。
                            Tracer.trace(t);
                        } finally {
                            // 記錄總數
                            total.addAndGet(1);// 先+n,然後在返回值
                            if (entry != null) {
                                entry.exit();
                            }
                        }
                    }
                }

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

    }

    /**
     * 當資源的每秒異常總數佔通過量的比值超過閾值(DegradeRule 中的 count)之後,
     * 資源進入降級狀態,即在接下的時間視窗(DegradeRule 中的 timeWindow,以 s 為單位)之內,對這個方法的呼叫都會自動地返回。
     * 異常比率的閾值範圍是 [0.0, 1.0],代表 0% - 100%。
     */
    private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<DegradeRule>();
        DegradeRule rule = new DegradeRule();
        rule.setResource(KEY);// 設定資源名,資源名是限流規則的作用物件
        // 將限制異常比率設定為0.1
        rule.setCount(0.5);// 異常比率的閾值範圍是 [0.0, 1.0],代表 0% - 100%。
        rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);// 設定降級模式,根據異常比率降級
        rule.setTimeWindow(10);// 設定降級的時間,以s為單位
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

    private static void tick() {
        Thread timer = new Thread(new TimerTask());
        timer.setName("sentinel-timer-task");
        timer.start();
    }

    static class TimerTask implements Runnable {
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            System.out.println("統計開始......");
            long oldTotal = 0;
            long oldPass = 0;
            long oldBlock = 0;
            long oldBizException = 0;

            SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String nowTime = "";

            while (!stop) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                }
                long globalTotal = total.get();
                long oneSecondTotal = globalTotal - oldTotal;
                oldTotal = globalTotal;

                long globalPass = pass.get();
                long oneSecondPass = globalPass - oldPass;
                oldPass = globalPass;

                long globalBlock = block.get();
                long oneSecondBlock = globalBlock - oldBlock;
                oldBlock = globalBlock;

                long globalBizException = bizException.get();
                long oneSecondBizException = globalBizException - oldBizException;
                oldBizException = globalBizException;

                nowTime = formatter.format(new java.util.Date(TimeUtil.currentTimeMillis()));
                System.out.println(nowTime + ", oneSecondTotal:" + oneSecondTotal
                        + ", oneSecondPass:" + oneSecondPass
                        + ", oneSecondBlock:" + oneSecondBlock
                        + ", oneSecondBizException:" + oneSecondBizException);
                if (seconds-- <= 0) {
                    stop = true;
                }
            }
            long cost = System.currentTimeMillis() - start;
            System.out.println("耗時:" + cost + " ms");
            System.out.println("總計數:" + total.get() + ", 通過數:" + pass.get()
                    + ", 降級數:" + block.get() + ", 業務異常數:" + bizException.get());
            System.out.println("統計結束!");
            System.exit(0);
        }
    }
}

執行結果如下:

統計開始......
2018-12-10 17:02:04, oneSecondTotal:37, oneSecondPass