使用 Spring Cloud Gateway 实现四种限流方案:固定窗口、滑动窗口、令牌桶与漏桶

3.2 滑动窗口限流

实现思路

  • 使用 Redis 的有序集合(ZSET)存储请求时间戳。
  • 移除窗口外(例如 1 秒前)的请求记录,统计窗口内的请求数。
  • 如果请求数超过阈值,拒绝请求。

代码示例

java 复制代码
@Component
public class SlidingWindowRateLimiter implements GatewayFilterFactory<SlidingWindowRateLimiter.Config> {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String key = "sliding_window:" + exchange.getRequest().getURI().getPath();
            long currentTime = System.currentTimeMillis();
            long windowStart = currentTime - 1000; // 1 秒窗口

            // 移除窗口外的记录
            redisTemplate.opsForZSet().removeRangeByScore(key, 0, windowStart);
            // 添加当前请求时间戳
            redisTemplate.opsForZSet().add(key, currentTime + "", currentTime);
            // 统计窗口内请求数
            Long count = redisTemplate.opsForZSet().count(key, windowStart, currentTime);

            if (count > config.getLimit()) {
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                return response.setComplete();
            }
            return chain.filter(exchange);
        };
    }

    public static class Config {
        private int limit; // 每秒最大请求数
        // getter 和 setter
    }
}

配置文件

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: sliding_window_route
        uri: http://localhost:8081
        predicates:
        - Path=/api/**
        filters:
        - name: SlidingWindowRateLimiter
          args:
            limit: 10

说明

  • 使用 ZSET 存储时间戳,动态维护 1 秒内的请求。
  • 通过移除窗口外的记录,精确统计滑动窗口内的请求数。
  • 相比固定窗口,滑动窗口平滑了边界流量突刺,但 Redis 操作更复杂。

3.3 令牌桶限流

实现思路

  • Spring Cloud Gateway 内置的 RequestRateLimiter 基于令牌桶算法,依赖 Redis。
  • 配置 redis-rate-limiter.replenishRate(令牌补充速率)和 redis-rate-limiter.burstCapacity(桶容量)。

代码示例: Spring Cloud Gateway 的默认令牌桶实现无需额外编码,只需配置即可:

配置文件

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379
  cloud:
    gateway:
      routes:
      - id: token_bucket_route
        uri: http://localhost:8081
        predicates:
        - Path=/api/**
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 10 # 每秒补充 10 个令牌
            redis-rate-limiter.burstCapacity: 20 # 桶容量 20
            key-resolver: "#{@pathKeyResolver}" # 按路径限流

KeyResolver 配置

java 复制代码
@Configuration
public class RateLimiterConfig {

    @Bean
    public KeyResolver pathKeyResolver() {
        return exchange -> Mono.just(exchange.getRequest().getURI().getPath());
    }
}

说明

  • replenishRate 控制令牌补充速度,burstCapacity 允许短时间内的突发流量。
  • 默认实现简单高效,适合大多数场景。
  • 如果需要自定义令牌桶逻辑,可以参考固定窗口的 Redis 实现,维护令牌计数。

3.4 漏桶限流

实现思路

  • 使用 Redis 队列(LIST)模拟漏桶,请求进入队列。
  • 以固定速率从队列中移除请求(模拟漏出)。
  • 如果队列长度超过桶容量,拒绝新请求。

代码示例

java 复制代码
@Component
public class LeakyBucketRateLimiter implements GatewayFilterFactory<LeakyBucketRateLimiter.Config> {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            String key = "leaky_bucket:" + exchange.getRequest().getURI().getPath();
            // 添加请求到队列
            redisTemplate.opsForList().rightPush(key, System.currentTimeMillis() + "");
            Long size = redisTemplate.opsForList().size(key);

            if (size > config.getCapacity()) {
                // 队列超限,拒绝请求
                redisTemplate.opsForList().rightPop(key); // 移除刚添加的请求
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
                return response.setComplete();
            }

            // 模拟漏桶定时漏出(实际生产中应使用定时任务)
            redisTemplate.opsForList().leftPop(key); // 假设固定速率漏出
            return chain.filter(exchange);
        };
    }

    public static class Config {
        private int capacity; // 桶容量
        // getter 和 setter
    }
}

配置文件

yaml 复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: leaky_bucket_route
        uri: http://localhost:8081
        predicates:
        - Path=/api/**
        filters:
        - name: LeakyBucketRateLimiter
          args:
            capacity: 10 # 桶容量 10

说明

  • 使用 Redis LIST 模拟队列,请求入队,超出容量拒绝。
  • 漏出速率需要额外定时任务控制(示例中简化处理)。
  • 漏桶严格控制流量速率,适合需要平滑输出的场景,但实现复杂。

4. 四种方案对比

方案 优点 缺点 适用场景
固定窗口 实现简单,性能高 边界突刺问题 对突刺不敏感的场景
滑动窗口 平滑流量,无边界突刺 实现复杂,Redis 开销较高 需要精确限流的场景
令牌桶 支持突发流量,内置支持 配置复杂,突发流量可能影响下游 通用场景,允许一定突发流量
漏桶 严格平滑流量 实现复杂,延迟较高 对流量速率要求严格的场景

5. 注意事项

  1. Redis 性能:限流依赖 Redis,需确保 Redis 性能和稳定性。
  2. 分布式环境:Redis 提供了分布式计数支持,但需注意网络延迟。
  3. 动态配置:生产环境中,建议将限流参数(如阈值、窗口大小)存储在配置中心,支持动态调整。
  4. 降级策略:限流触发时,应返回友好的错误信息(如 HTTP 429),并考虑降级逻辑。

6. 总结

Spring Cloud Gateway 提供了强大的扩展能力,支持实现多种限流算法:

  • 固定窗口适合简单场景,易于实现。
  • 滑动窗口适合需要平滑流量的场景,精度高。
  • 令牌桶内置支持,适合大多数场景。
  • 漏桶适合严格控制速率的场景,但实现复杂。

通过本文的代码示例,开发者可以根据实际需求选择合适的限流方案,并结合 Redis 实现高效的流量控制。希望这篇文章能为你的微服务限流设计提供帮助!

相关推荐
余衫马1 小时前
Windows 10 环境下 Redis 编译与运行指南
redis·后端
青柠编程3 小时前
基于Spring Boot的竞赛管理系统架构设计
java·spring boot·后端
s9123601015 小时前
【rust】 pub(crate) 的用法
开发语言·后端·rust
夕颜1116 小时前
关于排查问题的总结
后端
码事漫谈7 小时前
揭秘RAG的核心引擎:Document、Embedding与Retriever详解
后端
码事漫谈7 小时前
BM25 检索是什么
后端
Moment7 小时前
写代码也能享受?这款显示器让调试变得轻松又高效!😎😎😎
前端·后端
无双_Joney8 小时前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(bug修复篇)
前端·后端·node.js
stark张宇9 小时前
从入门到放弃?一份让PHP学习持续正反馈的知识清单
后端·php
sunbin9 小时前
软件授权管理系统-整体业务流程图(优化版)
后端