--- 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 倍作为第一道防线,配合监控告警逐步调优。 ## 关联笔记 - [[服务注册与发现]] - [[负载均衡算法]] - [[配置中心设计]]