你能區分熔斷和降級有什麼區別嗎?

2023年04月28日20:47:10 財經 1906

熔斷和降級都是系統自我保護的一種機制,但二者又有所不同,它們的區別主要體現在以下幾點:

  1. 概念不同
  2. 觸發條件不同
  3. 歸屬關係不同

1.概念不同

1.1 熔斷概念

「熔斷」一詞早期來自股票市場。熔斷(Circuit Breaker)也叫自動停盤機制,是指當股指波幅達到規定的熔斷點時,交易所為控制風險採取的暫停交易措施。比如 2020 年 3 月 9 日,紐約股市開盤出現暴跌,隨後跌幅達到 7% 上限,觸發熔斷機制,停止交易 15 分中,恢復交易後跌幅有所減緩。

熔斷在程序中,表示「斷開」的意思。如發生了某事件,程序為了整體的穩定性,所以暫時(斷開)停止服務一段時間,以保證程序可用時再被使用。

如果沒有熔斷機制的話,會導致聯機故障和服務雪崩等問題,如下圖所示:

你能區分熔斷和降級有什麼區別嗎? - 天天要聞

1.2 降級概念

降級(Degradation)降低級別的意思,它是指程序在出現問題時,仍能保證有限功能可用的一種機制。

比如電商交易系統在雙 11 時,使用的人比較多,此時如果開放所有功能,可能會導致系統不可用,所以此時可以開啟降級功能,優先保證支付功能可用,而其他非核心功能,如評論、物流、商品介紹等功能可以暫時關閉。

所以,從上述信息可以看出:降級是一種退而求其次的選擇,而熔斷卻是整體不可用

2.觸發條件不同

不同框架的熔斷和降級的觸發條件是不同的,本文咱們以經典的 Spring Cloud 組件 Hystrix 為例,來說明觸發條件的問題。

2.1 Hystrix 熔斷觸發條件

默認情況 hystrix 如果檢測到 10 秒內請求的失敗率超過 50%,就觸發熔斷機制。之後每隔 5 秒重新嘗試請求微服務,如果微服務不能響應,繼續走熔斷機制。如果微服務可達,則關閉熔斷機制,恢復正常請求。

你能區分熔斷和降級有什麼區別嗎? - 天天要聞

2.2 Hystrix 降級觸發條件

默認情況下,hystrix 在以下 4 種條件下都會觸發降級機制:

  1. 方法拋出 HystrixBadRequestException
  2. 方法調用超時
  3. 熔斷器開啟攔截調用
  4. 線程池或隊列或信號量已滿

雖然 hystrix 組件的觸發機制,不能代表所有的熔斷和降級機制,但足矣說明此問題。

3.歸屬關係不同

**熔斷時可能會調用降級機制,而降級時通常不會調用熔斷機制。**因為熔斷是從全局出發,為了保證系統穩定性而停用服務,而降級是退而求其次,提供一種保底的解決方案,所以它們的歸屬關係是不同(熔斷 > 降級)。

題外話

當然,某些框架如 Sentinel,它早期在 Dashboard 控制台中可能叫「降級」,但在新版中新版本又叫「熔斷」,如下圖所示:

你能區分熔斷和降級有什麼區別嗎? - 天天要聞

但在兩個版本中都是通過同一個異常類型 DegradeException 來監聽的,如下代碼所示:

你能區分熔斷和降級有什麼區別嗎? - 天天要聞

所以,在 Sentinel 中,熔斷和降級功能指的都是同一件事,也側面證明了「熔斷」和「降級」概念的相似性。但我們要知道它們本質上是不同的,就像兩個雙胞胎,不能因為他們長得像,就說他們是同一個人。


你能區分熔斷和降級有什麼區別嗎? - 天天要聞

當用戶請求 A、P、H、I 四個服務獲取數據時,在正常流量下系統穩定運行,如果某天系統進來大量流量,其中服務 I 出現 CPU、內存佔用過高等問題,結果導致服務 I 出現延遲、響應過慢,隨着請求的持續增加,服務 I 承受不住壓力導致內部錯誤或資源耗盡,一直不響應,此時更糟糕的是其他服務對 I 有依賴,那麼這些依賴 I 的服務一直等待 I 的響應,也會出現請求堆積、資源佔用,慢慢擴散到所有微服務,引發雪崩效應。

基本的容錯模式

常見的容錯模式主要包含以下幾種方式:

1.主動超時:Http請求主動設置一個超時時間,超時就直接返回,不會造成服務堆積

2.限流:限制最大並發數

3.熔斷:當錯誤數超過閾值時快速失敗,不調用後端服務,同時隔一定時間放幾個請求去重試後端服務是否能正常調用,如果成功則關閉熔斷狀態,失敗則繼續快速失敗,直接返回。(此處有個重試,重試就是彈性恢復的能力)

4.隔離:把每個依賴或調用的服務都隔離開來,防止級聯失敗引起整體服務不可用

5.降級:服務失敗或異常後,返回指定的默認信息

你能區分熔斷和降級有什麼區別嗎? - 天天要聞

服務降級

由於爆炸性的流量衝擊,對一些服務進行有策略的放棄,以此緩解系統壓力,保證目前主要業務的正常運行。它主要是針對非正常情況下的應急服務措施:當此時一些業務服務無法執行時,給出一個統一的返回結果。

服務熔斷

熔斷這一概念來源於電子工程中的斷路器(Circuit Breaker)。在互聯網系統中,當下游服務因訪問壓力過大而響應變慢或失敗,上游服務為了保護系統整體的可用性,可以暫時切斷對下游服務的調用。

服務熔斷與服務降級比較

服務熔斷對服務提供了proxy,防止服務不可能時,出現串聯故障(cascading failure),導致雪崩效應。

服務熔斷一般是某個服務(下游服務)故障引起,而服務降級一般是從整體負荷考慮

1.共性:

目的 -> 都是從可用性、可靠性出發,提高系統的容錯能力。

最終表現->使某一些應用不可達或不可用,來保證整體系統穩定。

粒度 -> 一般都是服務級別,但也有細粒度的層面:如做到數據持久層、只許查詢不許增刪改等。

自治 -> 對其自治性要求很高。都要求具有較高的自動處理機制。

2.區別:

觸發原因 -> 服務熔斷通常是下級服務故障引起;服務降級通常為整體系統而考慮。

管理目標 -> 熔斷是每個微服務都需要的,是一個框架級的處理;而服務降級一般是關注業務,對業務進行考慮,抓住業務的層級,從而決定在哪一層上進行處理:比如在IO層,業務邏輯層,還是在外圍進行處理。

實現方式 -> 代碼實現中的差異。

服務熔斷恢復需注意的問題

如果服務是冪等性的,則恢復重試不會有問題;而如果服務是非冪等性的,則重試會導致數據出現問題。

Java 實現限流

1 滑動窗口

public class WindowLimiterComponent implements LimiterComponent {

    /**
     * 隊列id和隊列的映射關係,隊列裏面存儲的是每一次通過時候的時間戳,這樣可以使得程序里有多個限流隊列
     */
    private final Map<String, List<Long>> MAP = new ConcurrentHashMap<>();

    /**
     * 限制次數
     */
    private final int count;

    /**
     * 時間窗口大小
     */
    private final long timePeriod;

    public WindowLimiterComponent(int count, long timePeriod) {
        this.count = count;
        this.timePeriod = timePeriod;
    }

    /**
     * 滑動時間窗口限流算法
     * 在指定時間窗口,指定限制次數內,是否允許通過
     *
     * @param id 隊列id
     * @return 是否被限流
     */
    @Override
    public synchronized boolean isLimited(String id) {
        // 獲取當前時間
        long nowTime = System.currentTimeMillis();
        // 根據隊列id,取出對應的限流隊列,若沒有則創建
        List<Long> list = MAP.computeIfAbsent(id, k -> new LinkedList<>());
        // 如果隊列還沒滿,則允許通過,並添加當前時間戳到隊列開始位置
        if (list.size() < count) {
            list.add(0, nowTime);
            return false;
        }
        // 隊列已滿(達到限制次數),則獲取隊列中最早添加的時間戳
        Long lastTime = list.get(count - 1);
        // 用當前時間戳 減去 最早添加的時間戳
        if (nowTime - lastTime <= timePeriod) {
            // 若結果小於等於timePeriod,則說明在timePeriod內,通過的次數大於count
            // 不允許通過
            return true;
        } else {
            // 若結果大於timePeriod,則說明在timePeriod內,通過的次數小於等於count
            // 允許通過,並刪除最早添加的時間戳,將當前時間添加到隊列開始位置
            list.remove(count - 1);
            list.add(0, nowTime);
            return false;
        }
    }

}
複製代碼
    @Test
    public void test() throws InterruptedException {
        // 任意10秒內,只允許2次通過
        LimiterComponent component = new WindowLimiterComponent(2, 10000L);
        while (true) {

            System.out.println(LocalTime.now().toString() + component.isLimited("1"));
            // 睡眠0-10秒
            Thread.sleep(1000 * new Random().nextInt(10));
        }
    }
