基于Spring Cloud Gateway与Redis实现请求限流的疑问及场景配置咨询
一、RedisRateLimiter 是什么?
RedisRateLimiter 是 Spring Cloud Gateway 提供的基于令牌桶算法的分布式限流组件,依赖 Redis 存储限流状态,通过 Lua 脚本保证限流逻辑的原子性,避免分布式环境下的并发问题,适合多实例网关的统一限流场景。
二、核心工作原理
它实现了令牌桶算法的核心逻辑:
- 令牌生成:以固定速率(
replenishRate)向令牌桶中添加令牌,比如每分钟生成5个令牌,相当于每秒生成约0.083个。 - 令牌消耗:每个请求过来时,尝试从桶中获取指定数量的令牌(默认1个):
- 成功获取到令牌:请求放行,桶中令牌数减少对应数量。
- 无法获取令牌:请求被限流,返回429 Too Many Requests。
- 令牌桶容量:桶的最大容量由
burstCapacity定义,当桶满时,新生成的令牌会被丢弃,避免令牌无限累积。
三、核心参数解析
RedisRateLimiter 的核心参数对应令牌桶的关键配置:
replenishRate:每分钟生成的令牌数,决定了长期允许的平均请求速率。burstCapacity:令牌桶的最大容量,决定了允许的突发请求峰值(比如短时间内允许超过平均速率的请求数)。requestedTokens:每个请求消耗的令牌数,默认值为1,可根据业务调整(比如一个复杂请求消耗2个令牌)。
四、对应限流规则的配置示例
针对你需要的3种限流规则,结合场景解释配置逻辑:
1. 规则1:每分钟最多5次请求
配置参数:replenishRate=5,burstCapacity=5
- 逻辑说明:
- 令牌桶每分钟生成5个令牌,初始时桶内装满5个令牌。
- 如果连续发送5个请求,桶内令牌耗尽,后续每个请求需要等待约12秒(60/5)才能获取到新生成的令牌,确保每分钟总请求数不超过5。
- 不允许突发请求(因为桶容量等于生成速率),严格控制在每分钟5次以内。
2. 规则2:每分钟最多7次请求
配置参数:replenishRate=7,burstCapacity=7
- 逻辑说明:
- 令牌桶每分钟生成7个令牌,初始桶内有7个令牌。
- 连续发送7个请求后,桶空,后续请求需等待约8.57秒(60/7)才能拿到令牌,保证每分钟总请求数不超过7。
- 同样严格限制速率,无突发空间。
3. 规则3:每分钟最多20次请求
配置参数:replenishRate=20,burstCapacity=20
- 逻辑说明:
- 令牌桶每分钟生成20个令牌,初始桶内有20个令牌。
- 连续发送20个请求后,桶空,后续请求需等待3秒(60/20)即可获取令牌,确保每分钟总请求数不超过20。
如果需要允许一定的突发请求(比如某一秒内允许发送20个请求,之后每分钟只能保持20次的平均速率),可以将burstCapacity设为更大的值,比如burstCapacity=40,但replenishRate仍为20,这样短时间内可以处理40个请求,但后续每分钟只能处理20个,直到令牌桶重新填满。
五、你的现有代码优化建议
1. 修正 RedisRateLimiter 构造参数
你当前的代码中第三个参数传了60,这是错误的。在Spring Boot 2.5.12对应的版本中,RedisRateLimiter的第三个参数是defaultRequestedTokens(每个请求消耗的令牌数),默认值为1,不需要手动设置为60。正确的全局默认配置应该是:
@Bean public RedisRateLimiter redisRateLimiter() { // 这里可以设一个全局默认值,比如每分钟10次 return new RedisRateLimiter(10, 10); }
2. 实现不同路由的差异化限流
如果需要给不同接口配置不同的限流规则,无需定义多个RedisRateLimiter Bean,只需在路由配置中通过metadata指定参数即可:
@Bean public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { return builder.routes() // 接口1:每分钟5次 .route("api1", r -> r.path("/api/v1/**") .filters(f -> f.requestRateLimiter(c -> c .rateLimiter(redisRateLimiter()) .keyResolver(ipKeyResolver()) // 通过metadata指定限流参数 .configureRateLimiter(rl -> rl.setReplenishRate(5).setBurstCapacity(5)))) .uri("http://localhost:8081")) // 接口2:每分钟7次 .route("api2", r -> r.path("/api/v2/**") .filters(f -> f.requestRateLimiter(c -> c .rateLimiter(redisRateLimiter()) .keyResolver(ipKeyResolver()) .configureRateLimiter(rl -> rl.setReplenishRate(7).setBurstCapacity(7)))) .uri("http://localhost:8082")) // 接口3:每分钟20次 .route("api3", r -> r.path("/api/v3/**") .filters(f -> f.requestRateLimiter(c -> c .rateLimiter(redisRateLimiter()) .keyResolver(ipKeyResolver()) .configureRateLimiter(rl -> rl.setReplenishRate(20).setBurstCapacity(20)))) .uri("http://localhost:8083")) .build(); }
3. KeyResolver 代码优化
你的IP限流KeyResolver可以简化,无需嵌套Optional操作:
@Component public class IpKeyResolver implements KeyResolver { @Override public Mono<String> resolve(ServerWebExchange exchange) { return Mono.justOrEmpty(exchange.getRequest().getRemoteAddress()) .map(InetSocketAddress::getHostString); } }
内容的提问来源于stack exchange,提问作者Alpesh Jikadra

