熔断、降级与限流
微服务调用链中,单点故障可能引发级联雪崩。熔断、降级、限流是保障系统稳定性的三道防线。
1. 熔断器(Circuit Breaker)
状态机
┌─────────┐ 失败率>阈值 ┌─────────┐
│ CLOSED │──────────────→│ OPEN │
│ (正常) │ │ (熔断) │
└────┬────┘ └────┬────┘
│ │
失败计数恢复 │ 超时后
│ ↓
│ ┌─────────────┐
└─────────────────│ HALF-OPEN │
探测失败 │ (半开/探测) │
└─────────────┘
参数配置
| 参数 | 说明 | 典型值 |
|---|---|---|
| failureThreshold | 触发熔断的失败次数/比例 | 5 次或 50% |
| slowCallThreshold | 触发熔断的慢调用比例 | 80% |
| waitDurationInOpenState | OPEN 持续时间 | 30s |
| permittedNumberOfCallsInHalfOpenState | 半开探测次数 | 3 |
Resilience4j 示例
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(3)
.slidingWindowSize(100)
.build();
CircuitBreaker cb = CircuitBreaker.of("orderService", config);
// 装饰调用
Supplier<String> decorated = CircuitBreaker
.decorateSupplier(cb, () -> orderClient.getOrder(id));
String result = Try.ofSupplier(decorated)
.recover(throwable -> "fallback_order")
.get();
2. 降级(Degrade)
当服务不可用或响应过慢时,提供简化替代方案。
降级策略
| 策略 | 场景 | 示例 |
|---|---|---|
| 返回默认值 | 非核心数据 | 推荐列表为空时返回热门商品 |
| 返回缓存 | 读服务 | 从 Redis 读取历史数据 |
| 功能裁剪 | 核心功能优先 | 关闭评论功能,保留购买 |
| 同步转异步 | 非实时要求 | 订单创建后异步发送通知 |
Sentinel 降级
// 基于慢调用比例的降级
DegradeRule rule = new DegradeRule("getOrder")
.setGrade(CircuitBreakerStrategy.SLOW_REQUEST_RATIO)
.setCount(0.5) // 慢调用比例阈值
.setTimeWindow(30) // 熔断时长(秒)
.setSlowRatioThreshold(500); // 慢调用阈值(ms)
3. 限流(Rate Limiting)
控制请求速率,防止服务被突发流量击垮。
算法对比
【令牌桶】 【漏桶】 【滑动窗口】
┌─────┐ ┌─────┐
│令牌 │ ← 匀速产生 │请求 │ → 匀速漏出
└─┬───┘ └──┬──┘
│ 取令牌 │ 排队
▼ ▼
服务 服务
允许突发 平滑流量 精确计数
| 算法 | 特点 | 适用场景 |
|---|---|---|
| 令牌桶 | 允许突发,平均速率可控 | 大多数限流场景 |
| 漏桶 | 强制平滑,无突发 | 需要严格平滑的下游 |
| 滑动窗口 | 精确统计窗口内请求 | 短时限流 |
| 固定窗口 | 实现简单,存在临界突刺 | 统计精度要求低 |
Guava RateLimiter
RateLimiter limiter = RateLimiter.create(100); // QPS = 100
// 阻塞获取
limiter.acquire();
// 非阻塞尝试
if (limiter.tryAcquire(10, TimeUnit.MILLISECONDS)) {
process(request);
} else {
return "Too Many Requests";
}
分布式限流(Redis + Lua)
-- 滑动窗口限流
local key = KEYS[1]
local now = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local limit = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now)
redis.call('EXPIRE', key, window)
return 1 -- 允许
else
return 0 -- 拒绝
end
4. 舱壁隔离(Bulkhead)
将服务资源划分为独立池,一个池的故障不影响其他池。
无隔离: 舱壁隔离:
┌─────┐ ┌──────┐ ┌──────┐
│所有请求│ │查询请求│ │写请求│
└──┬──┘ └───┬──┘ └───┬──┘
│ 共享线程池 │ 线程池 A │ 线程池 B
▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐
│DB连接│ │DB连接│ │DB连接│
└─────┘ └─────┘ └─────┘
Resilience4j Bulkhead
ThreadPoolBulkheadConfig config = ThreadPoolBulkheadConfig.custom()
.maxThreadPoolSize(10)
.coreThreadPoolSize(5)
.queueCapacity(20)
.build();
ThreadPoolBulkhead bulkhead = ThreadPoolBulkhead.of("backendA", config);
CompletionStage<String> result = bulkhead.executeSupplier(() -> backendService.call());
5. 组合使用
请求入站
↓
┌─────────────────┐
│ 限流(全局/用户)│ ← 第一层:防过载
└─────────────────┘
↓
┌─────────────────┐
│ 舱壁隔离 │ ← 第二层:资源隔离
└─────────────────┘
↓
┌─────────────────┐
│ 熔断器 │ ← 第三层:故障隔离
└─────────────────┘
↓
┌─────────────────┐
│ 降级预案 │ ← 最后防线
└─────────────────┘
6. 实战:Spring Cloud + Sentinel
spring:
cloud:
sentinel:
transport:
dashboard: sentinel-dashboard:8080
datasource:
flow:
nacos:
server-addr: nacos:8848
dataId: ${spring.application.name}-flow-rules
rule-type: flow
@RestController
public class OrderController {
@GetMapping("/orders/{id}")
@SentinelResource(
value = "getOrderById",
blockHandler = "handleBlock",
fallback = "handleFallback"
)
public Order getOrder(@PathVariable Long id) {
return orderService.getById(id);
}
public Order handleBlock(Long id, BlockException ex) {
throw new TooManyRequestsException();
}
public Order handleFallback(Long id, Throwable ex) {
return Order.empty(); // 降级
}
}
总结
| 手段 | 目标 | 触发条件 |
|---|---|---|
| 限流 | 防止过载 | 请求速率超过阈值 |
| 熔断 | 防止故障扩散 | 错误率/慢调用超标 |
| 降级 | 有损服务 | 依赖服务异常 |
| 隔离 | 资源保护 | 资源竞争 |
四者配合使用,构建多层防护体系,是微服务高可用的基石。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。