複製代碼

2 Redis zset

public class RedisZSetLimiterComponent implements LimiterComponent{

    private final redissonComponent redissonComponent;

    /**
     * 限制次數
     */
    private final int count;

    /**
     * 時間窗口大小,單位毫秒
     */
    private final long timePeriod;


    public RedisZSetLimiterComponent(RedissonComponent component) {
        this.redissonComponent = component;
        this.count = 5;
        this.timePeriod = 1000;
    }

    public RedisZSetLimiterComponent(RedissonComponent component, int count, long timePeriod) {
        this.redissonComponent = component;
        this.count = count;
        this.timePeriod = timePeriod;
    }

    /**
     * 基於 zset 的滑動時間窗口限流算法
     * 在指定時間窗口,指定限制次數內,是否允許通過
     *
     * @param key 隊列key
     * @return 是否允許通過
     */
    @Override
    public synchronized boolean isLimited(String key) {
        // 獲取當前時間
        long nowTime = System.currentTimeMillis();
        RScoredSortedSet<String> set = redissonComponent.getRScoredSortedSet(key);
        // 移除一個時間段以前的
        set.removeRangeByScore(0, true, (double) (nowTime - timePeriod), true);
        // 獲取集合內元素總數
        int size = set.count((double) (nowTime - timePeriod), true, nowTime, true);
        // 如果隊列沒滿
        if (size < count) {
            // 當前時間加入集合
            set.add((double) nowTime, String.valueOf(nowTime));
            return false;
        }
        return true;
    }


}
複製代碼
    @Test
    public void test() throws InterruptedException {
        // 任意10秒內,只允許2次通過
        LimiterComponent component = new RedisZSetLimiterComponent(redissonComponent, 2, 10000L);
        while (true) {

            System.out.println(LocalTime.now().toString() + component.isLimited("1"));
            // 睡眠0-10秒
            Thread.sleep(1000 * new Random().nextInt(10));
        }
    }
複製代碼

3 guava RateLimiter

@SuppressWarnings("UnstableApiUsage")
public class GuavaLimiterComponent implements LimiterComponent {

    private final int count;

    private final long timePeriod;

    private final Map<String, RateLimiter> MAP = new ConcurrentHashMap<>();

    public GuavaLimiterComponent(int count, long timePeriod) {
        this.count = count;
        this.timePeriod = timePeriod;
    }

    /**
     * 令牌桶算法
     *
     * @param key 鍵值
     * @return 是否被限流
     */
    @Override
    public synchronized boolean isLimited(String key) {
        RateLimiter rateLimiter = MAP.computeIfAbsent(key, k -> RateLimiter.create(count, timePeriod, TimeUnit.MILLISECONDS));
        return !rateLimiter.tryAcquire();
    }


}
複製代碼
    @Test
    public void test() throws InterruptedException {
        // 任意10秒內,只允許2次通過
        LimiterComponent component = new GuavaLimiterComponent(2, 10000L);
        while (true) {
            System.out.println(LocalTime.now().toString() + component.isLimited("1"));
            // 睡眠0-10秒
            Thread.sleep(1000 * new Random().nextInt(10));
        }
    }
複製代碼

4 redisson RRateLimiter

public class RedisRateLimiterComponent implements LimiterComponent {

    private final RedissonComponent redissonComponent;

    /**
     * 限制次數
     */
    private final int count;

    /**
     * 時間窗口大小,單位毫秒
     */
    private final long timePeriod;


    public RedisRateLimiterComponent(RedissonComponent component) {
        this.redissonComponent = component;
        this.count = 5;
        this.timePeriod = 1000;
    }

    public RedisRateLimiterComponent(RedissonComponent component, int count, long timePeriod) {
        this.redissonComponent = component;
        this.count = count;
        this.timePeriod = timePeriod;
    }

    /**
     * 基於 rateLimiter 的滑動時間窗口限流算法
     * 在指定時間窗口,指定限制次數內,是否允許通過
     *
     * @param key 隊列key
     * @return 是否允許通過
     */
    @Override
    public synchronized boolean isLimited(String key) {
        RRateLimiter rateLimiter = redissonComponent.getRateLimiter(key);
        rateLimiter.trySetRate(RateType.PER_CLIENT, count, timePeriod, RateIntervalUnit.MILLISECONDS);
        return !rateLimiter.tryAcquire();
    }


}
複製代碼
    @Test
    public void test() throws InterruptedException {
        // 任意10秒內,只允許2次通過
        LimiterComponent component = new RedisRateLimiterComponent(redissonComponent, 2, 10000L);
        while (true) {

            System.out.println(LocalTime.now().toString() + component.isLimited("1"));
            // 睡眠0-10秒
            Thread.sleep(1000 * new Random().nextInt(10));
        }
    }


