使用 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 实现高效的流量控制。希望这篇文章能为你的微服务限流设计提供帮助!

相关推荐
uhakadotcom26 分钟前
Python中orjson、json、json5三大JSON库简单对比与实用教程
后端·面试·github
南雨北斗1 小时前
Docker部署的优缺点
后端
嶂蘅1 小时前
OK!用大白话说清楚设计模式(二)
前端·后端·面试
AronTing1 小时前
03-Spring Cloud Gateway 深度解析:从核心原理到生产级网关实践
后端·面试·架构
liyanchao20181 小时前
java远程debug调试
后端
异常君2 小时前
Java 锁进化论:synchronized 的底层原理与锁优化技术详解
java·后端
谢尔登2 小时前
【Sequelize】迁移和种子
后端·express
快乐源泉2 小时前
【设计模式】适配器,已有功能扩展?你猜对了
后端·设计模式·go
玛奇玛丶2 小时前
坑爹的urlencode
后端
贝恩聊架构2 小时前
大模型应用开发Spring AI实战-Stdio方式实现MCP服务调用
人工智能·后端