Skip to content
第 69 / 250 章后端⏱ 10 分钟阅读

第 69 章:限流与熔断

学习目标

  • 理解限流、熔断、降级的区别与价值
  • 掌握 Sentinel 核心规则与 Spring Boot 集成
  • 学会热点参数限流、熔断降级的实战应用

一、为什么需要限流熔断?

三大场景

  • 限流(Rate Limiting):拒绝过多请求,保护系统不被压垮
  • 熔断(Circuit Breaker):下游故障时快速失败,避免雪崩
  • 降级(Degradation):系统压力大时,放弃非核心功能保核心

二、限流算法

1. 计数器(固定窗口)

java
// 简单粗暴:1 秒内超过 1000 个请求就拒绝
AtomicLong counter = new AtomicLong(0);

public boolean tryAcquire() {
    long currentSecond = System.currentTimeMillis() / 1000;
    if (currentSecond != lastSecond.get()) {
        counter.set(0);
        lastSecond.set(currentSecond);
    }
    return counter.incrementAndGet() <= 1000;
}

问题:临界突刺。0.9 秒时来了 1000 个,1.0 秒又来了 1000 个,2 秒内就放过 2000 个。

2. 滑动窗口

java
// 维护最近 N 个时间窗的计数
Deque<Long> timestamps = new ArrayDeque<>();

public boolean tryAcquire() {
    long now = System.currentTimeMillis();
    long windowStart = now - 1000;        // 1 秒窗口

    // 移除过期时间戳
    while (!timestamps.isEmpty() && timestamps.peekFirst() < windowStart) {
        timestamps.pollFirst();
    }

    if (timestamps.size() < 1000) {
        timestamps.add(now);
        return true;
    }
    return false;
}

3. 令牌桶(推荐)

java
// 桶里放 N 个令牌,每秒生成 R 个
// 拿令牌 → 没令牌就拒绝或等待
public class TokenBucket {
    private final int capacity;            // 桶容量
    private final double rate;             // 令牌生成速率(个/秒)
    private double tokens;                 // 当前令牌数
    private long lastRefillTime;           // 上次填充时间

    public synchronized boolean tryAcquire() {
        refill();
        if (tokens >= 1) {
            tokens -= 1;
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastRefillTime;
        tokens = Math.min(capacity, tokens + (elapsed / 1000.0) * rate);
        lastRefillTime = now;
    }
}

4. 漏桶

java
// 请求进入漏桶,桶以固定速率漏水(处理)
// 桶满则拒绝(背压)
public class LeakyBucket {
    private final int capacity;
    private final double rate;            // 处理速率
    private double water;                 // 当前水量

    public synchronized boolean tryAcquire() {
        if (water >= capacity) return false;
        water += 1;
        return true;
    }

