限流、熔断、降级:三者核心区别一文讲清

理解这三者,最有效的切入点不是定义,而是它们各自站在调用链的哪个位置、保护的是谁 。 一句话概括三者的差异:限流管"进来多少",熔断管"还要不要调用它",降级管"调不到时给什么"


一、限流(Rate Limiting)

解决的问题

任何服务的容量都是有限的(线程池、连接池、CPU、DB 连接)。当入口 QPS 超过容量水位,请求会在队列里堆积,响应时间指数级上升,最终线程全部占满、GC 频繁、整个进程雪崩。限流的本质是用可控的部分失败,换取整体不崩 ------保护的是自己

触发条件(都是"量"的维度)

维度 典型阈值
QPS / TPS 单机或集群维度超过设定阈值
并发线程数 同时在处理的请求数超过 N
令牌/漏桶余量 桶内无可用令牌
自适应指标 系统 Load、RT、CPU 超过水位(如 Sentinel 系统自适应保护)

常见算法与取舍

  • 固定窗口计数:实现最简单,但存在临界点双倍流量问题。
  • 滑动窗口:精度高,Sentinel 默认实现,内存开销略大。
  • 漏桶:匀速流出,能整形流量,但无法应对突发。
  • 令牌桶 :允许一定突发(桶容量),Guava RateLimiter 的实现,实际业务中最常用。
java 复制代码
// Sentinel 侧的典型资源定义:超过阈值抛 BlockException
@SentinelResource(value = "createOrder",
                  blockHandler = "onFlowBlocked")   // 限流/熔断被拦截
public OrderVO createOrder(OrderReq req) { ... }

public OrderVO onFlowBlocked(OrderReq req, BlockException ex) {
    // 明确返回"系统繁忙,请稍后重试",不要伪装成业务失败
    throw new BizException(ErrorCode.SYSTEM_BUSY);
}

适用场景

  • 网关 / BFF 入口层,防刷、防爬、防脚本攻击。
  • 秒杀、抢券这类已知会超卖流量的活动入口。
  • 开放 API 的多租户配额隔离(按 appKey 维度限流)。
  • 写库、发短信、调用付费第三方接口等成本敏感的下游保护。

实践要点:限流阈值要来自压测数据,不是拍脑袋;集群限流要注意单机阈值 × 实例数 ≠ 集群容量(下游 DB 才是真瓶颈);限流后必须返回明确的 429/系统繁忙语义,让客户端可以退避重试。


二、降级(Degradation / Fallback)

解决的问题

资源不够或依赖不可用时,保证系统仍然有"可接受的输出" 。它不解决容量问题,也不解决故障探测问题,它解决的是"出问题之后用户看到什么"。核心是区分核心链路与非核心链路------把有限的资源集中给核心功能。

触发条件(分两类,容易被混淆)

  1. 自动降级:被动触发。下游超时、熔断器已打开、线程池/信号量已满、限流被拦截、抛出指定异常 → 走 fallback 分支。
  2. 人工降级:主动触发。大促前通过配置中心(Apollo / Nacos)推开关,提前关掉商品评价、推荐、积分、日志埋点等非核心功能,把容量留给下单和支付。

降级的常见手段(按代价从低到高)

手段 说明
返回缓存旧数据 允许一定时间的数据不一致,用户体验最好
返回静态默认值 如推荐位返回运营配置的兜底榜单
简化逻辑 风控从"实时模型打分"退化为"规则黑名单"
异步化 同步扣积分改为投消息队列,最终一致
直接关闭功能 前端隐藏入口,返回"活动太火爆"

适用场景

  • 依赖非核心的推荐、评论、画像、榜单服务。
  • 大促/活动前的预案性关闭(这是收益最大、最被低估的一类)。
  • 读多写少、可容忍短暂陈旧数据的查询场景。
  • 反例:支付扣款、库存扣减这类强一致写操作不能降级,只能限流排队或直接失败。

三、熔断(Circuit Breaking)

解决的问题

防止故障扩散 。当下游 B 变慢(不是挂掉,而是变慢,这才是最危险的情况),调用方 A 的每个请求都要等到超时才返回,A 的线程池被迅速占满,A 也随之不可用,再传导到 A 的上游------这就是级联雪崩。熔断的作用是:既然大概率会失败,就不要再浪费自己的线程去等它,直接快速失败(Fail Fast)。

它的关键行为是有状态 的,这是与限流、降级最本质的区别: 触发条件(都是"质"的维度)

维度 说明
异常比例 滑动窗口内失败率超过阈值(如 50%)
异常数 窗口内失败绝对数超过 N
慢调用比例 RT 超过阈值的请求占比过高(最重要的一条,慢比错更致命)
最小请求数 前置门槛,窗口内请求数不足时不做判断,避免小样本误判
yaml 复制代码
# Resilience4j 的典型配置,注意 slow-call 相关项往往比 failure-rate 更关键
resilience4j.circuitbreaker:
  instances:
    riskService:
      slidingWindowType: TIME_BASED
      slidingWindowSize: 10          # 统计最近 10s
      minimumNumberOfCalls: 20       # 样本不足不熔断
      failureRateThreshold: 50       # 失败率 50% 打开
      slowCallDurationThreshold: 1s
      slowCallRateThreshold: 60      # 慢调用占比 60% 也打开
      waitDurationInOpenState: 10s   # Open 冷却时长
      permittedNumberOfCallsInHalfOpenState: 5

