diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java index e84d2e74..6ae0c467 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiter.java @@ -48,6 +48,7 @@ import org.springframework.validation.annotation.Validated; * @author Spencer Gibb * @author Ronny Bräunlich * @author Denis Cutic + * @author Andrey Muchnik */ @ConfigurationProperties("spring.cloud.gateway.redis-rate-limiter") public class RedisRateLimiter extends AbstractRateLimiter implements ApplicationContextAware { @@ -145,16 +146,16 @@ public class RedisRateLimiter extends AbstractRateLimiter getKeys(String id) { + static List getKeys(String id, String routeId) { // use `{}` around keys to use Redis Key hash tags // this allows for using redis cluster - // Make a unique key per user. - String prefix = "request_rate_limiter.{" + id; + // Make a unique key per user and route. + String prefix = "request_rate_limiter.{" + routeId + "." + id + "}."; // You need two Redis keys for Token Bucket. - String tokenKey = prefix + "}.tokens"; - String timestampKey = prefix + "}.timestamp"; + String tokenKey = prefix + "tokens"; + String timestampKey = prefix + "timestamp"; return Arrays.asList(tokenKey, timestampKey); } @@ -245,7 +246,7 @@ public class RedisRateLimiter extends AbstractRateLimiter keys = getKeys(id); + List keys = getKeys(id, routeId); // The arguments to the LUA script. time() returns unixtime in seconds. List scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", "", requestedTokens + ""); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java index b052d724..ced89ab0 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/ratelimit/RedisRateLimiterTests.java @@ -51,6 +51,7 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen * @author Spencer Gibb * @author Ronny Bräunlich * @author Denis Cutic + * @author Andrey Muchnik */ @SpringBootTest(webEnvironment = RANDOM_PORT) @DirtiesContext @@ -98,6 +99,30 @@ public class RedisRateLimiterTests extends BaseWebClientTests { checkLimitEnforced(id, replenishRate, burstCapacity, requestedTokens, routeId); } + + @Test + public void redisRateLimiterWorksForMultipleRoutes() throws Exception { + String id = UUID.randomUUID().toString(); + + int replenishRate = 10; + int burstCapacity = 2 * replenishRate; + int requestedTokens = 1; + + String firstRouteId = "myroute"; + String secondRouteId = "myroute2"; + var config = rateLimiter.getConfig(); + config.put(firstRouteId, new RedisRateLimiter.Config().setBurstCapacity(burstCapacity) + .setReplenishRate(replenishRate) + .setRequestedTokens(requestedTokens)); + + config.put(secondRouteId, new RedisRateLimiter.Config().setBurstCapacity(burstCapacity) + .setReplenishRate(replenishRate) + .setRequestedTokens(requestedTokens)); + + checkLimitEnforced(id, replenishRate, burstCapacity, requestedTokens, firstRouteId); + checkLimitEnforced(id, replenishRate, burstCapacity, requestedTokens, secondRouteId); + } + @Test public void redisRateLimiterWorksForLowRates() throws Exception { String id = UUID.randomUUID().toString(); @@ -137,8 +162,8 @@ public class RedisRateLimiterTests extends BaseWebClientTests { @Test public void keysUseRedisKeyHashTags() { - assertThat(RedisRateLimiter.getKeys("1")).containsExactly("request_rate_limiter.{1}.tokens", - "request_rate_limiter.{1}.timestamp"); + assertThat(RedisRateLimiter.getKeys("1", "routeId")).containsExactly("request_rate_limiter.{routeId.1}.tokens", + "request_rate_limiter.{routeId.1}.timestamp"); } @Test