引言:当服务"生病"时

还记得我们在前面学过的限流吗?限流是对流量的主动防御——就像景区设置的日接待量上限,人满了就不让进。

但在微服务世界里,除了流量洪峰,还有另一个更隐蔽的威胁:依赖服务的故障传播

想象这样一个场景:

你的订单服务依赖商品服务查询商品信息。某天凌晨,商品服务的数据库出现了慢查询问题,导致商品查询接口响应时间从10ms飙升到5秒。

订单服务不知情,继续调用商品服务。每次调用都要等5秒才超时。大量请求线程被阻塞,线程池很快被耗尽。

于是,订单服务也"挂了"。接着,依赖订单服务的购物车服务、支付服务也接连崩溃…

一场雪崩就这样发生了。

这就是微服务架构的阿喀琉斯之踵:故障会沿着调用链传播,一个服务的问题可能导致整个系统瘫痪。

熔断降级就是为了解决这个问题而生的。它的核心思想是:

当依赖服务出现故障时,主动"断开"对它的调用,避免故障传播。就像壮士断腕,牺牲局部,保全整体。

这就是"断臂求生的艺术"。


熔断的本质:防止故障蔓延

什么是熔断?

熔断(Circuit Breaking)的本质是故障隔离机制

当系统检测到某个依赖服务出现异常(如响应超时、异常率过高),就主动切断对该服务的调用,快速返回一个"降级结果"(如默认值、缓存数据、友好提示),避免调用方线程被长时间阻塞。

熔断的三个关键要素

  1. 故障检测:持续监控依赖服务的健康状况
  2. 快速失败:检测到故障后,快速返回而不是继续等待
  3. 自动恢复:故障恢复后,自动恢复正常调用

为什么需要熔断?

在微服务架构中,服务之间通过网络调用相互依赖,形成了复杂的调用链路。这种架构有一个天然的脆弱性:

单个服务的故障会迅速扩散,导致雪崩效应。

举个例子:

用户服务 → 订单服务 → 商品服务 → 库存服务
              ↓
            支付服务

如果库存服务出现故障(如数据库慢查询),会发生:

  1. 库存服务:响应变慢,每个请求耗时5秒
  2. 商品服务:调用库存服务被阻塞,线程池占满
  3. 订单服务:调用商品服务超时,自己的线程池也被占满
  4. 用户服务:调用订单服务失败,无法提供服务
  5. 整个系统:因为一个小问题而全面瘫痪

熔断的作用就是切断这条传播链

  • 商品服务检测到库存服务故障,熔断对库存服务的调用
  • 商品服务返回降级结果(如显示"暂时无法查询库存")
  • 商品服务自身保持正常运行
  • 订单服务、用户服务不受影响

类比:家用电路熔断器

熔断(Circuit Breaker)这个名字来源于我们日常生活中的电路熔断器

电路熔断器的工作原理

想象你家的配电箱:

  1. 正常工作:电流正常,熔断器保持闭合,电路正常供电
  2. 过载保护:当电流过大(如同时开太多电器),熔断器检测到异常
  3. 自动断开:熔断器立即跳闸,切断电路,避免电线过热引发火灾
  4. 手动恢复:排除故障后,可以手动合上开关,恢复供电

软件中的熔断器工作原理完全一样:

电路熔断器软件熔断器
电流过大服务响应慢/异常多
自动断开电路停止调用服务
避免火灾防止线程耗尽
手动合闸自动尝试恢复

熔断器状态机:三种状态

Sentinel的熔断器是一个有状态的对象,它有三种状态:

1. 关闭状态(Closed)

这是熔断器的初始状态正常工作状态

特征

  • 所有请求正常通过,调用依赖服务
  • 持续监控调用的健康指标(响应时间、异常比例)
  • 如果指标超过阈值,切换到"开启"状态

触发条件

  • 系统刚启动时
  • 从"半开"状态恢复后
