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

相关推荐
绝无仅有几秒前
常用 Kubernetes (K8s) 命令指南
后端·面试·github
bobz96512 分钟前
ovs 桥接了 bond0.1234, 链路层功能还在,但 IP 层功能无法使用
后端
似水流年流不尽思念21 分钟前
Spring Bean有哪些生命周期回调方法?有哪几种实现方式?
后端·spring·面试
Moonbit28 分钟前
提交即有奖!MGPIC 游戏赛道官方推荐框架上线,直播同步解读赛题。 MoonBit MoonBit
后端·微信·程序员
郝同学的测开笔记29 分钟前
打通回家之路:OpenVPN,你的企业网络万能钥匙(一)
运维·后端·测试
whitepure29 分钟前
万字详解Java代码块
java·后端
SimonKing1 小时前
Spring Boot Admin:一站式监控微服务,这个运维神器真香!
java·后端·程序员
uhakadotcom1 小时前
如何安装和使用开源的Meilisearch
后端·面试·github
高松燈1 小时前
自动拆箱 导致的空指针问题复盘
后端
IT_陈寒2 小时前
Java性能优化实战:5个立竿见影的技巧让你的应用提速50%
前端·人工智能·后端