Spring Cloud 微服务熔断限流实战

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. 总结

熔断限流不是加个注解就完事,它是一套需要结合业务容量、调用链路、降级语义来设计的治理体系。回顾本文要点:

  1. 先分清概念:限流保护自己,熔断保护调用方,降级是兜底动作,隔离防止扩散。
  2. 选型看版本:Spring Cloud 2020+ 用 Resilience4j 或 Sentinel,Hystrix 已是历史。
  3. 场景要精准:秒杀、开放 API、第三方依赖是重点,内部低频接口不必强上。
  4. 阈值靠压测:不要拍脑袋,留 30% 余量,熔断阈值宁松勿紧。
  5. 规则要持久化:内存规则重启即丢,必须接配置中心。
  6. 监控是闭环:没有监控的熔断限流等于没做,状态变化和限流次数都要告警。
  7. 组合有顺序:限流 → 隔离 → 超时 → 熔断 → 降级,超时梯度要合理。

最后一句经验:熔断限流的价值不在于拦住多少请求,而在于让系统在极端情况下仍然可用、可观测、可恢复。

相关推荐
猫猫不是喵喵.2 天前
从0到1创建一个微服务项目
java·数据库·微服务·1024程序员节
htzyl2062 天前
别急着上微前端:从 6 个场景倒推 Module Federation 2.0 与 qiankun 2.x 的选型
前端·微服务
知守观2 天前
一个方法两个触发口:反编译 XXL-Job 后,我看清了 inner/job 子包的裸奔现状
java·spring boot·微服务
草莓熊Lotso3 天前
【C++ 基于微服务的即时通讯系统】文件存储子服务实战:UUID生成、文件IO封装与全接口单元测试
开发语言·网络·数据库·c++·微服务·架构·单元测试
wdyan2978 天前
带着小白学习-微服务-5 实战:配置中心-数据库连接
数据库·学习·微服务
胡写代码9 天前
把数据库连接搬进配置中心,服务起不来:spring.config.import 的 4 个必填项
spring cloud·微服务
利威尔xu12 天前
Go IM 源码实战(14):本地缓存快 3000 倍,失效却难 10 倍
后端·缓存·微服务·面试·golang
利威尔xu12 天前
Go IM 源码实战(13):30 个会话只有 2 个红点,未读数错在三条路径
redis·后端·微服务·面试·golang
骑着蜗牛撵大象32712 天前
Spring Boot 4.1 + Spring AI 2.0:RAG 供文、图谱供关系、MCP 供工具的 Java Agent 平台
java·spring boot·spring·微服务·知识图谱·rag·mcp