---
title: 服务降级与熔断详解：Fallback、熔断器状态机与 Sentinel / Hystrix / Resilience4j 选型
description: 服务降级与熔断机制详解，讲解 Fallback 兜底、降级开关、熔断器 Closed/Open/Half-Open 状态机、雪崩效应、隔离策略，以及 Sentinel、Hystrix、Resilience4j 的选型对比。
category: 高可用
icon: "mdi:electric-switch"
tag:
  - 服务降级
  - 熔断
head:
  - - meta
    - name: keywords
      content: 服务降级,熔断器,熔断机制,Fallback,熔断器状态机,Sentinel,Hystrix,Resilience4j,限流降级熔断区别,雪崩效应,线程池隔离,信号量隔离,高可用面试题
---

在微服务架构里，一个请求往往要经过好几个服务的调用链。如果链路上某个服务出了问题——比如响应变慢、超时甚至直接挂掉——很容易把上游的资源也拖垮，最后演变成整条链路雪崩。服务降级和熔断就是应对这类问题的两道防线：降级负责在系统压力大的时候主动"丢车保帅"，熔断负责在下游持续异常的时候及时"断路"止损。

这篇文章会把降级和熔断的核心原理讲清楚，包括 Fallback 兜底、降级开关、熔断器状态机、隔离策略，以及 Sentinel、Hystrix、Resilience4j 的选型对比。

## 什么是降级？

服务降级，说白了就是：**系统快扛不住的时候，先牺牲不重要的功能，把资源留给核心链路。**

比如电商大促时，推荐模块、广告位、活动氛围图这些非核心功能可以先关掉，但下单、支付、库存扣减这些链路必须尽量保住。用户少看一个推荐还能接受，但下不了单就是真事故。

所以，降级是一套提前设计好的兜底方案，不是系统挂了以后随便返回个默认值。什么时候触发、关哪些功能、返回什么内容、怎么恢复，都应该提前想清楚。

### 降级一般在什么情况下触发？

很多人会把降级理解成“机器负载太高才降级”，其实不止这一种情况。

常见触发场景有几类：

- 系统整体压力太大，比如 CPU 飙高、线程池打满、接口响应时间明显变长；
- 某个下游服务异常，比如库存服务、价格服务、推荐服务突然大量超时；
- 某个机房、区域或网络链路出问题；
- 大促、秒杀、活动上线前，提前按预案关闭部分非核心功能；
- 运营临时需要控制某些功能入口。

不管触发原因是什么，降级的核心目标都一样：**保核心，弃非核心。**

### 降级可以降到什么粒度？

降级的粒度可以粗，也可以细。

粗一点，可以直接降级整个服务。比如推荐服务异常时，所有推荐结果都返回空列表。

细一点，可以只降级页面里的某个区块。比如商品详情页还正常展示，但“猜你喜欢”区域先隐藏。

再细一点，可以只降级某个接口、某个功能开关，甚至某一类用户、某一个业务场景。

实际项目里，通常会给功能分优先级。比如：

- 推荐、广告、排行榜：优先级较低，出问题可以先关；
- 商品详情、购物车：优先级较高，尽量保；
- 下单、支付、库存：核心链路，最后才考虑降级。

这里的 L0-L4、1-10 级都只是团队内部约定，不是行业统一标准。关键不在于怎么命名，而是要提前分清楚：**哪些功能可以牺牲，哪些功能必须保。**

### 降级方式有哪些？

#### 1. 延迟处理

有些操作没必要同步完成，可以先放到 MQ 里慢慢处理。

比如评论后发积分、统计用户行为、生成报表，这类操作即使晚几秒完成，用户一般也感知不到。主流程只要先返回成功，后面的事情交给异步任务处理即可。

但这里有个坑：MQ 也可能积压。如果消费者已经处理不过来了，生产者还一直重试，反而会把系统打得更严重。所以重试要加退避和随机抖动，必要时还要限流。

#### 2. 页面片段降级

这是最直观的一种方式：页面主体保留，把非核心区块关掉。

比如商品详情页里，商品标题、价格、库存要保；推荐区、广告位、活动挂件可以先隐藏。

这种方式简单有效，但前提是你提前做好了开关和兜底逻辑。真出问题时再改代码、再发版，基本就来不及了。

#### 3. 接口兜底降级

页面里有些接口是异步加载的，比如配送时效、价格预测、推荐列表。它们如果超时，可以直接返回默认值或缓存值。

比如配送时效接口异常时，可以先展示“预计 2-3 天送达”；推荐接口异常时，可以返回缓存里的热门商品。

这里要注意一点：兜底数据最好提前准备好。不要等接口已经挂了，才临时去查数据库或重新生成缓存，那样很可能把故障继续放大。

#### 4. 页面跳转降级

如果完整页面太重，可以把用户导到一个简版页面或静态页面。

比如大促活动页访问量太大时，可以临时切到静态活动页；系统维护时，可以跳到维护提示页。