    public void process() {
        water = Math.max(0, water - rate * 0.001);
    }
}

算法对比

算法特点适用
计数器简单,突刺问题粗粒度限流
滑动窗口精确,平滑API 网关
令牌桶允许突发,平滑最常用(Nginx、Guava)
漏桶强制恒定速率流量整形

三、Sentinel 集成

xml
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-sentinel-datasource-nacos</artifactId>
</dependency>
yaml
spring:
  application:
    name: taskflow
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080          # Sentinel 控制台
        port: 8719
      datasource:
        # ① 规则持久化(从 Nacos 拉取)
        flow:
          nacos:
            server-addr: localhost:8848
            data-id: taskflow-flow-rules
            rule-type: flow
        degrade:
          nacos:
            server-addr: localhost:8848
            data-id: taskflow-degrade-rules
            rule-type: degrade
      web-context-unify: false              # 关闭 context 合并
      filter:
        url-patterns: /*

四、流量控制规则

java
@Service
public class OrderService {

    @SentinelResource(
        value = "createOrder",                       // ① 资源名
        blockHandler = "createOrderBlockHandler",    // ② 限流/降级时调用
        fallback = "createOrderFallback"             // ③ 业务异常时调用
    )
    public Long createOrder(OrderDTO dto) {
        // 业务逻辑
        return orderMapper.insert(order);
    }

    // 限流处理
    public Long createOrderBlockHandler(OrderDTO dto, BlockException ex) {
        log.warn("触发限流: {}", ex.getMessage());
        throw new BusinessException(ErrorCode.RATE_LIMIT, "系统繁忙,请稍后再试");
    }

    // 降级处理
    public Long createOrderFallback(OrderDTO dto, Throwable ex) {
        log.error("业务异常", ex);
        return -1L;
    }
}

五种流控模式

yaml
# application.yml(也可以通过控制台动态配置)
sentinel:
  rules:
    flow:
      - resource: createOrder
        grade: qps                              # ① 按 QPS 限流
        count: 100                              # 阈值:100 QPS
        controlBehavior: reject                 # 直接拒绝
        limitApp: default

      - resource: createOrder
        grade: qps
        count: 50
        controlBehavior: warm_up                # ② 预热(冷启动)
        warmUpPeriodSec: 10                     # 10 秒预热到 50 QPS

      - resource: createOrder
        grade: qps
        count: 100
        controlBehavior: rate_limiter           # ③ 排队等待
        maxQueueingTimeMs: 5000                 # 最长等 5 秒

      - resource: getProduct
        grade: thread                           # ④ 按并发线程数限流
        count: 20

      # 关联限流:下单触发支付限流
      - resource: payOrder
        grade: qps
        count: 200
        refResource: createOrder                # ⑤ createOrder 触发时 payOrder 限流

热点参数限流

yaml
# 针对特定参数限流:同一用户 ID 1 秒内最多 10 次请求
- resource: getUser
  grade: qps
  count: 100
  paramFlowItem:
    - object: String                          # 参数类型
      paramIdx: 0                             # 参数位置
      count: 10                               # 该参数值阈值
      durationInSec: 1                        # 时间窗口
java
// Controller
@GetMapping("/user/{id}")
@SentinelResource("getUser")
public Result<UserVO> getUser(@PathVariable Long id) { ... }

五、熔断降级规则

yaml
# 三种熔断策略
sentinel:
  rules:
    degrade:
      # ① 慢调用比例:超过 1 秒的调用比例 > 50%,熔断 10 秒
      - resource: callPayment
        grade: rt                              # 慢调用比例
        count: 1000                            # 慢调用阈值(ms)
        slowRatioThreshold: 0.5                # 慢调用比例
        timeWindow: 10                         # 熔断时长(秒)
        minRequestAmount: 10                   # 最小请求数(避免抖动)
        statIntervalMs: 1000                   # 统计窗口

      # ② 异常比例:异常率 > 50% 熔断
      - resource: callInventory
        grade: exception_ratio
        count: 0.5                             # 异常比例阈值
        timeWindow: 10
        minRequestAmount: 10

      # ③ 异常数:1 分钟内异常数 > 10 熔断
      - resource: callLogistics
        grade: exception_count
        count: 10
        timeWindow: 60
        statIntervalMs: 60000

熔断状态机

六、网关层限流

yaml
spring:
  cloud:
    gateway:
      routes:
        - id: user-route
          uri: lb://user-service
          predicates:
            - Path=/api/user/**
          filters:
            - name: RequestRateLimiter
              args:
                redis-rate-limiter.replenishRate: 100    # 每秒允许 100 个请求
                redis-rate-limiter.burstCapacity: 200   # 桶容量 200
                redis-rate-limiter.requestedTokens: 1   # 每个请求消耗 1 个令牌
                key-resolver: "#{@userKeyResolver}"     # Key 解析器
java
@Bean
public KeyResolver userKeyResolver() {
    return exchange -> Mono.just(
        Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("X-User-Id"))
            .orElse(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress())
    );
}

七、自定义限流处理

java
@Component
public class SentinelBlockHandler {

    // 全局兜底
    public static Result<?> defaultBlockHandler(Object o, BlockException ex) {
        log.warn("限流触发 resource={} rule={}",
                ex.getResource(), ex.getRule());
        return Result.fail(429, "系统繁忙,请稍后再试");
    }

    // 指定资源
    public static Result<?> createOrderBlock(OrderDTO dto, BlockException ex) {
        // 排队等待场景:返回客户端"请重试"
        return Result.fail(429, "下单人数过多,请稍后再试");
    }
}
java
@SentinelResource(
    value = "createOrder",
    blockHandlerClass = SentinelBlockHandler.class,
    blockHandler = "createOrderBlock",
    fallbackClass = SentinelBlockHandler.class,
    fallback = "defaultBlockHandler"
)
public Long createOrder(OrderDTO dto) { ... }

八、限流维度对比

维度网关层服务层方法层
位置Nginx / Gateway微服务入口Controller / Service
粒度URL、IP、用户服务、方法参数值
适用防刷、防爬虫防雪崩热点保护
工具Nginx limit_req / GatewaySentinelSentinel

九、生产级限流配置示例

yaml
# 网关层:粗粒度,全局保护
spring.cloud.gateway.routes.*.filters.RequestRateLimiter:
  replenishRate: 1000          # 全局 1000 QPS
  burstCapacity: 2000

# 服务层:服务间调用的保护
@SentinelResource("callPayment")
- grade: qps
  count: 200                   # 支付服务最大 200 QPS

# 方法层:单个接口保护
@SentinelResource("seckill")
- grade: qps
  count: 50                    # 秒杀接口 50 QPS

# 热点参数:单个用户防刷
- resource: queryOrder
  paramFlowItem:
    - paramIdx: 0
      count: 5                  # 同一用户 5 QPS

十、Resilience4j 备选方案

xml
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>
yaml
resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 10
        failureRateThreshold: 50
        waitDurationInOpenState: 30s
        permittedNumberOfCallsInHalfOpenState: 5
  ratelimiter:
    instances:
      createOrder:
        limitForPeriod: 100
        limitRefreshPeriod: 1s
        timeoutDuration: 0
  retry:
    instances:
      paymentService:
        maxAttempts: 3
        waitDuration: 1s
java
@Service
public class PaymentService {

    @CircuitBreaker(name = "paymentService", fallbackMethod = "fallback")
    @Retry(name = "paymentService")
    @RateLimiter(name = "createOrder")
    public void pay(OrderDTO dto) {
        // 调用支付服务
    }

    public void fallback(OrderDTO dto, Throwable t) {
        log.warn("支付服务降级", t);
        // 排队、降级、补偿
    }
}

十一、本章小结

要点关键
算法计数器 / 滑动窗口 / 令牌桶(推荐)/ 漏桶
限流拒掉过多请求(QPS / 线程数 / 热点参数)
熔断下游故障时快速失败,避免雪崩
降级放弃非核心功能保核心
工具Sentinel(阿里)/ Resilience4j(Netflix)
维度网关层 / 服务层 / 方法层(多维度组合)
状态机关闭 → 打开 → 半开 → 关闭

动手练习

练习 1:基础题

集成 Sentinel,给订单创建接口加 QPS=10 的限流,用 JMeter 压测验证 11 QPS 时被限流。

练习 2:进阶题

实现一个用户维度的热点限流:同一用户 1 秒内最多 5 次请求,超过则拒绝。模拟某个用户用脚本刷接口,验证被拦截。

练习 3:思考题

你的秒杀系统有 3 层:网关、服务、方法。如何分层限流?分别用什么阈值?


下一章第 70 章:定时任务与异步任务

本站基于 VitePress 构建 · 由 Codebook 团队维护