┌─────────────────┐
│  Closed(关闭)  │  ← 初始状态
│  正常调用服务    │
└─────────────────┘
        ↓
    指标异常
        ↓

2. 开启状态(Open)

这是熔断器的保护状态

特征

  • 不再调用依赖服务,直接快速失败
  • 返回降级结果(如默认值、缓存数据)
  • 设置一个恢复时间窗口(如10秒)
  • 时间窗口到期后,切换到"半开"状态

触发条件

  • 慢调用比例超过阈值
  • 异常比例超过阈值
  • 异常数超过阈值
┌─────────────────┐
│  Open(开启)    │
│  快速失败       │
│  返回降级结果    │
└─────────────────┘
        ↓
    等待时间窗口
        ↓

3. 半开状态(Half-Open)

这是熔断器的探测状态

特征

  • 允许一个请求通过,试探性地调用依赖服务
  • 如果这个请求成功,说明服务恢复了,切换回"关闭"状态
  • 如果这个请求失败,说明服务还没恢复,切换回"开启"状态

触发条件

  • 从"开启"状态的恢复时间窗口到期
┌─────────────────┐
│ Half-Open(半开)│
│ 试探性调用       │
└─────────────────┘
        ↓
     调用成功?
    ↙      ↘
  是         否
  ↓          ↓
Closed     Open

完整的状态机流程

         ┌────────────────────────────────────┐
         │                                    │
         ↓                                    │
    ┌─────────┐   故障检测    ┌──────────┐   │
    │ Closed  │──────────→   │   Open   │   │
    │ (关闭)  │              │  (开启)  │   │
    │正常调用  │              │快速失败   │   │
    └─────────┘              └──────────┘   │
         ↑                        ↓          │
         │                    等待时间窗口     │
         │                        ↓          │
         │                   ┌──────────┐   │
         │     调用成功       │Half-Open │   │
         └───────────────    │  (半开)  │   │
                             │试探调用   │   │
                             └──────────┘   │
                                   │         │
                                调用失败      │
                                   └──────────┘

熔断 vs 限流:两种保护机制的对比

很多初学者会混淆熔断和限流,它们都是系统保护机制,但保护的对象和触发条件完全不同

核心区别

维度限流(Flow Control)熔断(Circuit Breaking)
保护对象保护自己不被流量压垮保护自己不被依赖服务拖垮
触发条件流量超过阈值(如QPS > 1000)依赖服务故障(如响应慢、异常多)
处理方式拒绝新请求停止调用依赖服务
恢复机制流量降低后自然恢复自动探测服务恢复
适用场景大促、秒杀等高并发场景微服务调用、外部依赖场景

形象的类比

限流:就像景区的限流措施

  • 景区有承载能力上限(资源有限)
  • 游客太多会导致体验差甚至踩踏事故
  • 解决方案:设置日接待量上限,人满不让进

熔断:就像防火墙的隔离措施

  • 一个房间着火了(依赖服务故障)
  • 火势可能蔓延到其他房间(故障传播)
  • 解决方案:关闭防火门,隔离故障区域

实战案例对比

限流场景

@SentinelResource(value = "seckill", blockHandler = "seckillBlockHandler")
public String seckill() {
    // 秒杀逻辑
    return "抢购成功";
}

// 限流规则:QPS不超过5000
FlowRule rule = new FlowRule();
rule.setResource("seckill");
rule.setCount(5000);

熔断场景

@SentinelResource(value = "callInventory", fallback = "inventoryFallback")
public InventoryInfo callInventory(Long productId) {
    // 调用库存服务
    return inventoryService.getInventory(productId);
}

// 熔断规则:慢调用比例超过50%则熔断
DegradeRule rule = new DegradeRule();
rule.setResource("callInventory");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
rule.setCount(1000); // 响应时间超过1秒算慢调用
rule.setSlowRatioThreshold(0.5); // 慢调用比例

组合使用