这种方案看起来简单，但一定要提前准备好静态页、路由规则和切换开关。线上出事后再临时做，基本不现实。

#### 5. 写降级

写降级适合一些“可以晚一点落库”的场景。

比如积分发放、消息通知、库存流水这类操作，可以先写 Redis、消息表或本地持久化队列，再由 MQ 或定时任务慢慢同步到数据库。

但写降级的代价也比较明显：你要处理最终一致性问题。比如消息有没有丢、数据有没有重复写、同步失败后怎么补偿、对账怎么做，这些都要提前设计。

尤其要注意，**纯内存队列不适合保存关键数据**。节点一重启，数据就没了。

#### 6. 读降级

读降级通常是“只读缓存，不再访问后端服务”。

比如商品详情、店铺信息、热门榜单这种读多写少的数据，就很适合做缓存兜底。后端服务异常时，先返回缓存里的旧数据，至少页面还能打开。

如果缓存也没命中，就不要继续硬调下游了。可以直接返回默认值、空结果或降级页。否则用户一次请求可能会把一串后端服务都拖进去。

### 降级开关怎么做？

降级必须能快速打开和关闭。不能每次都靠改代码、发版、重启。

常见方案有这些：

| **方案**        | **特点**                 | **适合场景**                 |
| --------------- | ------------------------ | ---------------------------- |
| 配置文件 + 重启 | 简单，但生效慢           | 非紧急、很少变更的开关       |
| 数据库开关表    | 可以做审计，但实时性一般 | 需要记录谁改了、什么时候改的 |
| 配置中心        | 实时性好，生产环境常用   | 核心降级开关、动态开关       |
| Redis 开关      | 实现轻量，接入快         | 简单业务、临时控制           |

生产环境里，更推荐用 Nacos、Apollo 这类配置中心做降级开关。它们比“改配置文件再重启”可靠得多，也更适合应急场景。

不过，这里不要把“实时推送”理解成“所有机器瞬间同时生效”。以 Nacos 为例，2.x 引入了 gRPC 长连接，配置变化可以通过长连接推给客户端，但真正生效仍然受客户端数量、网络、服务端负载、本地缓存刷新等因素影响。

所以降级开关设计时，要默认接受一个事实：**不同机器之间可能会有短暂不一致。**

### 用 Nacos 做降级开关时要注意什么？

如果项目里用的是 Nacos，有几个坑要提前知道。

第一，Nacos 2.x 和 1.x 的通信方式不完全一样。Nacos 2.x 新增了 gRPC 通信，所以除了主端口 8848，还会用到 9848、9849 这类端口。官方文档也明确提到，9848 是客户端 gRPC 请求服务端端口，9849 是服务端之间通信端口。

第二，升级时要注意兼容性。Nacos 2.0 服务端可以兼容 1.x 客户端，但 2.x 客户端不能连接 1.x 服务端，因为 2.x 客户端会使用 gRPC。

第三，如果中间有 Docker、防火墙、安全组、Nginx、VIP 之类的网络层，端口映射一定要提前处理。尤其是 9848 端口没放开时，客户端可能会出现连接不上、配置拉不到的问题。官方文档也提到，使用 VIP/Nginx 时需要配置 TCP 转发，不能按普通 HTTP 转发来处理。

第四，网络分区或客户端长连接异常时，客户端可能只能继续使用本地旧配置。所以预案里要写清楚：如果降级开关没有及时推到所有节点，系统应该怎么处理。

这也是为什么降级不能只依赖一个开关。关键链路最好还要配合超时、熔断、限流、缓存兜底一起做。

### 服务降级有哪些分类？

降级按照是否自动化可分为：

- **自动开关降级**（由规则引擎、熔断器、限流器或监控告警触发）
- **人工开关降级**（由值班人员或运营预案通过配置平台触发，如秒杀、电商大促等）

自动降级按触发条件分几种常见类型：

- **超时降级**：RT 持续超过阈值（阈值可以参考 P99/P999 监控基线，但具体框架触发是逐请求 RT + 窗口聚合，不是直接拿 P99 比较），触发后返回默认值。需要注意幂等性保护，否则重试风暴比原始故障更麻烦。
- **失败降级**：异常率超过阈值（比如 50%）时触发，返回兜底数据。兜底数据得提前预热到缓存里。
- **故障降级**：下游返回 HTTP 5xx、RPC 异常、DNS 解析失败等硬性错误时触发，返回缓存数据。缓存没命中就直接返回默认值，别再往下调了。
- **限流降级**：QPS 超过阈值时触发，返回排队页、无货提示或错误页。排队页需要防重入（幂等令牌），不然用户狂刷就失去意义了。

> 重试风暴：服务刚恢复，所有客户端同时重试，服务又被打挂。解决办法：带 Jitter 的指数退避、令牌桶限流、分组分批恢复。

## 大规模分布式系统如何降级？

在大规模分布式系统中，经常会有成百上千的服务。在大促前往往会根据业务的重要程度和业务间的关系批量降级。

