在微服务架构中,服务与服务之间存在复杂的依赖关系。当某个下游服务出现故障(如响应超时、网络拥塞、异常抛出)时,如果缺乏有效的保护机制,故障可能会向上传导,导致整个链路的资源被耗尽,最终引发“雪崩效应”。
为了解决这一问题,**熔断(Circuit Breaking)和降级(Downgrading)**成为了保障系统高可用性的两把利剑。本文将深入探讨这两者的原理、状态流转机制及具体的实现案例。
一、服务熔断 (Circuit Breaking)
1.1 核心概念
服务熔断类似于电路中的保险丝。当电流过大(服务错误率过高)时,保险丝熔断(熔断器打开),切断电流(拦截请求),从而保护整个电路(系统)不被烧毁。
熔断的核心在于**“止损”**:当检测到下游服务不可用时,立即切断与下游的交互,快速返回错误或默认值,避免调用方线程长期阻塞。
1.2 熔断器的状态机模型
服务调用方为每一个调用的服务维护一个有限状态机(Finite State Machine)。在这个状态机中包含三种核心状态:关闭 (Closed)、半打开 (Half-Open) 和 打开 (Open)。
这三种状态之间的切换逻辑如下:
-
关闭状态 (Closed): 这是服务的正常工作模式。在此状态下,熔断器处于关闭状态,所有请求都会正常发往目标服务,没有任何特殊限制。同时,熔断器会实时统计调用的成功、失败及超时情况。
-
触发切换:当调用失败的次数(或比例)累积到预设的阈值时,熔断器判定服务不可用,状态从 Closed 切换到 Open。
-
-
打开状态 (Open): 当熔断器处于打开状态时,为了防止应用程序不断地尝试执行可能会失败的操作,后续的请求会被直接拦截(Fail Fast),直接返回错误或执行预设的降级逻辑。
-
触发切换:当进入 Open 状态时,会启动一个超时计时器。当计时器超时(即经过了一段“冷却期”)后,熔断器推测服务可能已经恢复,于是将状态切换到 Half-Open。
-
-
半打开状态 (Half-Open): 这是熔断器在尝试恢复时的试探性阶段。在此状态下,熔断器不再全量拦截,而是严格限制通过的请求数量(通常只允许极少数几个请求通过),目的是用这些少量请求来探测目标服务是否已恢复正常。
-
触发切换:
-
恢复成功:如果这几个探测请求调用成功(累计达到一定次数),熔断器判定服务已恢复,状态切换回 Closed,并重置失败计数。
-
恢复失败:如果探测请求依然失败,说明服务仍未恢复,状态重新切换回 Open,并重新开始计时。
-
-