适用场景

  • 跨服务的 RPC / HTTP 调用,尤其是不可控的第三方接口(支付渠道、短信、地图、征信)。
  • 依赖 DB、Redis、ES 等中间件的访问点。
  • 多副本部署下的实例级熔断(摘除个别慢节点)。
  • 不适用场景:本地方法调用、幂等性无保障的关键写操作(熔断带来的"快速失败"可能让上游误判,进而重复提交)。

四、三者的核心区别

维度 限流 熔断 降级
保护对象 自己(本服务容量) 自己 + 阻止故障扩散 用户体验与核心链路
面向方向 入口(inbound) 出口(outbound,调用下游) 响应装配阶段
判断依据 流量的(QPS、并发) 依赖的健康度(失败率、RT) 上游是否已被拦截 / 人工开关
是否有状态 无状态,逐请求判定 有状态,三态机 + 自动恢复 无状态,只是分支逻辑
动作 拒绝 / 排队 / 匀速放行 断开链路,快速失败 返回兜底结果
恢复方式 流量回落即自动恢复 冷却 → 半开探测 → 关闭 熔断/限流解除 或 人工关开关
对用户 部分用户被拒 该依赖的调用全部立即失败 功能能用但能力变弱
归类 因(防止过载发生) 因(防止故障传染) (前两者触发后的善后)

最容易被搞混的是熔断和降级 。准确的关系是:熔断是决策,降级是动作。熔断器决定"这条链路现在不通",降级决定"不通的时候返回什么"。熔断打开后必然需要降级来兜底,但降级可以在没有熔断的场景下独立发生(比如限流被拦、人工推开关)。

反过来,限流和熔断的区别在于因果朝向:限流是"我知道我只能吃三碗饭,第四碗直接不接";熔断是"我发现这家饭馆上菜要一小时,我不点它家了"。


五、在高可用体系中的配合关系

三者不是三选一,而是纵深防御的三层,在一次真实故障中是接力 关系: 协作时的四条工程原则

  1. 顺序不能反:限流必须在最前(网关/入口),熔断在调用出口,降级在最后兜底。如果先熔断再限流,超额流量已经进入系统消耗了线程。
  2. 熔断必须配超时和隔离 :没有合理的连接/读超时,熔断器的统计窗口永远等不到"失败"信号;没有线程池或信号量隔离,一个慢依赖仍会拖垮共享线程池。三者的前提是超时先配对
  3. 降级要预先设计,不能临时写 :fallback 的数据来源(缓存、静态配置、简化规则)必须提前准备好,并且降级路径本身不能再依赖那个已经故障的下游------这是最常见的线上翻车点。
  4. 限流拒绝 + 客户端无脑重试 = 二次雪崩:限流返回的必须是可识别的语义(429 / 明确错误码),客户端要做指数退避 + 抖动,否则拒绝越多重试越猛。

常见误区

  • 把"降级"当成万能词用,实际把限流、熔断、fallback 混成一个开关,故障时无法定位到底是哪一层生效。
  • 熔断阈值只看失败率、不看慢调用比例 ------ 而生产事故里"变慢不报错"占绝大多数。
  • 阈值全靠拍脑袋,且从不做故障演练。规则配了但从未被触发验证过,等于没配。
  • 缺少可观测性:至少要暴露限流拦截量、熔断器状态变更事件、降级命中率三组指标并接入告警,否则系统"悄悄降级"了几天都没人知道。

一句话总结 :限流决定放多少进来 ,熔断决定还要不要往外打 ,降级决定打不通时给用户什么。前两者是主动防御,防止过载与传染;降级是被动兜底,把技术故障翻译成用户可接受的体验。三者叠加,再配上超时、隔离、退避重试与可观测性,才构成完整的稳定性纵深防御。

相关推荐
斯维赤1 小时前
Spring AI | Function Calling 是什么?
java·后端
不灭的黄金瞳1231 小时前
Java数据类型与变量
java·开发语言·intellij-idea
shehuiyuelaiyuehao1 小时前
算法47,分治快排,第K大
java
程序猫.1 小时前
算法刷题笔记:模拟题从入门到实战(含 LeetCode 例题与习题)
java·数据结构·算法
Wang's Blog1 小时前
Java 服务器: Linux-MySQL的rpm安装与初始化配置
java·服务器
IT枫斗者枫哥1 小时前
Spring Boot Excel 导入实战:把“导入失败”改成逐行错误报告
java·spring boot
殷紫川1 小时前
Java 27 九大核心特性解析与实战
java
用户094248568031 小时前
第11章:OpenJDK反射、动态代理与 MethodHandle 初探
java·jvm
小蒜学长1 小时前
基于SpringBoot+Vue的游戏论坛系统的设计与实现(代码+数据库+LW)
java·后端·springboot·游戏论坛系统·社区生态