Fix for redis rate limiter not working with multiple routes. Fixes gh-2288 (#2517)

This commit is contained in:
Muchnik Andrey
2024-09-27 22:51:33 +03:00
committed by GitHub
parent bf493a3411
commit 9b8208146f
2 changed files with 34 additions and 8 deletions

View File

@@ -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<RedisRateLimiter.Config> implements ApplicationContextAware {
@@ -145,16 +146,16 @@ public class RedisRateLimiter extends AbstractRateLimiter<RedisRateLimiter.Confi
this.defaultConfig.setRequestedTokens(defaultRequestedTokens);
}
static List<String> getKeys(String id) {
static List<String> 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<RedisRateLimiter.Confi
int requestedTokens = routeConfig.getRequestedTokens();
try {
List<String> keys = getKeys(id);
List<String> keys = getKeys(id, routeId);
// The arguments to the LUA script. time() returns unixtime in seconds.
List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "", "", requestedTokens + "");

View File

@@ -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