在实际项目中,限流和熔断通常组合使用

                 ┌──────────────┐
                 │   网关层     │
                 │  限流保护    │  ← 防止流量洪峰
                 └──────────────┘
                        ↓
                 ┌──────────────┐
                 │  订单服务    │
                 │  限流保护    │  ← 保护自己的接口
                 └──────────────┘
                        ↓
                 ┌──────────────┐
                 │  调用商品服务 │
                 │  熔断保护    │  ← 防止依赖服务拖垮自己
                 └──────────────┘

Sentinel的熔断实现

Sentinel提供了三种熔断策略

1. 慢调用比例(DEGRADE_GRADE_RT)

触发条件

  • 请求响应时间(RT)超过阈值的比例超过设定值
  • 例如:RT > 1秒的请求占比 > 50%

适用场景

  • 依赖服务响应变慢
  • 数据库慢查询
  • 网络抖动
DegradeRule rule = new DegradeRule();
rule.setResource("callRemoteService");
rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 慢调用比例策略
rule.setCount(1000); // 响应时间超过1000ms算慢调用
rule.setSlowRatioThreshold(0.5); // 慢调用比例阈值50%
rule.setMinRequestAmount(5); // 最小请求数
rule.setStatIntervalMs(10000); // 统计时长10秒
rule.setTimeWindow(10); // 熔断时长10秒

2. 异常比例(DEGRADE_GRADE_EXCEPTION_RATIO)

触发条件

  • 异常请求占总请求的比例超过阈值
  • 例如:异常率 > 30%

适用场景

  • 依赖服务频繁抛异常
  • 网络连接失败
  • 第三方API不稳定
DegradeRule rule = new DegradeRule();
rule.setResource("callThirdPartyApi");
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); // 异常比例策略
rule.setCount(0.3); // 异常比例阈值30%
rule.setMinRequestAmount(5);
rule.setStatIntervalMs(10000);
rule.setTimeWindow(10);

3. 异常数(DEGRADE_GRADE_EXCEPTION_COUNT)

触发条件

  • 在统计时长内,异常数超过阈值
  • 例如:10秒内异常数 > 10

适用场景

  • 低流量接口
  • 对异常非常敏感的场景
DegradeRule rule = new DegradeRule();
rule.setResource("callPaymentApi");
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); // 异常数策略
rule.setCount(10); // 异常数阈值10次
rule.setMinRequestAmount(5);
rule.setStatIntervalMs(10000);
rule.setTimeWindow(10);

什么时候需要熔断?

熔断不是万能的,需要在合适的场景使用。

适合使用熔断的场景

  1. 调用外部依赖

    • 第三方API(如支付、物流、短信)
    • 其他团队维护的服务
    • 不受自己控制的服务
  2. 不稳定的服务

    • 经常出现慢查询
    • 偶尔会抛异常
    • 资源紧张的服务
  3. 非核心功能

    • 推荐服务(可以降级为不推荐)
    • 评论服务(可以暂时不显示)
    • 积分服务(可以异步补偿)

不适合使用熔断的场景

  1. 核心业务流程

    • 订单创建(不能降级)
    • 支付扣款(不能降级)
    • 用户登录(不能降级)
  2. 强一致性要求

    • 库存扣减(必须精确)
    • 账户余额(不能有误差)
    • 金融交易(不允许降级)
  3. 内部可控的调用

    • 同一进程内的方法调用
    • 本地缓存查询
    • 内存计算

决策树

是否需要熔断?
    ↓
是否调用外部依赖?
    ├─ 否 → 不需要熔断
    └─ 是 ↓
       依赖服务是否稳定?
           ├─ 稳定 → 可以不熔断,但建议配置
           └─ 不稳定 ↓
              是否核心业务?
                  ├─ 是 → 需要熔断 + 精心设计降级方案
                  └─ 否 → 必须熔断

实战演示:最简单的熔断案例

让我们通过一个简单的例子,体验熔断的效果。

场景

模拟调用一个不稳定的商品服务

  • 正常响应时间:50ms
  • 故障时响应时间:3秒
  • 我们设置熔断规则:RT > 1秒的请求占比 > 50% 则熔断

完整代码

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
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 java.util.ArrayList;
import java.util.List;

public class CircuitBreakerDemo {

    public static void main(String[] args) throws InterruptedException {
        // 1. 配置熔断规则
        initDegradeRule();

        // 2. 模拟调用
        for (int i = 0; i < 20; i++) {
            Thread.sleep(500);
            callProductService(i < 10); // 前10次正常,后10次故障
        }
    }

    /**
     * 配置熔断规则
     */
    private static void initDegradeRule() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource("callProductService");
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 慢调用比例
        rule.setCount(1000); // RT超过1000ms算慢调用
        rule.setSlowRatioThreshold(0.5); // 慢调用比例50%
        rule.setMinRequestAmount(5); // 最少5个请求
        rule.setStatIntervalMs(10000); // 统计时长10秒
        rule.setTimeWindow(10); // 熔断10秒
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
        System.out.println("✅ 熔断规则已加载");
    }

    /**
     * 调用商品服务
     */
    private static void callProductService(boolean isNormal) {
        try (Entry entry = SphU.entry("callProductService")) {
            // 模拟调用商品服务
            if (isNormal) {
                Thread.sleep(50); // 正常响应50ms
                System.out.println("✅ 调用成功,响应时间:50ms");
            } else {
                Thread.sleep(3000); // 故障时响应3秒
                System.out.println("⚠️  调用成功,响应时间:3000ms(慢调用)");
            }
        } catch (BlockException e) {
            // 熔断降级
            System.out.println("🔴 熔断生效,快速失败,返回降级结果");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行效果

✅ 熔断规则已加载
✅ 调用成功,响应时间:50ms
✅ 调用成功,响应时间:50ms
✅ 调用成功,响应时间:50ms
✅ 调用成功,响应时间:50ms
✅ 调用成功,响应时间:50ms
⚠️  调用成功,响应时间:3000ms(慢调用)
⚠️  调用成功,响应时间:3000ms(慢调用)
⚠️  调用成功,响应时间:3000ms(慢调用)
⚠️  调用成功,响应时间:3000ms(慢调用)
⚠️  调用成功,响应时间:3000ms(慢调用)
🔴 熔断生效,快速失败,返回降级结果  ← 熔断器开启
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果

效果分析

  1. 前5次:正常调用,响应时间50ms
  2. 第6-10次:模拟故障,响应时间3000ms(慢调用)
  3. 第11次:慢调用比例达到50%(5/10),熔断器开启
  4. 第11-20次:熔断生效,快速失败,不再调用服务

关键收益

  • 避免线程阻塞:如果不熔断,每次请求都要等3秒
  • 快速失败:熔断后直接返回,响应时间从3秒降到1ms
  • 保护系统:调用方不会因为依赖服务慢而被拖垮

总结

本文我们学习了熔断降级的核心原理:

  1. 熔断的本质:防止故障在微服务调用链路中传播
  2. 熔断器状态机:关闭、开启、半开三种状态
  3. 熔断 vs 限流:保护自己不被依赖服务拖垮 vs 保护自己不被流量压垮
  4. Sentinel的熔断策略:慢调用比例、异常比例、异常数
  5. 使用场景:调用外部依赖、不稳定的服务、非核心功能

核心要点

熔断是一种"断臂求生"的策略,通过主动切断对故障服务的调用,避免故障扩散,保护整个系统的稳定性。

下一篇预告

我们将深入学习三种熔断策略的触发条件和配置方法

  • 慢调用比例:如何界定"慢"?阈值如何设置?
  • 异常比例:什么样的异常会被统计?
  • 异常数:适合什么场景?

这些都是实战中非常关键的知识点,敬请期待!