### 降级平台能力

大型互联网公司通常会有统一的降级平台，需要具备这些能力：

- **分级管理**：服务按优先级分级（1-10 级或 L0-L4，看团队约定），核心业务必须经过评审，依赖关系要提前梳理清楚。
- **批量降级**：按级别或分组批量执行，编排好执行顺序、版本化推送、一键回滚。灰度、预览、审批走完再动手，别一把全关了才发现砍到核心链路。通常不要求严格 2PC 原子性。
- **动态开关**：配置中心（Nacos 2.x gRPC 或 Apollo）实时推送，不需要重启。
- **效果验证**：灰度 + 监控，指标对比确认降级生效且没误伤。
- **一键回滚**：配置版本化，变更审计，出问题了快速回退。

### 降级预案制定

1. **业务分级**：先分清哪些服务不能动（如下单、支付），哪些可以关（如推荐、评论），定义优先级
2. **依赖分析**：画出调用链，找出关键路径和单点依赖——哪个挂了会连累一片
3. **降级策略**：给每个非核心服务设计降级方案，别忘了失败路径怎么处理
4. **演练验证**：定期跑降级演练，纸上预案不跑一遍心里没底

> 网络分区时别纠结理论，想清楚这几件事就行：各服务在分区期间读本地缓存还是拒绝请求、跨区写入要不要停、核心链路怎么保、要不要切成只读模式。
>
> **详细介绍：** [CAP & BASE理论详解](https://javaguide.cn/distributed-system/protocol/cap-and-base-theorem.html)。

## 什么是 Fallback？

Fallback 可以理解成：**兜底结果**。

正常情况下，请求会去调用真实的业务逻辑，比如查推荐、查商品详情、查用户信息。但如果这次调用失败了，或者被限流、熔断规则拦住了，系统不能直接把异常甩给用户，而是要返回一个提前准备好的结果。

比如推荐服务挂了，就先返回空列表；商品详情接口超时了，就返回缓存里的旧数据；活动页接口异常了，就切到静态页面。

这就是 Fallback 的作用：**别让一次失败，把整个链路都拖垮。**

不过要注意，Fallback 不是用来“修好问题”的，它只是让系统在异常情况下还能勉强可用。用户看到的内容可能不完整，也可能不是最新的，但至少页面不会直接炸掉。

### Sentinel 里的 blockHandler 和 fallback 有什么区别？

在 Sentinel 里，`blockHandler` 和 `fallback` 很容易被混在一起。它们都像是在“兜底”，但触发原因不一样。

简单说：

| **场景**                                                 | **走哪个方法** | **常见异常**   |
| -------------------------------------------------------- | -------------- | -------------- |
| 被 Sentinel 规则拦住，比如限流、熔断、系统保护           | blockHandler   | BlockException |
| 业务代码自己抛异常，比如空指针、远程调用失败、运行时异常 | fallback       | Throwable      |

举个例子。

接口被限流了，Sentinel 判断这个请求不能继续往下走，这时候进入 `blockHandler`。

如果请求已经进入业务方法了，但是业务里调用库存服务失败，抛了 `RuntimeException`，这时候才会进入 `fallback`。

如果 `blockHandler` 和 `fallback` 都配置了，Sentinel 规则触发的异常会优先进入 `blockHandler`；业务异常才进入 `fallback`。这一点别搞反，不然排查线上问题时会很绕。

![Sentinel：blockHandler 与 Fallback 的区别](https://oss.javaguide.cn/github/javaguide/high-availability/fallback-and-circuit-breaker-blockHandler-vs-Fallback.png)

### 常见的 Fallback 做法

常见的 Fallback 做法有这么几种：

- **默认值返回**：直接返回一个静态默认对象。推荐列表为空就返回 `[]`，配置查不到就返回默认配置。注意默认值的结构要跟正常返回一致，别返回 null，不然上游拿到 null 直接 NPE。
- **缓存兜底**：读本地缓存或 Redis 里最近一次成功的结果。商品详情、用户信息这种读场景很适合。缓存可能过期，返回的时候最好标注一下数据时效；缓存穿透时别硬撑，兜底到默认值。
- **降级页面**：返回静态 HTML 或简化版页面，活动页、营销模块经常这么干。但静态资源本身也要有容灾——CDN 回源失败时的处理得提前想好。
- **写降级**：先写 Redis 或本地消息表，异步同步到 DB。秒杀扣减、积分发放等高并发写场景常用。代价是需保证最终一致性（对账/补偿），纯内存队列在节点宕机时会丢数据。

### Fallback 常见的坑

Fallback 本身也可能出问题，而且这种问题比正常故障更难发现。常见的坑有这几个：

- **Fallback 里面藏了远程调用**：兜底逻辑里调了 Redis、DB 或其他服务，结果这些依赖也挂了，雪崩反而加剧。优先用 Caffeine、本地 Map 或静态默认值；非得访问 Redis 的话，用独立连接池、独立超时和独立熔断器，缓存没命中就返回默认值，别再往下串了。
- **Fallback 返回 null**：上游拿到 null 直接 NPE，引发新的异常链。返回结构要跟正常返回一致，用空对象（Empty Object）而不是 null。
- **Fallback 逻辑太复杂**：兜底本身变慢，拖垮调用线程。Fallback 逻辑必须极简，禁止复杂业务处理。
- **多个 Fallback 共用同一个缓存**：缓存被打满，所有兜底同时失效。按业务维度隔离缓存，核心链路独立缓存实例。

说白了：Fallback 是最后一道防线，必须比正常调用更快更简单。如果兜底里面还藏着同步远程调用且没超时保护，那不是兜底，是埋雷。

## 什么是熔断？

熔断，名字听起来有点抽象，简单理解就是及时止损。它就是应对微服务雪崩效应的一种链路保护机制，类似电路中的保险丝。

在微服务里，一个服务经常要调用另一个服务。正常情况下，请求一路往下走：

```text
服务 A -> 服务 B -> 服务 C
```

如果服务 C 突然变慢，服务 B 调 C 的线程就会一直等。B 的线程被占满后，A 调 B 也开始超时。再往上，调用 A 的服务也会被拖住。

最后就会出现一种很典型的情况：**明明只是服务 C 出了问题，结果整条链路都被拖垮了。**

熔断器就是为了解决这个问题。

它的思路很简单：如果发现下游已经明显不正常，就先别继续打它了，直接返回兜底结果。等一段时间后，再放少量请求过去试探一下。如果下游恢复了，再慢慢恢复正常调用。

这有点像电路里的保险丝。电流异常时先断开，避免把后面的设备一起烧坏。

### 雪崩是怎么发生的？

很多线上雪崩不是一瞬间发生的，而是一层一层传开的。

比如还是这条链路：

```text
服务 A -> 服务 B -> 服务 C
```

一开始可能只是服务 C 响应变慢。比如原来 50ms 返回，现在要 3 秒。

接着，服务 B 调 C 的线程开始排队。线程池里的线程都在等 C 返回，新的请求进来只能继续排队。

再往后，服务 A 调 B 也开始超时。A 自己的线程也被占住，接口响应越来越慢。

如果这时候客户端、RPC 框架或者业务代码还在不停重试，请求量会被进一步放大。最后不是一个服务慢，而是一串服务都慢，整条链路都开始不可用。

这就是雪崩效应。

![雪崩效应传播](https://oss.javaguide.cn/github/javaguide/high-availability/fallback-and-circuit-breaker-avalanche-effect-spread.png)

### 一个更接近真实情况的例子

假设有一个广告点击链路：

```text
广告点击服务 A -> 渠道过滤服务 B -> Redis
```

正常情况下，A 每分钟处理 3-4 万次点击。有一天流量突然涨到 8 万多。

问题一开始出在 B。B 需要从 Redis 里读一段过滤数据，但这个 value 太大，`get` 一次要 3 秒多。

于是 A 调 B 开始大量超时。

如果 Dubbo 这类 RPC 接口还配置了失败重试，比如一次初始调用后又重试 2 次，那原本 1 次调用就可能变成 3 次调用。这里要注意，具体会不会这样，取决于 Dubbo 版本、接口配置和调用类型。尤其是写接口，一般不建议默认重试，至少要非常谨慎。

重试一多，B 收到的请求更多，线程池更快被打满，然后开始拒绝请求。A 这边继续收到超时和异常，连接、文件句柄、线程资源也被消耗。

最后，原本只是 Redis 读取慢，结果一路拖垮了 A、B 和上游链路。

这类事故里，重试经常是“加速器”。没有限流和熔断时，它会把一个局部故障放大成全链路故障。

所以线上不是不能重试，而是重试必须有边界：要有超时时间、最大次数、退避策略，还要配合限流和熔断。

### 熔断器有哪几种状态？

熔断器包含三种状态：

| 状态                 | 说明                   | 行为                                   | 状态转换条件                                                                                                                |
| -------------------- | ---------------------- | -------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Closed（关闭）**   | 正常状态，允许请求通过 | 记录失败率/慢调用比例                  | 失败率/慢调用比例 > 阈值 → Open                                                                                             |
| **Open（打开）**     | 熔断触发，拒绝请求     | 快速返回 Fallback，不再调用下游        | 经过冷却时间（不同框架名称不同，如 Hystrix 的 sleepWindow、Resilience4j 的 waitDurationInOpenState，典型值 10s） → HalfOpen |
| **HalfOpen（半开）** | 探测服务是否恢复       | 放行少量探路请求（数量取决于框架实现） | 探测请求满足成功条件 → Closed；失败 → Open                                                                                  |

![熔断器状态机](https://oss.javaguide.cn/github/javaguide/high-availability/fallback-and-circuit-breaker-fuse-state-machine.png)

### Half-Open 和 Warm Up 不是一回事

这两个概念很容易混。

Half-Open 解决的是：**下游到底恢复没有？**

Warm Up 解决的是：**恢复以后，流量要不要慢慢放？**

也就是说，Half-Open 更像“探测”，Warm Up 更像“预热放量”。

比如服务刚重启，缓存还没热，JIT 也没完全稳定，数据库连接池也刚开始建立。这时候即使探测成功，也不代表马上能扛住全部流量。

所以更稳的做法是：

1. Half-Open 先放少量请求探测；
2. 探测成功后，不要立刻打满流量；
3. 再用 Warm Up、限流或者分批放量，让服务慢慢恢复。

Sentinel 的 Warm Up 属于流控里的冷启动策略，默认 `coldFactor` 是 3，也就是一开始从 `threshold / 3` 左右的 QPS 慢慢升到配置阈值。

恢复期间不要只看平均耗时。平均值很容易掩盖少量极慢请求。更建议同时盯 P99、P999、线程池活跃数、数据库连接池活跃数和错误率。

### Sentinel 支持哪些熔断策略？

Sentinel 1.8.0 之后，熔断降级主要有三类策略：慢调用比例、异常比例、异常数。官方文档里的 DegradeRule 也说明了这些字段，比如 `count`、`timeWindow`、`minRequestAmount`、`statIntervalMs`、`slowRatioThreshold` 等。

**1. 慢调用比例**

这种策略看的是慢请求占比。

比如你设置最大 RT 是 500ms，那么超过 500ms 的请求就算慢调用。接着 Sentinel 会在一个统计窗口里计算慢调用比例。

如果窗口里的请求数达到最小请求数，并且慢调用比例超过阈值，就触发熔断。

这个策略适合处理“下游没报错，但越来越慢”的情况。

**2. 异常比例**

这种策略看的是失败请求占比。

比如最近一段时间里，请求数量达到最低要求，并且异常比例超过 50%，就触发熔断。

它适合那种下游开始大量报错的场景，比如 RPC 异常、运行时异常、服务端 5xx。

**3. 异常数**

这种策略看的是异常次数。

比如 1 分钟内异常数超过 50 次，就触发熔断。

它比异常比例更直观，但对流量大小比较敏感。高 QPS 接口和低 QPS 接口不能套同一套阈值。

### P99 能不能直接当熔断条件？

不建议这么理解。

P99 是监控指标，适合用来观察接口整体表现，也适合辅助你设置熔断阈值。

但 Sentinel 触发熔断时，不是简单拿 P99 跟阈值比较。它的逻辑更接近：

1. 先看单次请求是不是慢调用或异常；
2. 再放到统计窗口里计算比例或数量；
3. 请求数达到最小阈值后，再判断是否触发熔断。

所以 P99 更适合回答这个问题：**我的阈值设得合不合理？**

比如某接口平时 P99 是 120ms，你把慢调用阈值设成 100ms，那很可能误伤。

如果平时 P99 是 120ms，突然涨到 800ms，那就说明下游可能真的有问题，熔断阈值也需要结合这个基线来评估。

低 QPS 接口尤其要小心。如果窗口太短、最小请求数太小，一两个偶发慢请求就可能触发熔断。更稳的做法是适当拉长统计窗口，或者提高最小请求数。

## 降级和熔断有什么区别？

降级和熔断经常被放在一起说，但它们解决的问题不一样。

降级关注的是：系统出问题后，还能以什么方式继续服务用户。

比如推荐服务挂了，商品详情页别跟着白屏，可以先隐藏推荐区；活动接口超时了，可以先展示静态活动页；用户标签查不到，可以先按普通用户处理。

也就是说，降级更像是业务层面的取舍：哪些功能可以先牺牲，哪些核心链路必须保住。

熔断关注的是：下游已经不正常了，还要不要继续调用。

比如订单服务调用库存服务，库存服务已经大量超时。这时候如果还继续打请求，只会让库存服务更慢，也会把订单服务自己的线程池拖死。熔断器发现下游异常后，会先拦住请求，快速返回兜底结果，等一段时间后再放少量请求过去探测。

一句话记：**降级是"出了问题后怎么继续服务"，熔断是"异常依赖还要不要继续调"。**

### 限流、熔断、降级分别管什么？

这三个概念也很容易混。

**限流管入口。** 它解决的是"流量太多，要不要都放进来"。比如秒杀时 10 万个请求同时打进来，系统只能扛 1 万个，那就必须拦掉一部分。

**熔断管下游。** 它解决的是"下游已经慢了或挂了，要不要继续调"。如果继续调，下游恢复不了，自己也会被拖垮。

**降级管结果。** 它解决的是"请求被限流了、下游被熔断了、业务出异常了，接下来给用户返回什么"。可以返回默认值、缓存数据、静态页面，也可以关闭某个非核心功能。

更口语一点说：

- **限流**：别让太多请求进来
- **熔断**：别再打已经异常的下游
- **降级**：出问题后换一种方式继续服务
- **Fallback**：真正返回的兜底结果

Fallback 不是一个独立的治理策略，它更像是限流、熔断、降级触发后的"兜底动作"。

> 打个比方：把系统想象成一个商场。限流就是控制进商场的人数，人太多了先在门口排队；降级就是商场临时关闭餐饮区和电影院，但超市、药店这些核心区域继续营业；熔断就是发现某个供应商出问题了，先暂停向它下单，过一会儿再少量试几单看看恢复没有。

### 一次请求里它们怎么协同？

一条真实请求链路里，限流、熔断、降级通常是分层生效的。

1. **网关/入口限流**：请求刚进来时，先判断入口流量有没有超过系统承载能力。如果超过了，直接返回"系统繁忙，请稍后再试"，不要让流量继续往后打。
2. **服务内限流**：有些请求过了网关，但某个服务自己的线程池、连接池、数据库已经快扛不住了，这时服务内部也可以做限流，保护自己的关键资源。
3. **熔断器判断**：服务准备调用下游之前，先看这个下游是不是已经处于熔断状态。如果已经熔断，就不要继续调了，直接走兜底逻辑。
4. **业务执行与 Fallback**：下游调用失败、业务代码抛异常，或者规则拦截了请求，就返回提前准备好的兜底结果。比如空列表、缓存数据、默认对象、静态页面。

一句话总结：**限流拦入口，熔断断下游，降级保核心，Fallback 给兜底结果。**

![请求保护流程](https://oss.javaguide.cn/github/javaguide/high-availability/fallback-and-circuit-breaker-request-protection-process.png)

### Sentinel 注解方式的最小示例

下面这个例子先看懂 `blockHandler` 和 `fallback` 的分工就行：

```java
@RestController
public class OrderController {

    @SentinelResource(
        value = "createOrder",
        blockHandler = "createOrderBlockHandler",
        fallback = "createOrderFallback",
        exceptionsToIgnore = {IllegalArgumentException.class}
    )
    @PostMapping("/order")
    public OrderVO createOrder(@RequestBody OrderDTO dto) {
        return orderService.create(dto);
    }

    // 限流、熔断、系统保护等 Sentinel 规则触发时，进入 blockHandler
    public OrderVO createOrderBlockHandler(OrderDTO dto, BlockException ex) {
        return OrderVO.degraded("当前下单人数过多，请稍后重试");
    }

    // 业务异常进入 fallback
    // 方法签名需要和原方法一致，或者额外多一个 Throwable 参数
    public OrderVO createOrderFallback(OrderDTO dto, Throwable t) {
        return OrderVO.failure("下单失败，请稍后再试");
    }
}
```

有几个点要讲清楚：

`blockHandler` 处理的是 Sentinel 拦截类异常，比如限流、熔断、系统保护触发后的 `BlockException`。`fallback` 处理的是业务代码里抛出来的异常，比如远程调用失败、运行时异常等。它的方法签名要和原方法一致，或者额外加一个 `Throwable` 参数。

如果 `blockHandler` 和 `fallback` 同时配置，Sentinel 规则触发的异常只会进入 `blockHandler`，不会进入 `fallback`。这个地方很容易写错。

另外，不是所有业务异常都应该 fallback。比如参数校验失败、权限不足、库存不足，这些本来就应该明确告诉上游，不应该被统一兜底成"系统繁忙"。这类异常可以用 `exceptionsToIgnore` 排除掉。

## 有哪些现成解决方案？

Spring Cloud 体系里，常见的熔断、限流、降级方案主要有这几个：

- **Hystrix 1.5.18**：老牌组件，很多早期 Spring Cloud Netflix 项目都用过。但它已经进入维护模式，不再主动迭代，新项目一般不建议再选。
- **Sentinel 1.8.x**：阿里开源的流量治理组件，国内 Spring Cloud Alibaba 项目里很常见。它不只是熔断，还包含限流、流量整形、热点参数限流、系统自适应保护等能力。
- **Resilience4j 2.x**：轻量级容错库，模块拆得比较细，包含熔断、限流、重试、舱壁隔离等能力。Spring Boot 3 / Java 17 项目里经常会考虑它。
- **Spring Retry**：主要解决重试问题，本身不是完整的熔断降级框架。一般会配合 Spring Cloud CircuitBreaker 或业务超时策略一起用。

版本号最好不要写死。Sentinel、Resilience4j 都还在更新，本文基于发文时可查的 Release，具体以各项目 GitHub Release 页面和版本矩阵为准。

### Hystrix、Sentinel、Resilience4j 怎么选？

不用一上来就背功能表，先按项目情况判断。

**老项目 Hystrix：能稳定跑就先别急着动，但要规划迁移。**

Hystrix 当年很火，线程池隔离、熔断、Fallback 都是它带起来的。但 Netflix 官方已经说明，Hystrix 现在处于维护模式，不再活跃开发。

如果是老项目已经用了 Hystrix，不需要为了"新"马上推倒重来。只要系统稳定，可以先继续用，同时把迁移排进后续 Spring Boot / Spring Cloud 升级计划里。但新项目就没必要再从 Hystrix 开始了，后面升级 Java、Spring Boot、Spring Cloud 时，兼容性成本会越来越高。

**Spring Cloud Alibaba / Dubbo 项目：优先看 Sentinel。**

Sentinel 的优势不是单纯"能熔断"，而是它围绕流量治理做得比较完整。它支持限流、熔断降级、热点参数限流、系统自适应保护，也有控制台可以看实时监控和配置规则。对 Spring Cloud Alibaba、Dubbo 这类项目来说，接入成本通常更低。

但也别把 Sentinel Dashboard 当成生产配置中心。Dashboard 更适合做规则管理和查看效果，生产环境通常还要接 Nacos、Apollo 等数据源做规则持久化，否则重启后规则可能丢。

**Spring Boot 3 / Java 17+ 新项目：可以优先看 Resilience4j。**

Resilience4j 的特点是轻量、模块化。你需要熔断，就引 CircuitBreaker；需要限流，就引 RateLimiter；需要重试，就引 Retry；需要隔离，就引 Bulkhead。

它和函数式编程、Reactor、Spring Boot 3 的组合会更自然一些。如果你的项目是 Spring Boot 3、Java 17+，又不依赖 Spring Cloud Alibaba 生态，Resilience4j 通常是更干净的选择。

### 简单对比

| 维度               | Sentinel                                    | Hystrix                  | Resilience4j                           |
| ------------------ | ------------------------------------------- | ------------------------ | -------------------------------------- |
| **维护状态**       | 活跃维护                                    | 维护模式                 | 活跃维护                               |
| **主要定位**       | 流量治理组件                                | 熔断降级组件             | 轻量级容错库                           |
| **熔断能力**       | 慢调用比例、异常比例、异常数                | 异常比例为主             | 异常比例、慢调用、异常数等             |
| **限流能力**       | 强，支持 QPS、并发线程、热点参数等          | 较弱                     | 有 RateLimiter 模块                    |
| **隔离能力**       | 并发线程数控制                              | 线程池隔离 / 信号量隔离  | SemaphoreBulkhead / ThreadPoolBulkhead |
| **流量整形**       | 支持 Warm Up、匀速排队                      | 不突出                   | 不突出                                 |
| **系统自适应保护** | 支持                                        | 不支持                   | 不支持                                 |
| **控制台**         | 有 Sentinel Dashboard                       | Hystrix Dashboard 已过时 | 通常自行接监控体系                     |
| **适合场景**       | Spring Cloud Alibaba、Dubbo、国内 Java 项目 | 存量老项目               | Spring Boot 3、Java 17+、轻量化项目    |

初学者只要记住三点：老项目 Hystrix 能稳定跑就先别急着动；Spring Cloud Alibaba / Dubbo 项目优先看 Sentinel；Spring Boot 3 / Java 17+ 新项目可以优先看 Resilience4j。

### 隔离策略怎么理解？

熔断只是"不继续调异常下游"，隔离解决的是"别让一个依赖把所有资源吃光"。

**线程池隔离**：给某个下游依赖单独分一个线程池。比如订单服务要调用库存服务，就让库存调用走自己的线程池。库存服务慢了，最多把这个线程池打满，不会把订单服务的主线程池一起拖死。优点是隔离更彻底，坏处是成本更高——线程多了以后会有上下文切换开销，线程池大小也不好估，P99 可能变差。Hystrix 默认就是线程池隔离。

**信号量隔离**：限制同时进来的请求数量。比如最多允许 100 个请求同时调用库存服务，超过就直接拒绝或降级。优点是轻量，不需要把业务调用切到另一个线程池里。缺点是如果下游调用本身一直阻塞，业务线程还是会被占住，所以通常要配合超时控制一起用。Sentinel 常见的并发线程数控制更接近信号量隔离的思路。

> 线程池隔离的代价不是很多人以为的"GC 扫描"，而是上下文切换。线程多了，CPU 在线程间频繁调度唤醒和挂起，sy 飙高，P99 尾延迟跟着恶化。到底严不严重，得看线程数、CPU sy/us 比例、队列等待时间和 P99 指标，别光凭感觉。

![隔离策略对比](https://oss.javaguide.cn/github/javaguide/high-availability/fallback-and-circuit-breaker-isolation-strategy-comparison.png)

### Sentinel 的系统自适应保护是什么？

普通限流通常是手动设置一个阈值，比如 QPS 超过 1000 就限流。但真实系统不是这么简单，CPU、Load、RT、线程数、入口 QPS 都会影响系统是否已经接近极限。

Sentinel 的系统自适应保护就是想解决这个问题：不只看单个接口，而是从整个系统负载角度判断要不要拒绝部分请求。官方文档里说这个思路受 TCP BBR 启发，目标是在保证系统可靠的前提下维持较高吞吐。但别把它理解成完整的 TCP BBR——Sentinel 是借鉴思路，结合实时统计和规则阈值做保护，不是像 TCP 那样动态探测带宽、调整拥塞窗口。

可以粗略理解成：

**`当前并发请求数 > 系统最大 QPS × 最小 RT`**

如果当前在途请求已经超过系统估算的承载能力，就开始拒绝一部分流量。系统规则还可以按这些指标配置：

| 指标           | 说明                 | 常见用法                         |
| -------------- | -------------------- | -------------------------------- |
| **Load**       | Linux `load1` 值     | Load 明显高于 CPU 核数时触发保护 |
| **平均 RT**    | 入口流量平均响应时间 | 平均耗时持续升高时保护系统       |
| **并发线程数** | 当前处理中的请求数   | 防止线程资源被打满               |
| **入口 QPS**   | 入口流量大小         | 控制整体入口压力                 |

有个坑要注意：Sentinel 系统规则看的是平均 RT，不是 P99。平均值会掩盖长尾延迟，所以生产环境最好额外接 P99、P999、线程池、连接池、错误率这些监控。

### 选型建议

| 场景                            | 推荐方案                                   | 原因                                         |
| ------------------------------- | ------------------------------------------ | -------------------------------------------- |
| 存量 Hystrix 项目               | 先继续用，规划迁移                         | 不要为了替换而替换，优先保证稳定             |
| Spring Cloud Alibaba 项目       | Sentinel                                   | 生态适配好，控制台和规则模型更完整           |
| Dubbo 项目                      | Sentinel                                   | 接入和流量治理能力比较成熟                   |
| Spring Boot 3 / Java 17+ 新项目 | Resilience4j 或 Sentinel                   | Resilience4j 更轻量，Sentinel 治理能力更完整 |
| 只需要重试                      | Spring Retry / Resilience4j Retry          | 不要为了重试引完整治理框架                   |
| 需要系统级过载保护              | Sentinel，或网关 / Service Mesh / 自研平台 | Sentinel 三者里更完整，但不是唯一方案        |

如果团队已经在用 Nacos、Spring Cloud Alibaba，选 Sentinel 的成本会比较低。如果团队偏原生 Spring Boot 3、Micrometer、Prometheus 这套监控体系，Resilience4j 往往更自然。

### Hystrix 迁移到 Sentinel 要注意什么？

老项目从 Hystrix 迁到 Sentinel，不是简单把注解换一下。

**1. Fallback 要拆开。** Hystrix 里很多项目只有一个 `fallbackMethod`，限流、熔断、业务异常最后都走同一套兜底。Sentinel 里要分清：`blockHandler` 处理 Sentinel 规则拦截（限流、熔断、系统保护），`fallback` 处理业务代码抛出的异常。迁移时，原来的 fallback 逻辑最好拆一拆，不要混在一起。

**2. 隔离模型不一样。** Hystrix 默认线程池隔离，很多调用实际是在 Hystrix 线程池里跑的。Sentinel 更常见的是并发线程数控制，不会天然把下游调用切到独立线程池里。迁移后要重新检查超时、中断、线程池隔离这些能力，尤其是慢下游调用，如果没有单独超时控制，只靠 Sentinel 并发线程数限制不够。

**3. 规则配置方式不一样。** Hystrix 很多规则写在代码或配置文件里。Sentinel 更推荐把规则动态化，比如通过 Dashboard 管理，再接 Nacos、Apollo 等数据源做持久化和推送。生产环境不要只依赖 Dashboard 内存规则，否则服务重启、控制台重启后可能有规则丢失风险。

**4. 监控体系要重新接。** Hystrix 时代常见的是 Hystrix Dashboard + Turbine。迁到 Sentinel 后，可以先用 Sentinel Dashboard 看实时调用和规则效果，但生产上通常还要把指标接到 Prometheus、Grafana 或公司内部监控平台。如果用了集群限流，还要额外考虑 Token Server 的部署和可用性。

> 迁移最好跟着 Spring Boot / Spring Cloud 大版本升级一起做，单独迁移容易踩兼容性坑。如果存量系统到处都是 Hystrix 注解，可以先试 Sentinel 的 [Hystrix adapter](https://github.com/alibaba/Sentinel/tree/master/sentinel-adapter/sentinel-hystrix) 过渡一下，但这个 adapter 本身的维护状态也要看一眼再决定。

### 小结

新项目一般不建议再选 Hystrix，它已经进入维护模式，更适合作为存量系统的过渡方案。如果项目在 Spring Cloud Alibaba、Nacos、Dubbo 这条技术线上，Sentinel 通常更顺手。如果是 Spring Boot 3 / Java 17+ 的新项目，团队又更偏 Micrometer、Prometheus、Reactor 这套生态，Resilience4j 会更轻量。

真正选型时别只看"谁功能多"，要看团队技术栈、Spring Cloud 版本、监控体系、规则是否要动态下发，以及后续谁来维护。