1.3 状态流转示意图
[*] --> Closed: 初始化
Closed --> Open: 失败次数 >= 阈值
Open --> HalfOpen: 冷却时间结束 / 定时器探测
HalfOpen --> Closed: 探测请求成功 (连续成功 >= 阈值)
HalfOpen --> Open: 探测请求失败
1.4 实战案例:基于 Redis 的简易熔断器实现
下面演示一个基于 Redis 客户端操作的手写熔断器逻辑。该示例展示了如何通过定时器探测和业务逻辑中的异常捕获来驱动状态流转。
A. 探测机制:定时器检测
我们使用一个定时任务,定期检查处于“打开”状态的熔断器,尝试探测下游服务(Redis)是否可用。如果 Ping 成功,则尝试进入“半打开”状态。
// 初始化定时器,定期检测 Redis 是否可用
// recoverInterval 为检测间隔时间
new Timer("RedisPort-Recover", true).scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// 只有在熔断器打开时才进行探测
if (breaker.isOpen()) {
Jedis jedis = null;
try {
jedis = connPool.getResource();
// 1. 探活:验证 Redis 是否可用
jedis.ping();
// 2. 准备恢复:重置连续成功的计数
successCount.set(0);
// 3. 状态变更:设置为半打开态,允许部分流量通过进行进一步验证
breaker.setHalfOpen();
} catch (Exception ignored) {
// 探测失败,维持 Open 状态,等待下一次定时任务
} finally {
if (jedis != null) {
jedis.close();
}
}
}
}
}, 0, recoverInterval);
B. 业务熔断逻辑:请求拦截与状态变更
在实际执行 Redis 操作时,根据熔断器的当前状态决定是执行逻辑、直接返回、还是进行状态切换。
public K executeRedisCommand(Callback<K> callback) {
// 1. 熔断判定:如果断路器完全打开,直接快速失败
if (breaker.isOpen()) {
return null; // 或者抛出自定义熔断异常
}
K value = null;
Jedis jedis = null;
try {
jedis = connPool.getResource();
value = callback.call(jedis); // 执行具体的 Redis 业务逻辑
// 2. 半开状态下的成功判定逻辑
if(breaker.isHalfOpen()) {
// 如果在半开状态下成功了一次,增加成功计数
if(successCount.incrementAndGet() >= SUCCESS_THRESHOLD) {
// 成功次数超过阈值,说明服务稳定,彻底关闭熔断器
failCount.set(0); // 清空历史失败数
breaker.setClose(); // 恢复正常模式
}
}
return value;
} catch (JedisException je) {
// 3. 异常处理与状态切换
if(breaker.isClose()){
// 在正常关闭状态下发生异常,增加失败计数
if(failCount.incrementAndGet() >= FAILS_THRESHOLD){
breaker.setOpen(); // 失败超过阈值,触发熔断
}
} else if(breaker.isHalfOpen()) {
// 在半开探测阶段发生异常,说明服务依然不可用
breaker.setOpen(); // 立即退回打开状态,等待下一次探测周期
}
throw je; // 抛出异常给上层应用感知
} finally {
if (jedis != null) {
jedis.close();
}
}
}
二、服务降级 (Downgrading)
2.1 核心概念
如果说熔断是“保险丝”,那么降级就是“弃车保帅”。
服务降级是指当服务器压力剧增、资源紧张或非核心服务出现故障时,根据当前的业务情况和流量,对一些服务和页面有策略地进行降低级别(停止服务、简化服务),以此释放服务器资源,保证核心业务(如支付、下单)的正常运行。
2.2 常见的降级策略
-
返回默认值/空值:
-
例如:获取用户头像失败时,直接显示一张默认的灰色图片,而不是让页面转圈加载。
-
例如:在上述 Redis 例子中,
if (breaker.isOpen()) return null;本质上就是一种降级处理。
-
-
返回静态缓存:
-
例如:大促期间,商品推荐服务压力过大,可以降级为只返回昨天计算好的静态推荐列表,不再进行实时的个性化计算。
-
-
简化逻辑:
-
例如:写入日志时,如果数据库压力过大,降级为只写入本地文件或消息队列,稍后再异步处理。
-
-
页面/功能屏蔽:
-
例如:双十一零点,电商网站可能会暂时关闭“评价”、“修改收货地址”等非核心功能,确保下单链路的带宽。
-
2.3 自动降级与手动降级
-
自动降级:系统根据预设的阈值(如响应时间、错误率、系统 Load)自动触发。熔断后的 Fallback 逻辑通常属于自动降级。
-
手动降级:在预知会有大流量进入(如活动开始前),运维或开发人员通过配置中心人工关闭某些非核心服务开关。
三、熔断与降级的区别与联系
虽然熔断和降级最终的表现往往都是让用户体验到“功能暂时不可用”或“功能简化”,但它们在触发原因和目的上有着本质区别。
|
维度 |
服务熔断 (Circuit Breaking) |
服务降级 (Downgrading) |
|---|---|---|
|
触发原因 |
下游服务故障。如下游服务响应超时、抛出异常。 |
整体系统负荷高。可能是CPU跑满、队列积压,也可能是为了腾出资源给核心业务。 |
|
归属层面 |
链路保护。类似于物理世界的保险丝。 |
业务策略。类似于物理世界的“停电保产”。 |
|
自动/手动 |
通常是自动触发的(基于错误计数)。 |
可以是自动的,也可以是人工干预的(配置开关)。 |
|
恢复机制 |
经过冷却期后,自动探测并恢复。 |
通常需要系统负载降低后自动恢复,或人工关闭降级开关。 |
|
依赖关系 |
熔断通常会导致降级(熔断打开后,执行降级逻辑返回兜底数据)。 |
降级不一定需要熔断(可以主动配置降级)。 |
四、总结
-
熔断关注的是**“下游挂了怎么办”**,它通过状态机机制阻断请求,防止故障扩散。
-
降级关注的是**“资源不够了怎么办”**,它通过牺牲非核心业务,保证核心业务的高可用。
在实际的微服务开发中(如使用 Sentinel、Hystrix 或 Resilience4j),熔断和降级通常是结合使用的:当熔断发生时,触发降级逻辑,从而构建一个健壮的分布式系统。
975

被折叠的 条评论
为什么被折叠?



