引言:当服务"生病"时
还记得我们在前面学过的限流吗?限流是对流量的主动防御——就像景区设置的日接待量上限,人满了就不让进。
但在微服务世界里,除了流量洪峰,还有另一个更隐蔽的威胁:依赖服务的故障传播。
想象这样一个场景:
你的订单服务依赖商品服务查询商品信息。某天凌晨,商品服务的数据库出现了慢查询问题,导致商品查询接口响应时间从10ms飙升到5秒。
订单服务不知情,继续调用商品服务。每次调用都要等5秒才超时。大量请求线程被阻塞,线程池很快被耗尽。
于是,订单服务也"挂了"。接着,依赖订单服务的购物车服务、支付服务也接连崩溃…
一场雪崩就这样发生了。
这就是微服务架构的阿喀琉斯之踵:故障会沿着调用链传播,一个服务的问题可能导致整个系统瘫痪。
熔断降级就是为了解决这个问题而生的。它的核心思想是:
当依赖服务出现故障时,主动"断开"对它的调用,避免故障传播。就像壮士断腕,牺牲局部,保全整体。
这就是"断臂求生的艺术"。
熔断的本质:防止故障蔓延
什么是熔断?
熔断(Circuit Breaking)的本质是故障隔离机制。
当系统检测到某个依赖服务出现异常(如响应超时、异常率过高),就主动切断对该服务的调用,快速返回一个"降级结果"(如默认值、缓存数据、友好提示),避免调用方线程被长时间阻塞。
熔断的三个关键要素:
- 故障检测:持续监控依赖服务的健康状况
- 快速失败:检测到故障后,快速返回而不是继续等待
- 自动恢复:故障恢复后,自动恢复正常调用
为什么需要熔断?
在微服务架构中,服务之间通过网络调用相互依赖,形成了复杂的调用链路。这种架构有一个天然的脆弱性:
单个服务的故障会迅速扩散,导致雪崩效应。
举个例子:
用户服务 → 订单服务 → 商品服务 → 库存服务
↓
支付服务
如果库存服务出现故障(如数据库慢查询),会发生:
- 库存服务:响应变慢,每个请求耗时5秒
- 商品服务:调用库存服务被阻塞,线程池占满
- 订单服务:调用商品服务超时,自己的线程池也被占满
- 用户服务:调用订单服务失败,无法提供服务
- 整个系统:因为一个小问题而全面瘫痪
熔断的作用就是切断这条传播链:
- 商品服务检测到库存服务故障,熔断对库存服务的调用
- 商品服务返回降级结果(如显示"暂时无法查询库存")
- 商品服务自身保持正常运行
- 订单服务、用户服务不受影响
类比:家用电路熔断器
熔断(Circuit Breaker)这个名字来源于我们日常生活中的电路熔断器。
电路熔断器的工作原理
想象你家的配电箱:
- 正常工作:电流正常,熔断器保持闭合,电路正常供电
- 过载保护:当电流过大(如同时开太多电器),熔断器检测到异常
- 自动断开:熔断器立即跳闸,切断电路,避免电线过热引发火灾
- 手动恢复:排除故障后,可以手动合上开关,恢复供电
软件中的熔断器工作原理完全一样:
| 电路熔断器 | 软件熔断器 |
|---|---|
| 电流过大 | 服务响应慢/异常多 |
| 自动断开电路 | 停止调用服务 |
| 避免火灾 | 防止线程耗尽 |
| 手动合闸 | 自动尝试恢复 |
熔断器状态机:三种状态
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);
什么时候需要熔断?
熔断不是万能的,需要在合适的场景使用。
适合使用熔断的场景
调用外部依赖
- 第三方API(如支付、物流、短信)
- 其他团队维护的服务
- 不受自己控制的服务
不稳定的服务
- 经常出现慢查询
- 偶尔会抛异常
- 资源紧张的服务
非核心功能
- 推荐服务(可以降级为不推荐)
- 评论服务(可以暂时不显示)
- 积分服务(可以异步补偿)
不适合使用熔断的场景
核心业务流程
- 订单创建(不能降级)
- 支付扣款(不能降级)
- 用户登录(不能降级)
强一致性要求
- 库存扣减(必须精确)
- 账户余额(不能有误差)
- 金融交易(不允许降级)
内部可控的调用
- 同一进程内的方法调用
- 本地缓存查询
- 内存计算
决策树
是否需要熔断?
↓
是否调用外部依赖?
├─ 否 → 不需要熔断
└─ 是 ↓
依赖服务是否稳定?
├─ 稳定 → 可以不熔断,但建议配置
└─ 不稳定 ↓
是否核心业务?
├─ 是 → 需要熔断 + 精心设计降级方案
└─ 否 → 必须熔断
实战演示:最简单的熔断案例
让我们通过一个简单的例子,体验熔断的效果。
场景
模拟调用一个不稳定的商品服务:
- 正常响应时间: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(慢调用)
🔴 熔断生效,快速失败,返回降级结果 ← 熔断器开启
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
🔴 熔断生效,快速失败,返回降级结果
效果分析
- 前5次:正常调用,响应时间50ms
- 第6-10次:模拟故障,响应时间3000ms(慢调用)
- 第11次:慢调用比例达到50%(5/10),熔断器开启
- 第11-20次:熔断生效,快速失败,不再调用服务
关键收益:
- 避免线程阻塞:如果不熔断,每次请求都要等3秒
- 快速失败:熔断后直接返回,响应时间从3秒降到1ms
- 保护系统:调用方不会因为依赖服务慢而被拖垮
总结
本文我们学习了熔断降级的核心原理:
- 熔断的本质:防止故障在微服务调用链路中传播
- 熔断器状态机:关闭、开启、半开三种状态
- 熔断 vs 限流:保护自己不被依赖服务拖垮 vs 保护自己不被流量压垮
- Sentinel的熔断策略:慢调用比例、异常比例、异常数
- 使用场景:调用外部依赖、不稳定的服务、非核心功能
核心要点:
熔断是一种"断臂求生"的策略,通过主动切断对故障服务的调用,避免故障扩散,保护整个系统的稳定性。
下一篇预告:
我们将深入学习三种熔断策略的触发条件和配置方法:
- 慢调用比例:如何界定"慢"?阈值如何设置?
- 异常比例:什么样的异常会被统计?
- 异常数:适合什么场景?
这些都是实战中非常关键的知识点,敬请期待!