目录
[1.3、常用指标 & 参数](#1.3、常用指标 & 参数)
[4、隔离(Bulkhead / 资源隔离)](#4、隔离(Bulkhead / 资源隔离))
背景
分布式系统中任一服务或下游依赖变慢或宕掉,可能产生级联故障(请求积压、线程耗尽、连接尽、资源竞用),导致系统整体不可用或雪崩。
熔断(Circuit Breaker)避免对不健康依赖不断重试/请求;
降级(Fallback/Degrade)在功能不可用或超载时提供替代方案;
限流(Rate Limit)控制进入系统或单个服务的请求速率,保护资源;
隔离(Bulkhead/隔离)将故障影响局限在某个隔间,避免资源争抢扩散。
这几种方式是分布式系统中应对高并发、依赖故障的核心容错机制,**共同解决服务雪崩风险,保障系统可用性。**
1、熔断
1.1、介绍
断路保护。
比如 A 服务调用 B 服务,由于网络问题或 B 服务宕机了或 B 服务的处理时间长,导致请求的时间超长,如果在一定时间内多次出现这种情况,就可以直接将 B 断路了(A 不再请求B)。
而调用 B 服务的请求直接返回降级数据,不必等待 B 服务的执行。因此 B 服务的问题,不会级联影响到 A 服务。

- 作用 :快速失败止损,当服务失败率超过阈值时自动切断调用链路。
- 实现逻辑 :
- 监控请求失败率(如10秒内失败率>50%)
- 触发熔断后,后续请求直接走降级逻辑,不再访问故障服务
- 定期进入"半开状态"试探服务恢复情况
- 典型场景:支付服务持续超时后,网关层直接熔断,避免请求堆积
1.2、原理/状态机
基本三态:
CLOSED(闭合,正常请求通过并收集成功/失败指标);
OPEN(打开,短路,不再调用下游,直接失败或走降级);
HALF_OPEN(半开,允许少量试探请求以探测依赖是否恢复)。
触发条件通常基于:
在滑动窗口内的失败率、失败次数、响应时延、吞吐量等。达到阈值触发 OPEN,过一段时间后进入 HALF_OPEN,若试探请求成功则回到 CLOSED,否则继续 OPEN。
1.3、常用指标 & 参数
- failureRateThreshold(失败率阈值,例如 50%)
- minimumNumberOfCalls(最小采样请求数,避免样本量太小)
- slidingWindowSize(统计窗口大小,基于时间或计数)
- waitDurationInOpenState(打开态保持时间,过后转 HALF_OPEN)
- permittedNumberOfCallsInHalfOpenState(半开允许的试探请求数)
1.4、常见实现/库
- Netflix Hystrix(已停止维护,思想仍然有价值)
- Resilience4j(现代、轻量、功能丰富)
- Sentinel(阿里,支持流控、熔断、降级、热点限流)
1.5、Java 示例:Resilience4j(maven)
- 依赖:
- org.resilience4j:resilience4j-circuitbreaker
- 简单示例(同步调用):
java
import io.github.resilience4j.circuitbreaker.*;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.SlidingWindowType;
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50) // 50%
.minimumNumberOfCalls(10)
.slidingWindowType(SlidingWindowType.TIME_BASED)
.slidingWindowSize(10) // 10 seconds window (if TIME_BASED)
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(5)
.build();
CircuitBreaker cb = CircuitBreaker.of("myService", config);
// 装饰一个 Supplier 或 Callable
Supplier<String> decorated = CircuitBreaker
.decorateSupplier(cb, () -> callRemoteService());
try {
String result = decorated.get();
} catch (CallNotPermittedException ex) {
// 熔断打开,短路到这里,做降级
fallback();
}
简单手写熔断器(伪实现):
java
class SimpleCircuitBreaker {
enum State { CLOSED, OPEN, HALF_OPEN }
private State state = State.CLOSED;
private long openUntil = 0;
private int failCount = 0;
private int successCount = 0;
private final int failThreshold = 5;
private final long openMs = 10_000L;
public synchronized <T> T call(Callable<T> callable) throws Exception {
long now = System.currentTimeMillis();
if (state == State.OPEN) {
if (now < openUntil) throw new RuntimeException("circuit open");
state = State.HALF_OPEN;
}
try {
T r = callable.call();
onSuccess();
return r;
} catch (Exception e) {
onFailure();
throw e;
}
}
private void onSuccess() {
if (state == State.HALF_OPEN) {
// 一个成功就关闭,也可以要求连续成功次数
state = State.CLOSED; failCount = 0;
}
}
private void onFailure() {
failCount++;
if (failCount >= failThreshold) {
state = State.OPEN;
openUntil = System.currentTimeMillis() + openMs;
}
}
}
注意:真实生产要用滑窗统计、并发安全、冷启动保护等。
2、降级
2.1、介绍
返回降级数据。
网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级(停止服务,所有的调用直接返回降级数据)。
以此缓解服务器资源的压力,保证核心业务的正常运行,保持了客户和大部分客户得到正确的响应。
降级数据可以简单理解为快速返回了一个 false,前端页面告诉用户"服务器当前正忙,请稍后再试。"

- 作用 :提供柔性方案,在熔断或服务不可用时返回预设结果。
- 实现方式 :
- 返回缓存数据(如商品详情页降级展示昨日销量)
- 返回默认值(如查询失败时显示"服务繁忙")
- 流程简化(下单跳过风控校验)
- 关键点:需提前设计降级策略,确保用户体验平滑
2.2、分类
功能降级(返回默认/缓存数据/静态页面)、流量降级(拒绝非核心请求)、延迟降级(将请求入队异步处理)、降级到降级服务(更便宜或更稳定的实现)。
2.3、实现方式
- 在代码中实现 fallback(try/catch 或 使用库注入 fallback,例如 Resilience4j 的 fallback 或 Spring Cloud 的 fallback)
- 使用缓存作为后备(比如返回缓存数据)
- 业务级降级:降低功能强度(只返回必要字段、减少并行查询、去掉非核心聚合)
- 降级开关/灰度:通过配置中心(HOT)控制降级
Java 示例(Resilience4j with fallback)
java
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
try {
String res = decorated.get();
} catch (CallNotPermittedException ex) {
// 熔断短路 -> 降级处理
return fallbackData();
} catch (Exception e) {
// 依赖超时/异常 -> 降级
return fallbackData();
}
- 缓存优先(先返回缓存,再异步刷新缓存)
- 简化响应(只返回 ID 和关键字段)
- 限功能(把高级功能关闭,保持核心支付/登录业务)
熔断和降级的相同点?
- 熔断和限流都是为了保证集群大部分服务的可用性和可靠性。防止核心服务崩溃。
- 给终端用户的感受就是某个功能不可用。
熔断和降级的不同点?
- 熔断是被调用方出现了故障,主动触发的操作。
- 降级是基于全局考虑,停止某些正常服务,释放资源。
3、限流
3.1、定义
对请求的流量进行控制, 只
放行部分请求
,使服务能够承担不超过自己能力的流量压力。

- 控制突发流量、保护后端资源、避免请求淹没服务、实现 QoS 策略(优先级/计费)
3.2、常见算法
- 固定窗口计数(Fixed Window):按时间窗口计数(简单但在窗口边界有突发)。
- 滑动窗口计数(Sliding Window Log/Counter):记录时间戳(更精确,但可能昂贵)。
- 令牌桶(Token Bucket):以固定速率产生令牌,请求拿到令牌则放行,可实现平滑突发(允许短时突发)。
- 漏桶(Leaky Bucket):以固定速率处理请求,过载则丢弃/排队(平滑输出)。
- 令牌桶 + Redis/Lua:分布式限流实现常用。
本地简单实现:令牌桶(Token Bucket)
java
class TokenBucket {
private final long capacity;
private final long refillTokens;
private final long refillIntervalMillis;
private double tokens;
private long lastRefillTimestamp;
public TokenBucket(long capacity, long refillTokens, long refillIntervalMillis) {
this.capacity = capacity;
this.refillTokens = refillTokens;
this.refillIntervalMillis = refillIntervalMillis;
this.tokens = capacity;
this.lastRefillTimestamp = System.currentTimeMillis();
}
public synchronized boolean tryConsume(int numTokens) {
refill();
if (tokens >= numTokens) {
tokens -= numTokens;
return true;
}
return false;
}
private void refill() {
long now = System.currentTimeMillis();
long intervals = (now - lastRefillTimestamp) / refillIntervalMillis;
if (intervals > 0) {
double add = intervals * refillTokens;
tokens = Math.min(capacity, tokens + add);
lastRefillTimestamp += intervals * refillIntervalMillis;
}
}
}
分布式限流(Redis + Lua)
- 用 Lua 脚本在 Redis 原子执行计数或令牌桶操作,避免竞争。
- 示例:滑动窗口计数(用时间戳链表)或令牌桶(使用 Redis key 存 token 值与 timestamp)。
简单的 Redis 计数(固定窗口)Lua(伪代码):
java
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
local count = redis.call('GET', key)
if not count then
redis.call('SET', key, 1, 'PX', window)
return 1
end
if tonumber(count) + 1 > limit then
return 0
else
redis.call('INCR', key)
return 1
end
4、隔离(Bulkhead / 资源隔离)
4.1、概念
将服务或资源按"隔间"划分(线程池、连接池、限额),使得某一隔间发生故障或耗尽时不致影响其他隔间(业务)。灵感来自船舱隔离(bulkhead)。
4.2、实现方式
- 线程池隔离:对不同下游或不同类型请求使用不同线程池(或不同拒绝策略),避免单个慢调用耗尽主线程池。
- 信号量隔离(semaphore):限制并发调用数(轻量,不带线程切换)。
- 连接池/资源配额:每个依赖一个单独连接池,上游耗尽连接不影响其它依赖。
- 容器/微服务资源限制:通过 k8s 限制资源、pod 副本隔离。
Java 示例:线程池隔离(Executor)
java
ExecutorService pool = new ThreadPoolExecutor(
10, 10, 0L, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<>(50),
new ThreadPoolExecutor.AbortPolicy()); // 当队列满时拒绝
Future<String> future = pool.submit(() -> callRemoteService());
try {
String res = future.get(2, TimeUnit.SECONDS); // 超时控制
} catch (TimeoutException e) {
// 超时 -> 降级
future.cancel(true);
fallback();
}
信号量隔离(Resilience4j 提供 Bulkhead)
java
import io.github.resilience4j.bulkhead.*;
BulkheadConfig config = BulkheadConfig.custom()
.maxConcurrentCalls(20)
.maxWaitDuration(Duration.ZERO) // 不等待,直接拒绝
.build();
Bulkhead bulkhead = Bulkhead.of("service", config);
Supplier<String> decorated = Bulkhead.decorateSupplier(bulkhead, () -> callRemoteService());
try { decorated.get(); } catch (BulkheadFullException ex) { fallback(); }
为什么优先选择信号量还是线程池?
- 信号量:延迟低、轻量(适合在同线程中限流),但当依赖阻塞时会占用调用线程。
- 线程池:能把阻塞转化为排队,保护调用线程(比如 tomcat 请求处理线程),但可能导致上下文切换和队列积压。
5、四者组合与实际策略
请求进入时做鉴权与快速限流(Token Bucket)------ 保护入口。
常见防御链(建议):
- 对调用下游前先使用隔离(线程池/信号量)------ 保证主线程不被耗尽。
- 用熔断器判断下游健康,快速短路失败------ 减少无用等待与重试风暴。
- 对短路/失败使用降级策略(缓存/默认结果/异步排队)------ 保证核心业务可用。
- 对内部延迟/超时做度量并上报监控(实时告警)。
配置示例(伪):限流 200 rps;线程池 max 50;熔断:10s 窗口内失败率>50% 且最小20个请求 -> 打开 30s -> 半开 5次试探。
6、监控与指标(必须)
- 熔断器需上报:成功率、失败率、请求数、state transitions(CLOSED->OPEN->HALF_OPEN)。
- 限流需上报:命中数、被拒绝数、当前速率。
- 隔离需上报:线程池活跃数、队列长度、拒绝数、等待时长。
- 降级需上报:fallback 调用次数、降级率、触发原因(熔断、超时、资源压力)。
- 告警阈值应基于业务SLA设定,避免噪音。
参考文章: