Files
autumn-recruitment/05.架构/service-governance/限流熔断降级.md
T

189 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [arch/microservice, rate-limiting, circuit-breaker, sentinel, resilience4j, token-bucket, leaky-bucket]
create time: 2026-08-08 18:00
update time: 2026-08-08 18:00
---
# 限流熔断降级
## 概述
限流、熔断、降级是微服务稳定性的三道防线。限流保护系统不被过量请求打垮,熔断在下游故障时快速失败避免级联雪崩,降级在服务不可用时返回兜底数据。三者层层递进,构成完整的容错体系。
## 核心原理
### 限流算法对比
```mermaid
graph LR
subgraph Counter["计数器限流"]
A[请求到达] --> B{本窗口内计数 < 阈值?}
B -- 是 --> C[允许通过]
B -- 否 --> D[拒绝]
end
subgraph TokenBucket["令牌桶"]
E[请求到达] --> F{桶中有足够令牌?}
F -- 是 --> G[消费令牌放行]
F -- 否 --> H[拒绝/等待]
I[匀速生成器] -->|"每秒 N 个"| J[(令牌桶)]
end
subgraph LeakyBucket["漏桶"]
K[请求到达] --> L[(漏桶)]
L --> M[固定速率流出处理]
L -->|"溢出"| N[拒绝]
end
```
| 算法 | 核心机制 | 突发能力 | 延迟特性 | 适用场景 |
|------|---------|---------|---------|---------|
| 滑动窗口计数器 | 时间窗内计数,超过阈值则拒绝 | 差(窗口切换时有"双倍突发") | 低开销 | 简单 API QPS 限制 |
| 令牌桶 | 以恒定速率向桶中添加令牌,请求需消耗令牌后才能通过 | 强(桶满时可突发消费存量令牌) | 中 | CDN 加速、消息队列限速 |
| 漏桶 | 请求进入桶中,以固定速率流出处理 | 无(强制匀速) | 高(有排队延迟) | 需要平滑流量的网关层 |
**滑动窗口的改进版——Leak Bucket vs Token Bucket:**
- 令牌桶适合保护生产者:允许突发流量,但长期受限于平均速率。
- 漏桶适合保护消费者:无论输入多猛,输出永远匀速,但延迟不确定。
> [!NOTE]
> Redisson 的限流器使用令牌桶算法实现 `rate(10, RateInterval.SECONDS, RateLimitRate.SEGMENTED)`,支持分布式环境下跨节点的精确计数。
### 熔断三态转换
```mermaid
stateDiagram-v2
[*] --> Closed: 初始状态
Closed --> Open: 失败率超过阈值 (如 >50%)
Open --> HalfOpen: 等待探测期超时 (如 30s)
HalfOpen --> Closed: 探测请求全部成功
HalfOpen --> Open: 探测请求仍有失败
Open --> Closed: 直接硬切回 (应急手段)
state Closed {
[*] --> Monitoring: 持续统计失败比例
}
state Open {
[*] --> RejectAll: 所有请求直接拒绝
}
state HalfOpen {
[*] --> ProbeLimited: 有限探针进入
}
```
**各状态的详细行为:**
| 状态 | 请求处理方式 | 统计维度 | 自动恢复? |
|------|------------|---------|----------|
| Closed(关闭) | 正常转发 | 统计最近 N 秒的请求成功/失败率 | 当失败率低于阈值且连续 M 次成功 |
| Open(打开) | 直接拒绝(FastFail),不调用下游 | 不统计(因为根本不发请求) | 等待 ProbeTimeout 进入 HalfOpen |
| HalfOpen(半开) | 发出少量探测请求(通常 1~3 个) | 只统计这几次探测结果 | 全成功 → Closed;任一失败 → Open |
**半开探测策略:**
- 探测请求数量通常是固定的(如 Sentinel 默认 1 个),而非渐进式增加(像 Hystrix 那样)。
- 探测期间原有业务请求仍然被熔断拦截,避免探测还没结束就压力暴增。
- 如果探测成功立即转入 Closed,否则重新进入 Open 并重置倒计时。
### Sentinel — 规则推送 + 指标统计
阿里巴巴开源的流量控制组件,核心特性:
1. **双链路指标采集**:滑动时间窗口 + 并发线程数两种维度。
2. **规则动态推送**:支持本地文件、Apollo、Nacos 等多种数据源,规则变更无需重启。
3. **多级限流粒度**:API 级别、方法级别、URL 级别、资源级别。
4. **系统自适应保护**:根据 Load、CPU 使用率、总 RPS 等自动调整限流阈值。
```go
// Sentinel Go SDK - 规则定义
import "github.com/alibaba/sentinel-golang/api"
import "github.com/alibaba/sentinel-golang/core/slot"
rule := &flow.Rule{
Resource: "user-query",
ThresholdType: flow.QPS,
Threshold: 100,
ControlBehavior: flow.Reject,
}
api.AddFlowRules([]*flow.Rule{rule})
```
### Resilience4j — 函数式 API
与 Spring Cloud CircuitBreaker 深度集成,采用装饰器模式:
```java
// Resilience4j 熔断器 + 重试组合
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofMillis(1000))
.slidingWindowSize(10)
.build();
io.github.resilience4j.circuitbreaker.CircuitBreaker cb =
CircuitBreaker.of("user-service", config);
Retry retry = Retry.of("user-service", RetryConfig.custom()
.maxAttempts(3)
.waitDuration(Duration.ofMillis(100))
.build());
Decorators.ofSupplier(() -> userService.getUser(id))
.retry(retry)
.circuitBreaker(cb)
.fallback(e -> new User(-1, "default"))
.get();
```
> [!TIP]
> 面试常考点:Resilience4j 和 Hystrix 的核心区别?Hystrix 基于线程池隔离(重),Resilience4j 基于信号量隔离(轻),且两者都是函数式设计而非继承 Thread。
### 降级的兜底思路
| 降级方式 | 示例 | 适合场景 |
|---------|------|---------|
| 返回缓存 | 用户信息从 DB 查不到时返回 Redis 旧值 | 对实时性要求不高的读接口 |
| 返回默认值 | 推荐接口超时返回热门商品列表 | 非核心业务流程 |
| 折叠请求 | 多个相同请求合并为一个真实请求 | 热点 Key 防护 |
| Mock 数据 | 开发环境/预发布时直接返回预设 JSON | CI/CD 阶段 |
## 代码示例
Go 中使用信号量实现简单的并发限流:
```go
type SemaphoreLimit struct {
sem chan struct{}
}
func NewSemaphoreLimit(maxConcurrent int) *SemaphoreLimit {
return &SemaphoreLimit{sem: make(chan struct{}, maxConcurrent)}
}
func (s *SemaphoreLimit) Allow() bool {
select {
case s.sem <- struct{}{}:
defer func() { <-s.sem }()
return true // 放行
default:
return false // 拒绝
}
}
```
## 实践场景
**秋招高频问题:**
- "Sentinel 的滑动窗口和传统的固定窗口有什么区别?" — 固定窗口在边界处会漏算(上一个窗口的尾巴 + 下一个窗口的头同时超限);Sentinel 将窗口细分为更小的 tick,在每个 tick 内独立计数再聚合,精度由 tickSize 决定。
- "为什么熔断要从 Open 到 HalfOpen 而不是直接回 Closed?" — HalfOpen 是一个安全阀:如果下游还在恢复中,直接进入 Closed 会让流量瞬间全部灌入,可能导致二次雪崩。有限的探测请求可以验证下游是否真的恢复了。
- "令牌桶和漏桶选型建议?" — 网关层对外暴露 API 时用令牌桶(允许客户端合理突发);内部服务间调用时用漏桶(保护消费方不被打爆)。
> [!WARNING]
> 不要把限流的阈值设得太激进。建议先观察线上 P99/RPS 基线,然后设为基线的 1.5~2 倍作为第一道防线,配合监控告警逐步调优。
## 关联笔记
- [[服务注册与发现]]
- [[负载均衡算法]]
- [[配置中心设计]]