1. 引言:为什么熔断限流是微服务的必修课
微服务架构把单体拆成了几十上百个服务,带来了独立部署、独立扩容的好处,也带来了一个致命问题:故障会沿着调用链快速放大。
一个典型的雪崩场景是这样的:
- 服务 A 调用服务 B,服务 B 因为某个慢 SQL 响应变慢;
- 服务 A 的线程池被慢请求占满,开始排队;
- 服务 A 的上游服务 C 调用 A 也开始超时,线程池同样被占满;
- 故障沿着调用链一路上溯,最终整个系统不可用。
熔断和限流就是用来在故障扩散之前踩刹车的两类手段:
| 手段 | 解决的问题 | 核心思想 |
|---|---|---|
| 限流(Rate Limiting) | 流量超过系统承载能力 | 主动拒绝超额流量,保护自己 |
| 熔断(Circuit Breaking) | 下游服务已经不可用 | 快速失败,不再把请求打过去 |
| 隔离(Bulkhead) | 单个依赖拖垮整个服务 | 资源池隔离,故障不扩散 |
| 降级(Fallback) | 依赖不可用时如何兜底 | 返回兜底数据或友好提示 |
| 超时(Timeout) | 请求无限等待 | 给每次调用设定时间上限 |
这五者通常组合使用,本文会围绕 Spring Cloud 生态把它们串起来讲清楚。
2. 核心概念辨析:熔断、限流、降级、隔离到底有什么区别
很多同学把熔断和限流混为一谈,其实它们的触发条件和作用对象完全不同。
2.1 限流:保护自己不被压垮
限流是入口侧的保护,作用对象是"打到我这里的流量"。当 QPS 超过阈值时,直接拒绝或排队。
常见限流算法:
- 计数器(固定窗口):实现最简单,但存在临界问题(窗口边界瞬间双倍流量)。
- 滑动窗口:把窗口切分成多个小格,平滑统计,Sentinel 默认采用。
- 漏桶(Leaky Bucket):以恒定速率流出,削峰填谷,但无法应对突发流量。
- 令牌桶(Token Bucket):以恒定速率放令牌,允许一定程度的突发,Guava RateLimiter、Sentinel 的匀速排队模式都基于此。
2.2 熔断:保护自己不被下游拖死
熔断是出口侧的保护,作用对象是"我要调用的下游"。当下游错误率或慢调用比例超过阈值时,断路器打开,后续请求直接走 fallback,不再真正发起调用。
断路器有三种状态:
#mermaid-svg-Aj9BHiRKccQvE2Bz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Aj9BHiRKccQvE2Bz .error-icon{fill:#552222;}#mermaid-svg-Aj9BHiRKccQvE2Bz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Aj9BHiRKccQvE2Bz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Aj9BHiRKccQvE2Bz .marker.cross{stroke:#333333;}#mermaid-svg-Aj9BHiRKccQvE2Bz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Aj9BHiRKccQvE2Bz p{margin:0;}#mermaid-svg-Aj9BHiRKccQvE2Bz defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-Aj9BHiRKccQvE2Bz g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-Aj9BHiRKccQvE2Bz g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-Aj9BHiRKccQvE2Bz g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-Aj9BHiRKccQvE2Bz g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-Aj9BHiRKccQvE2Bz g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-Aj9BHiRKccQvE2Bz .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-Aj9BHiRKccQvE2Bz .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-Aj9BHiRKccQvE2Bz .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Aj9BHiRKccQvE2Bz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Aj9BHiRKccQvE2Bz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Aj9BHiRKccQvE2Bz .edgeLabel .label text{fill:#333;}#mermaid-svg-Aj9BHiRKccQvE2Bz .label div .edgeLabel{color:#333;}#mermaid-svg-Aj9BHiRKccQvE2Bz .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-Aj9BHiRKccQvE2Bz .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-Aj9BHiRKccQvE2Bz .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-Aj9BHiRKccQvE2Bz .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-Aj9BHiRKccQvE2Bz .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-Aj9BHiRKccQvE2Bz .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Aj9BHiRKccQvE2Bz #statediagram-barbEnd{fill:#333333;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .cluster-label,#mermaid-svg-Aj9BHiRKccQvE2Bz .nodeLabel{color:#131300;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-Aj9BHiRKccQvE2Bz .note-edge{stroke-dasharray:5;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-note text{fill:black;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram-note .nodeLabel{color:black;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagram .edgeLabel{color:red;}#mermaid-svg-Aj9BHiRKccQvE2Bz #dependencyStart,#mermaid-svg-Aj9BHiRKccQvE2Bz #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-Aj9BHiRKccQvE2Bz .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Aj9BHiRKccQvE2Bz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} "错误率/慢调用比例超阈值"
"等待时间窗口结束"
"探测请求成功"
"探测请求失败"
Closed
Open
HalfOpen
- Closed(关闭):正常放行,统计失败率。
- Open(打开):直接拒绝,走降级逻辑。
- Half-Open(半开):放行少量探测请求,成功则恢复,失败则继续打开。
2.3 降级:熔断之后的兜底动作
降级不是独立机制,而是熔断、限流、超时触发后的处理策略。常见做法:
- 返回缓存中的旧数据;
- 返回默认值(如空列表、默认商品推荐);
- 返回友好提示("当前人数较多,请稍后再试");
- 异步化处理(写入 MQ 稍后补偿)。
2.4 隔离:让故障止步于一个资源池
隔离分为两种:
- 线程池隔离:每个依赖独享线程池,某个依赖变慢只耗尽自己的线程。Hystrix 的经典做法,代价是线程上下文切换开销。
- 信号量隔离:用计数器限制并发数,轻量但无法异步超时。Sentinel 默认采用。
3. 技术选型:当前主流的可选方案对比
Spring Cloud 生态里,熔断限流组件经历过几代更替,选型时要特别注意版本兼容性。
3.1 主流方案一览
| 方案 | 定位 | 是否活跃维护 | 与 Spring Cloud 集成方式 | 推荐场景 |
|---|---|---|---|---|
| Sentinel | 流量治理全家桶(限流、熔断、热点、系统自适应) | 活跃(阿里) | spring-cloud-starter-alibaba-sentinel |
国内主流首选,功能最全 |
| Resilience4j | 轻量容错库(熔断、限流、重试、隔离) | 活跃 | spring-cloud-starter-circuitbreaker-resilience4j |
Spring Cloud 官方推荐,函数式友好 |
| Spring Cloud CircuitBreaker | 抽象层 | 活跃 | 统一 API,底层可切 Resilience4j | 需要屏蔽实现细节时 |
| Hystrix | 老一代熔断 | 已停止维护 | spring-cloud-starter-netflix-hystrix |
仅存量系统维护,新项目不要用 |
| Gateway + Redis 限流 | 网关层限流 | 活跃 | RequestRateLimiter 过滤器 |
全局限流、API 维度限流 |
| Bucket4j | 令牌桶限流库 | 活跃 | 手动集成 | 需要精细控制单机限流时 |
3.2 版本兼容性提醒(非常重要)
Spring Cloud 2020.0(Ilford)之后,Netflix 组件(Hystrix、Ribbon、Zuul)被正式移除。选型时务必对齐版本:
- Spring Cloud 2020.x / 2021.x / 2022.x / 2023.x:官方推荐 Resilience4j,或使用 Spring Cloud Alibaba 的 Sentinel。
- Spring Cloud Alibaba 2022.x:对应 Spring Boot 3.x,Sentinel 版本需 ≥ 1.8.6。
- Spring Boot 3.x :注意
javax.*已迁移到jakarta.*,老版本 Sentinel 的某些适配器可能不兼容。
经验教训:升级 Spring Boot 3 时,很多团队卡在 Sentinel 与 Jakarta 命名空间的兼容问题上,建议先在小服务验证再全量升级。
3.3 选型建议
- 国内团队、需要控制台可视化、需要热点参数限流:选 Sentinel。
- 追求轻量、偏好 Spring 官方生态、需要函数式编程:选 Resilience4j。
- 网关层统一限流 :Spring Cloud Gateway 的
RequestRateLimiter+ Redis。 - 存量 Hystrix 系统:制定迁移计划,逐步替换为 Resilience4j 或 Sentinel。
4. 使用场景梳理:什么情况下该上熔断限流
不是所有接口都需要熔断限流,盲目加会带来额外复杂度和误杀风险。以下是典型场景。
4.1 必须限流的场景
- 秒杀、抢购、大促:瞬时流量是平时的几十倍,不限流必然打垮数据库。
- 开放平台 API:按 AppKey 做配额,防止单个调用方拖垮整体。
- 登录、短信验证码接口:防止恶意刷接口造成资损。
- 写操作接口:数据库写入能力有限,需要保护。
- 第三方回调入口:外部流量不可控,必须设上限。
4.2 必须熔断的场景
- 强依赖外部第三方服务(支付、地图、短信):对方不可用时快速失败,避免线程堆积。
- 调用链较深的核心链路:下游某个非核心服务变慢,不能拖垮主链路。
- 数据库/缓存访问层:慢查询导致连接池耗尽时,熔断可以保护连接池。
- 跨机房、跨区域调用:网络抖动概率高,需要快速失败。
4.3 不需要或需谨慎的场景
- 纯内存计算、无外部依赖的接口:限流意义不大。
- 内部低频管理后台接口:加熔断反而增加排查难度。
- 强一致性要求的核心交易:降级返回兜底数据可能造成业务错误,需要业务侧确认降级语义。
5. Sentinel 实战:从接入到规则配置
Sentinel 是国内使用最广的方案,下面给出完整的落地步骤。
5.1 依赖引入
xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 规则持久化到 Nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
5.2 基础配置
yaml
spring:
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080 # Sentinel 控制台地址
port: 8719 # 客户端与控制台通信端口
eager: true # 启动即建立心跳,避免首次调用才注册
datasource:
flow:
nacos:
server-addr: 127.0.0.1:8848
data-id: ${spring.application.name}-flow-rules
group-id: SENTINEL_GROUP
rule-type: flow
degrade:
nacos:
server-addr: 127.0.0.1:8848
data-id: ${spring.application.name}-degrade-rules
group-id: SENTINEL_GROUP
rule-type: degrade
5.3 定义资源与降级方法
java
@Service
public class OrderService {
// 方式一:注解定义资源,blockHandler 处理限流,fallback 处理异常
@SentinelResource(
value = "createOrder",
blockHandler = "createOrderBlockHandler",
fallback = "createOrderFallback"
)
public Order createOrder(Long userId, Long skuId) {
// 调用库存、优惠券等下游服务
return orderClient.create(userId, skuId);
}
// 限流/熔断触发时执行,签名需与原方法一致并多一个 BlockException 参数
public Order createOrderBlockHandler(Long userId, Long skuId, BlockException ex) {
log.warn("createOrder 被限流或熔断, userId={}, skuId={}", userId, skuId, ex);
throw new BizException("当前下单人数过多,请稍后重试");
}
// 业务异常兜底
public Order createOrderFallback(Long userId, Long skuId, Throwable t) {
log.error("createOrder 执行异常", t);
return Order.fail("下单失败,请稍后重试");
}
}
5.4 规则配置示例(Nacos 中的 JSON)
流控规则:
json
[
{
"resource": "createOrder",
"limitApp": "default",
"grade": 1,
"count": 200,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
字段说明:
grade:0 表示线程数,1 表示 QPS。strategy:0 直接拒绝,1 关联资源,2 链路。controlBehavior:0 快速失败,1 Warm Up 预热,2 匀速排队,3 冷启动+匀速。
熔断规则:
json
[
{
"resource": "createOrder",
"grade": 1,
"count": 0.5,
"timeWindow": 10,
"minRequestAmount": 20,
"statIntervalMs": 10000,
"slowRatioThreshold": 0.5
}
]
grade:0 慢调用比例,1 异常比例,2 异常数。count:慢调用 RT 阈值(ms)或异常比例阈值。timeWindow:熔断时长(秒)。minRequestAmount:最小请求数,低于此值不触发熔断。
5.5 热点参数限流
针对某个参数值单独限流,比如限制单个用户 ID 的下单频率:
java
@SentinelResource(value = "queryByUserId", blockHandler = "hotBlockHandler")
public List<Order> queryByUserId(@RequestParam Long userId) {
return orderMapper.selectByUserId(userId);
}
规则中配置 paramIdx: 0,即可对第一个参数做热点限流,还能为特定参数值设置例外阈值。
6. Resilience4j 实战:Spring Cloud 官方推荐方案
Resilience4j 是 Spring Cloud 官方在 Netflix 组件退役后主推的容错库,模块化设计,无额外依赖。
6.1 依赖与配置
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
yaml
resilience4j:
circuitbreaker:
instances:
inventoryService:
sliding-window-type: COUNT_BASED
sliding-window-size: 20
minimum-number-of-calls: 10
failure-rate-threshold: 50
slow-call-rate-threshold: 60
slow-call-duration-threshold: 2s
wait-duration-in-open-state: 10s
permitted-number-of-calls-in-half-open-state: 5
automatic-transition-from-open-to-half-open-enabled: true
ratelimiter:
instances:
orderApi:
limit-for-period: 100
limit-refresh-period: 1s
timeout-duration: 0
bulkhead:
instances:
inventoryService:
max-concurrent-calls: 20
max-wait-duration: 10ms
timelimiter:
instances:
inventoryService:
timeout-duration: 2s
6.2 编程式使用
java
@Service
public class InventoryService {
private final CircuitBreaker circuitBreaker;
private final InventoryClient inventoryClient;
public InventoryService(CircuitBreakerRegistry registry, InventoryClient client) {
this.circuitBreaker = registry.circuitBreaker("inventoryService");
this.inventoryClient = client;
}
public Integer getStock(Long skuId) {
Supplier<Integer> supplier = () -> inventoryClient.getStock(skuId);
Supplier<Integer> decorated = Decorators.ofSupplier(supplier)
.withCircuitBreaker(circuitBreaker)
.withFallback(Arrays.asList(CallNotPermittedException.class),
ex -> -1) // 熔断时返回 -1 表示库存未知
.decorate();
return decorated.get();
}
}
6.3 声明式使用(Spring Cloud OpenFeign 集成)
java
@FeignClient(name = "inventory-service", fallbackFactory = InventoryFallbackFactory.class)
public interface InventoryClient {
@GetMapping("/stock/{skuId}")
Integer getStock(@PathVariable("skuId") Long skuId);
}
@Component
public class InventoryFallbackFactory implements FallbackFactory<InventoryClient> {
@Override
public InventoryClient create(Throwable cause) {
return skuId -> {
log.warn("库存服务降级, skuId={}", skuId, cause);
return -1;
};
}
}
yaml
feign:
circuitbreaker:
enabled: true
注意:Spring Cloud 2022 之后,Feign 的熔断开关从
feign.hystrix.enabled改为feign.circuitbreaker.enabled,升级时容易漏改。
7. 网关层限流:Spring Cloud Gateway + Redis
网关是流量的第一道关口,在这里做全局限流成本最低、覆盖面最广。
7.1 配置
yaml
spring:
cloud:
gateway:
routes:
- id: order-service
uri: lb://order-service
predicates:
- Path=/api/order/**
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 100 # 每秒补充令牌数
redis-rate-limiter.burstCapacity: 200 # 令牌桶容量
redis-rate-limiter.requestedTokens: 1 # 每个请求消耗令牌数
key-resolver: "#{@userKeyResolver}"
7.2 限流维度 KeyResolver
java
@Configuration
public class RateLimiterConfig {
// 按用户维度限流
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.justOrEmpty(
exchange.getRequest().getHeaders().getFirst("X-User-Id")
).defaultIfEmpty("anonymous");
}
// 按 IP 维度限流
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(
Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostString()
);
}
// 按接口路径维度限流
@Bean
public KeyResolver pathKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
7.3 自定义限流响应
默认返回 429,建议自定义为业务统一格式:
java
@Component
public class CustomRateLimitResponse implements BlockRequestHandler {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange exchange, Throwable t) {
return ServerResponse.status(HttpStatus.TOO_MANY_REQUESTS)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(Result.fail("RATE_LIMITED", "请求过于频繁,请稍后再试"));
}
}
8. 最佳实践清单
以下是从多个生产项目中沉淀下来的经验,建议逐条对照检查。
8.1 阈值设定
- 不要拍脑袋定阈值 :先压测拿到单机容量,再按
阈值 = 单机容量 × 0.7留出余量。 - 区分 QPS 和线程数:CPU 密集型接口用线程数限流更准,IO 密集型用 QPS。
- 熔断阈值宁松勿紧:错误率阈值建议 50% 起步,最小请求数不低于 10,避免少量失败就熔断。
- 慢调用阈值参考 P99:比如 P99 是 800ms,慢调用阈值可设 1000ms。
8.2 规则管理
- 规则必须持久化:Sentinel 控制台默认规则存在内存,重启即丢失,务必接入 Nacos/Apollo/Zookeeper。
- 规则变更要审计:谁在什么时候改了阈值,必须可追溯,否则线上出问题无法定位。
- 灰度生效:大流量服务先对部分实例生效,观察指标后再全量。
8.3 降级策略
- 降级返回值要有业务语义 :返回
null容易引发 NPE,建议返回明确的空对象或错误码。 - 降级日志要打全:记录资源名、触发原因、入参,便于事后分析。
- 核心链路降级要业务确认:比如库存查询降级返回 -1,前端必须能正确处理。
8.4 监控告警
- 必须监控断路器状态变化:Open 状态持续时间、触发次数是核心指标。
- 限流次数要告警:持续大量限流说明容量不足或遭到攻击。
- 接入链路追踪:Sentinel 支持与 Sleuth/Micrometer Tracing 集成,能在链路中看到熔断点。
8.5 组合使用顺序
一个请求的完整防护链路建议如下:
#mermaid-svg-xfQ7DmzAat15TJjS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xfQ7DmzAat15TJjS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xfQ7DmzAat15TJjS .error-icon{fill:#552222;}#mermaid-svg-xfQ7DmzAat15TJjS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xfQ7DmzAat15TJjS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xfQ7DmzAat15TJjS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xfQ7DmzAat15TJjS .marker.cross{stroke:#333333;}#mermaid-svg-xfQ7DmzAat15TJjS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xfQ7DmzAat15TJjS p{margin:0;}#mermaid-svg-xfQ7DmzAat15TJjS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-xfQ7DmzAat15TJjS .cluster-label text{fill:#333;}#mermaid-svg-xfQ7DmzAat15TJjS .cluster-label span{color:#333;}#mermaid-svg-xfQ7DmzAat15TJjS .cluster-label span p{background-color:transparent;}#mermaid-svg-xfQ7DmzAat15TJjS .label text,#mermaid-svg-xfQ7DmzAat15TJjS span{fill:#333;color:#333;}#mermaid-svg-xfQ7DmzAat15TJjS .node rect,#mermaid-svg-xfQ7DmzAat15TJjS .node circle,#mermaid-svg-xfQ7DmzAat15TJjS .node ellipse,#mermaid-svg-xfQ7DmzAat15TJjS .node polygon,#mermaid-svg-xfQ7DmzAat15TJjS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xfQ7DmzAat15TJjS .rough-node .label text,#mermaid-svg-xfQ7DmzAat15TJjS .node .label text,#mermaid-svg-xfQ7DmzAat15TJjS .image-shape .label,#mermaid-svg-xfQ7DmzAat15TJjS .icon-shape .label{text-anchor:middle;}#mermaid-svg-xfQ7DmzAat15TJjS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xfQ7DmzAat15TJjS .rough-node .label,#mermaid-svg-xfQ7DmzAat15TJjS .node .label,#mermaid-svg-xfQ7DmzAat15TJjS .image-shape .label,#mermaid-svg-xfQ7DmzAat15TJjS .icon-shape .label{text-align:center;}#mermaid-svg-xfQ7DmzAat15TJjS .node.clickable{cursor:pointer;}#mermaid-svg-xfQ7DmzAat15TJjS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xfQ7DmzAat15TJjS .arrowheadPath{fill:#333333;}#mermaid-svg-xfQ7DmzAat15TJjS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xfQ7DmzAat15TJjS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xfQ7DmzAat15TJjS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xfQ7DmzAat15TJjS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xfQ7DmzAat15TJjS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xfQ7DmzAat15TJjS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xfQ7DmzAat15TJjS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xfQ7DmzAat15TJjS .cluster text{fill:#333;}#mermaid-svg-xfQ7DmzAat15TJjS .cluster span{color:#333;}#mermaid-svg-xfQ7DmzAat15TJjS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xfQ7DmzAat15TJjS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xfQ7DmzAat15TJjS rect.text{fill:none;stroke-width:0;}#mermaid-svg-xfQ7DmzAat15TJjS .icon-shape,#mermaid-svg-xfQ7DmzAat15TJjS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xfQ7DmzAat15TJjS .icon-shape p,#mermaid-svg-xfQ7DmzAat15TJjS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xfQ7DmzAat15TJjS .icon-shape .label rect,#mermaid-svg-xfQ7DmzAat15TJjS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xfQ7DmzAat15TJjS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xfQ7DmzAat15TJjS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xfQ7DmzAat15TJjS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 网关限流
服务入口限流
线程池/信号量隔离
调用下游: 超时控制
熔断器判断
降级兜底
顺序原则:先限流后熔断,先隔离后超时,超时时间要小于上游超时时间(否则上游先超时,熔断统计失真)。
9. 常见问题与处理方案
9.1 熔断后一直不恢复
现象:断路器打开后再也不关闭。
原因:
- 半开状态放行的探测请求仍然失败,说明下游确实没恢复;
wait-duration-in-open-state配置过长;- 熔断统计窗口内失败请求占比一直很高。
处理 :检查下游健康状态;适当缩短 Open 状态时长;确认半开探测请求数量足够(permitted-number-of-calls-in-half-open-state 建议 ≥ 3)。
9.2 限流误杀正常流量
现象:业务低峰期也出现大量限流。
原因:
- 阈值按集群总量配置,但实际是单机生效,导致总容量被低估;
- 热点参数限流未配置例外值;
- 网关限流 Key 选取不当,比如按 IP 限流时大量用户共用 NAT 出口 IP。
处理:集群限流需部署 Token Server;热点参数配置例外项;网关限流优先按用户 ID 而非 IP。
9.3 Sentinel 规则重启丢失
现象:服务重启后控制台配置的规则全部消失。
原因 :默认使用内存 InMemoryDataSource。
处理 :接入 Nacos 等持久化数据源,并在控制台改造推送逻辑(开源版控制台默认只推送到内存,需要自行改造或使用 sentinel-dashboard 的 Nacos 推送分支)。
9.4 Feign 熔断不生效
现象:配置了 fallback 但异常时没有走降级。
原因:
- 未开启
feign.circuitbreaker.enabled=true; - fallback 类没有被 Spring 扫描到;
- 异常类型不在熔断统计范围内(如业务异常被
ignoreExceptions排除)。
处理 :逐项检查配置;fallback 类加 @Component;确认 ignoreExceptions 配置。
9.5 线程池隔离导致线程数暴涨
现象:使用 Hystrix 线程池隔离后,服务线程数达到数千。
原因:每个依赖一个线程池,依赖多时线程总数 = 依赖数 × 池大小。
处理:迁移到信号量隔离(Sentinel 默认)或 Resilience4j 的 Bulkhead;合理设置池大小,一般 10~20 即可。
9.6 熔断与重试叠加放大流量
现象:下游故障时,上游重试导致流量翻倍,反而加剧下游崩溃。
原因:熔断 + 重试同时开启,且重试次数较多。
处理:重试只对幂等接口开启;重试次数不超过 2 次;熔断打开时禁止重试;使用指数退避。
9.7 超时时间设置不合理
现象:熔断统计的慢调用比例异常偏高或偏低。
原因:Feign/Ribbon 超时时间大于熔断慢调用阈值,导致慢调用统计失真。
处理 :保证 连接超时 < 读超时 < 熔断慢调用阈值 < 上游超时,形成合理的超时梯度。
10. 总结
熔断限流不是加个注解就完事,它是一套需要结合业务容量、调用链路、降级语义来设计的治理体系。回顾本文要点:
- 先分清概念:限流保护自己,熔断保护调用方,降级是兜底动作,隔离防止扩散。
- 选型看版本:Spring Cloud 2020+ 用 Resilience4j 或 Sentinel,Hystrix 已是历史。
- 场景要精准:秒杀、开放 API、第三方依赖是重点,内部低频接口不必强上。
- 阈值靠压测:不要拍脑袋,留 30% 余量,熔断阈值宁松勿紧。
- 规则要持久化:内存规则重启即丢,必须接配置中心。
- 监控是闭环:没有监控的熔断限流等于没做,状态变化和限流次数都要告警。
- 组合有顺序:限流 → 隔离 → 超时 → 熔断 → 降级,超时梯度要合理。
最后一句经验:熔断限流的价值不在于拦住多少请求,而在于让系统在极端情况下仍然可用、可观测、可恢复。