總結

熔斷和降級都是程序在我保護的一種機制,但二者在概念、觸發條件、歸屬關係上都是不同的。熔斷更偏向於全局視角的自我保護(機制),而降級則偏向於具體模塊「退而請其次」的解決方案。



財經分類資訊推薦

7月A股關注哪些風險? - 天天要聞

7月A股關注哪些風險?

來源:市場資訊來源:國際金融報記者 朱燈花7月1日,A股市場表現分化,與前幾日態勢有所不同,滬市跑贏深市,但整體漲跌幅較為溫和。受訪人士表示,本月需重點關注政治局會議、中報業績,以及國際環境與地緣風險的擾動。滬市跑贏深市7月的第一個交易日,
數據復盤 7月1日 - 天天要聞

數據復盤 7月1日

轉自:EarlETF7月1日,萬得全A指數漲跌幅為+0.27%,成交額為14965.31億,相較上一交易日變動為-208.39億。7月1日,主要市場指數中,表現最好的是中證紅利,漲跌幅為+0.81%,表現最差的是科創50,漲跌幅為-0.86
傳音控股悄然布局電摩業務 非洲手機市場遭遇小米、三星等勁敵 - 天天要聞

傳音控股悄然布局電摩業務 非洲手機市場遭遇小米、三星等勁敵

每經記者:王晶 每經編輯:文多「非洲之王」傳音控股正積極尋找手機之外的新增長點。近日有媒體報道,傳音控股(SH688036,股價77.80元,市值887.2億元)已成立出行事業部,探索兩輪電動車等相關業務,公司正在非洲等地區的發展中國家鋪開兩輪電動車業務。日前,記者聯繫傳音控股方面核實並了解情況,相關工作人員僅回應稱...
香港豪門鄭氏家族旗下地產公司,等來882億港元「救命錢」! - 天天要聞

香港豪門鄭氏家族旗下地產公司,等來882億港元「救命錢」!

6月2日,香港地產豪門鄭裕彤家族旗下的新世界發展(00017.HK)突發變故,遭遇「股債雙殺」。導火索是公司決定對四筆永續債延遲利息支付,其總規模為34億美元,今年6月為原定分派支付日期,合計要支付利息7720萬美元。 永續債延遲派息,不會引發違約,但可能使債務利率增加,並影響與銀行的再融資談判。這一消息也讓這家老牌...
溢價率35.71%!陝西民企殺入三亞拿地! - 天天要聞

溢價率35.71%!陝西民企殺入三亞拿地!

2025年上半年最後一天,三亞上演了土拍大戰。位於三亞、陵水的4宗地,均以溢價形式成交,就連不到5畝的小幅地塊,開發商也不放過。值得關注的是,陝西的一家民企當日殺入三亞灣拿地,而這家民企的背後站着的是實力雄厚的陝北資本!01溢價率35.71%,陝西鑫洋三亞拿地6月30日這一天,三亞、陵水兩地分別有2宗城鎮住宅用地(市...
已確認!7月5日起,價格上調… - 天天要聞

已確認!7月5日起,價格上調…

來源:第一財經版權歸原作者所有,如有侵權請及時聯繫據第一財經消息,記者獲悉,有國內航空公司剛剛發佈通知,自2025年7月5日(含出票日期)起,國內航線旅客運輸燃油附加費上調10元。其中800公里(含)以下航線燃油附加費收取10元,800公里以上航線每位旅客每航段收取20元燃油附加費。上一次調整是2025年6月5日(出票日期...
A股IPO大爆發,半年受理177家 - 天天要聞

A股IPO大爆發,半年受理177家

【編者按】2025年上半年,隨着滬深北交易所密集發佈多項規則修訂,資本市場的深化改革邁向關鍵一程。A股IPO受理加快、併購重組熱度攀升、回購增持密集宣布等共同構成了上半年資本市場的新圖景。
喜迎食博丨龍海第二輪產銷對接「探廠選品」活動圓滿收官 - 天天要聞

喜迎食博丨龍海第二輪產銷對接「探廠選品」活動圓滿收官

【來源:龍海新聞】6月29日—30日,由區工信局聯合區食品工業協會焙烤食品產業分會主辦的第二輪產銷對接「探廠選品」活動成功舉辦。此次活動特邀中國食品工業協會休閑食品經銷商專業委員會團隊走進龍海,深入轄區38家食品企業開展選品對接。