Restore Redis retries for select operations

This commit is contained in:
Jon Chambers
2025-08-27 11:52:16 -04:00
committed by GitHub
parent f616612104
commit 8825396fc1
46 changed files with 449 additions and 262 deletions

View File

@@ -13,6 +13,7 @@ import java.io.UncheckedIOException;
import java.time.Clock;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
@@ -30,11 +31,12 @@ public abstract class BaseRateLimiters<T extends RateLimiterDescriptor> {
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final ClusterLuaScript validateScript,
final FaultTolerantRedisClusterClient cacheCluster,
final ScheduledExecutorService retryExecutor,
final Clock clock) {
this.rateLimiterByDescriptor = Arrays.stream(values)
.map(descriptor -> Pair.of(
descriptor,
createForDescriptor(descriptor, dynamicConfigurationManager, validateScript, cacheCluster, clock)))
createForDescriptor(descriptor, dynamicConfigurationManager, validateScript, cacheCluster, retryExecutor, clock)))
.collect(Collectors.toUnmodifiableMap(Pair::getKey, Pair::getValue));
}
@@ -56,9 +58,10 @@ public abstract class BaseRateLimiters<T extends RateLimiterDescriptor> {
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final ClusterLuaScript validateScript,
final FaultTolerantRedisClusterClient cacheCluster,
final ScheduledExecutorService retryExecutor,
final Clock clock) {
final Supplier<RateLimiterConfig> configResolver =
() -> dynamicConfigurationManager.getConfiguration().getLimits().getOrDefault(descriptor.id(), descriptor.defaultConfig());
return new DynamicRateLimiter(descriptor.id(), configResolver, validateScript, cacheCluster, clock);
return new DynamicRateLimiter(descriptor.id(), configResolver, validateScript, cacheCluster, retryExecutor, clock);
}
}

View File

@@ -14,11 +14,13 @@ import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.metrics.MetricsUtil;
import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
import org.whispersystems.textsecuregcm.util.CircuitBreakerUtil;
import org.whispersystems.textsecuregcm.util.ExceptionUtils;
import org.whispersystems.textsecuregcm.util.Util;
@@ -30,22 +32,26 @@ public class DynamicRateLimiter implements RateLimiter {
private final ClusterLuaScript validateScript;
private final FaultTolerantRedisClusterClient cluster;
private final ScheduledExecutorService retryExecutor;
private final Counter limitExceededCounter;
private final Clock clock;
private static final String RETRY_NAME = DynamicRateLimiter.class.getSimpleName();
public DynamicRateLimiter(
final String name,
final Supplier<RateLimiterConfig> configResolver,
final ClusterLuaScript validateScript,
final FaultTolerantRedisClusterClient cluster,
final ScheduledExecutorService retryExecutor,
final Clock clock) {
this.name = requireNonNull(name);
this.configResolver = requireNonNull(configResolver);
this.validateScript = requireNonNull(validateScript);
this.cluster = requireNonNull(cluster);
this.retryExecutor = requireNonNull(retryExecutor);
this.clock = requireNonNull(clock);
this.limitExceededCounter = Metrics.counter(MetricsUtil.name(getClass(), "exceeded"), "rateLimiterName", name);
}
@@ -129,13 +135,15 @@ public class DynamicRateLimiter implements RateLimiter {
@Override
public void clear(final String key) {
cluster.useCluster(connection -> connection.sync().del(bucketName(name, key)));
CircuitBreakerUtil.getGeneralRedisRetry(RETRY_NAME)
.executeRunnable(() -> cluster.useCluster(connection -> connection.sync().del(bucketName(name, key))));
}
@Override
public CompletionStage<Void> clearAsync(final String key) {
return cluster.withCluster(connection -> connection.async().del(bucketName(name, key)))
.thenRun(Util.NOOP);
return CircuitBreakerUtil.getGeneralRedisRetry(RETRY_NAME)
.executeCompletionStage(retryExecutor, () -> cluster.withCluster(connection -> connection.async().del(bucketName(name, key)))
.thenRun(Util.NOOP));
}
@Override

View File

@@ -7,6 +7,7 @@ package org.whispersystems.textsecuregcm.limits;
import com.google.common.annotations.VisibleForTesting;
import java.time.Clock;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.redis.ClusterLuaScript;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisClusterClient;
@@ -77,9 +78,10 @@ public class RateLimiters extends BaseRateLimiters<RateLimiters.For> {
public static RateLimiters create(
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final FaultTolerantRedisClusterClient cacheCluster) {
final FaultTolerantRedisClusterClient cacheCluster,
final ScheduledExecutorService retryExecutor) {
return new RateLimiters(
dynamicConfigurationManager, defaultScript(cacheCluster), cacheCluster, Clock.systemUTC());
dynamicConfigurationManager, defaultScript(cacheCluster), cacheCluster, retryExecutor, Clock.systemUTC());
}
@VisibleForTesting
@@ -87,8 +89,9 @@ public class RateLimiters extends BaseRateLimiters<RateLimiters.For> {
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager,
final ClusterLuaScript validateScript,
final FaultTolerantRedisClusterClient cacheCluster,
final ScheduledExecutorService retryExecutor,
final Clock clock) {
super(For.values(), dynamicConfigurationManager, validateScript, cacheCluster, clock);
super(For.values(), dynamicConfigurationManager, validateScript, cacheCluster, retryExecutor, clock);
}
public RateLimiter getAllocateDeviceLimiter() {