diff --git a/pom.xml b/pom.xml index b0e37f97bd..3fdffa5f56 100644 --- a/pom.xml +++ b/pom.xml @@ -482,15 +482,7 @@ **/Endpoint.java src/main/java/redis/clients/jedis/mcf/*.java src/test/java/redis/clients/jedis/failover/*.java - **/mcf/EchoStrategyIntegrationTest.java - **/mcf/LagAwareStrategyUnitTest.java - **/mcf/RedisRestAPI*.java - **/mcf/ActiveActiveLocalFailoverTest* - **/mcf/FailbackMechanism*.java - **/mcf/PeriodicFailbackTest*.java - **/mcf/AutomaticFailoverTest*.java - **/mcf/MultiCluster*.java - **/mcf/StatusTracker*.java + src/test/java/redis/clients/jedis/mcf/*.java **/Health*.java **/*IT.java **/scenario/RestEndpointUtil.java diff --git a/src/main/java/redis/clients/jedis/MultiClusterClientConfig.java b/src/main/java/redis/clients/jedis/MultiClusterClientConfig.java index 9132378f2c..29e86041a7 100644 --- a/src/main/java/redis/clients/jedis/MultiClusterClientConfig.java +++ b/src/main/java/redis/clients/jedis/MultiClusterClientConfig.java @@ -130,16 +130,16 @@ public static interface StrategySupplier { .asList(JedisConnectionException.class); /** Default failure rate threshold percentage for circuit breaker activation. */ - private static final float CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD_DEFAULT = 50.0f; + private static final float CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD_DEFAULT = 10.0f; /** Default minimum number of calls required before circuit breaker can calculate failure rate. */ - private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_MIN_CALLS_DEFAULT = 100; + private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_MIN_CALLS_DEFAULT = 1000; /** Default sliding window type for circuit breaker failure tracking. */ private static final SlidingWindowType CIRCUIT_BREAKER_SLIDING_WINDOW_TYPE_DEFAULT = SlidingWindowType.COUNT_BASED; /** Default sliding window size for circuit breaker failure tracking. */ - private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT = 100; + private static final int CIRCUIT_BREAKER_SLIDING_WINDOW_SIZE_DEFAULT = 2; /** Default slow call duration threshold in milliseconds. */ private static final int CIRCUIT_BREAKER_SLOW_CALL_DURATION_THRESHOLD_DEFAULT = 60000; @@ -156,10 +156,10 @@ public static interface StrategySupplier { .asList(CallNotPermittedException.class, ConnectionFailoverException.class); /** Default interval in milliseconds for checking if failed clusters have recovered. */ - private static final long FAILBACK_CHECK_INTERVAL_DEFAULT = 5000; + private static final long FAILBACK_CHECK_INTERVAL_DEFAULT = 120000; /** Default grace period in milliseconds to keep clusters disabled after they become unhealthy. */ - private static final long GRACE_PERIOD_DEFAULT = 10000; + private static final long GRACE_PERIOD_DEFAULT = 60000; /** Default maximum number of failover attempts. */ private static final int MAX_NUM_FAILOVER_ATTEMPTS_DEFAULT = 10; diff --git a/src/main/java/redis/clients/jedis/mcf/EchoStrategy.java b/src/main/java/redis/clients/jedis/mcf/EchoStrategy.java index 512f1609fc..3c73e17d6f 100644 --- a/src/main/java/redis/clients/jedis/mcf/EchoStrategy.java +++ b/src/main/java/redis/clients/jedis/mcf/EchoStrategy.java @@ -11,18 +11,19 @@ import redis.clients.jedis.MultiClusterClientConfig.StrategySupplier; public class EchoStrategy implements HealthCheckStrategy { + private static final int MAX_HEALTH_CHECK_POOL_SIZE = 2; private final UnifiedJedis jedis; private final HealthCheckStrategy.Config config; public EchoStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig) { - this(hostAndPort, jedisClientConfig, HealthCheckStrategy.Config.builder().build()); + this(hostAndPort, jedisClientConfig, HealthCheckStrategy.Config.create()); } public EchoStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig, HealthCheckStrategy.Config config) { GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig<>(); - poolConfig.setMaxTotal(2); + poolConfig.setMaxTotal(MAX_HEALTH_CHECK_POOL_SIZE); this.jedis = new JedisPooled(hostAndPort, jedisClientConfig, poolConfig); this.config = config; } diff --git a/src/main/java/redis/clients/jedis/mcf/HealthCheckStrategy.java b/src/main/java/redis/clients/jedis/mcf/HealthCheckStrategy.java index 7e2b43d6db..7d1e5292ca 100644 --- a/src/main/java/redis/clients/jedis/mcf/HealthCheckStrategy.java +++ b/src/main/java/redis/clients/jedis/mcf/HealthCheckStrategy.java @@ -49,6 +49,11 @@ default void close() { int getDelayInBetweenProbes(); public static class Config { + private static final int INTERVAL_DEFAULT = 5000; + private static final int TIMEOUT_DEFAULT = 1000; + private static final int NUM_PROBES_DEFAULT = 3; + private static final int DELAY_IN_BETWEEN_PROBES_DEFAULT = 500; + protected final int interval; protected final int timeout; protected final int numProbes; @@ -97,14 +102,14 @@ public ProbingPolicy getPolicy() { * @return a new Config instance */ public static Config create() { - return new Builder<>().build(); + return builder().build(); } /** * Create a new builder for HealthCheckStrategy.Config. * @return a new Builder instance */ - public static Builder builder() { + public static Builder builder() { return new Builder<>(); } @@ -114,11 +119,11 @@ public static Builder builder() { * @param the config type being built */ public static class Builder, C extends Config> { - protected int interval = 1000; - protected int timeout = 1000; - protected int numProbes = 3; + protected int interval = INTERVAL_DEFAULT; + protected int timeout = TIMEOUT_DEFAULT; + protected int numProbes = NUM_PROBES_DEFAULT; protected ProbingPolicy policy = ProbingPolicy.BuiltIn.ALL_SUCCESS; - protected int delayInBetweenProbes = 100; + protected int delayInBetweenProbes = DELAY_IN_BETWEEN_PROBES_DEFAULT; /** * Set the interval between health checks in milliseconds. diff --git a/src/main/java/redis/clients/jedis/mcf/LagAwareStrategy.java b/src/main/java/redis/clients/jedis/mcf/LagAwareStrategy.java index fcbe0a11ec..03489b7be3 100644 --- a/src/main/java/redis/clients/jedis/mcf/LagAwareStrategy.java +++ b/src/main/java/redis/clients/jedis/mcf/LagAwareStrategy.java @@ -94,7 +94,7 @@ public HealthStatus doHealthCheck(Endpoint endpoint) { public static class Config extends HealthCheckStrategy.Config { public static final boolean EXTENDED_CHECK_DEFAULT = true; - public static final Duration AVAILABILITY_LAG_TOLERANCE_DEFAULT = Duration.ofMillis(100); + public static final Duration AVAILABILITY_LAG_TOLERANCE_DEFAULT = Duration.ofMillis(5000); private final Endpoint restEndpoint; private final Supplier credentialsSupplier; @@ -102,7 +102,7 @@ public static class Config extends HealthCheckStrategy.Config { // SSL configuration for HTTPS connections to Redis Enterprise REST API private final SslOptions sslOptions; - // Maximum acceptable lag in milliseconds (default: 100); + // Maximum acceptable lag in milliseconds (default: 5000); private final Duration availability_lag_tolerance; // Enable extended lag checking (default: true - performs lag validation in addition to standard @@ -111,7 +111,7 @@ public static class Config extends HealthCheckStrategy.Config { private final boolean extendedCheckEnabled; public Config(Endpoint restEndpoint, Supplier credentialsSupplier) { - this(builder(restEndpoint, credentialsSupplier).interval(1000).timeout(1000).numProbes(3) + this(builder(restEndpoint, credentialsSupplier) .availabilityLagTolerance(AVAILABILITY_LAG_TOLERANCE_DEFAULT) .extendedCheckEnabled(EXTENDED_CHECK_DEFAULT)); } @@ -157,6 +157,15 @@ public static ConfigBuilder builder(Endpoint restEndpoint, return new ConfigBuilder(restEndpoint, credentialsSupplier); } + /** + * Use {@link LagAwareStrategy.Config#builder(Endpoint, Supplier)} instead. + * @return a new Builder instance + */ + public static ConfigBuilder builder() { + throw new UnsupportedOperationException( + "Endpoint and credentials are required to build LagAwareStrategy.Config."); + } + /** * Create a new Config instance with default values. *

diff --git a/src/test/java/redis/clients/jedis/mcf/DefaultValuesTest.java b/src/test/java/redis/clients/jedis/mcf/DefaultValuesTest.java new file mode 100644 index 0000000000..6939ef7069 --- /dev/null +++ b/src/test/java/redis/clients/jedis/mcf/DefaultValuesTest.java @@ -0,0 +1,77 @@ +package redis.clients.jedis.mcf; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.time.Duration; + +import org.junit.jupiter.api.Test; +import redis.clients.jedis.DefaultJedisClientConfig; +import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.JedisClientConfig; +import redis.clients.jedis.MultiClusterClientConfig; + +public class DefaultValuesTest { + + HostAndPort fakeEndpoint = new HostAndPort("fake", 6379); + JedisClientConfig config = DefaultJedisClientConfig.builder().build(); + + @Test + void testDefaultValuesInConfig() { + + MultiClusterClientConfig.ClusterConfig clusterConfig = MultiClusterClientConfig.ClusterConfig + .builder(fakeEndpoint, config).build(); + MultiClusterClientConfig multiConfig = new MultiClusterClientConfig.Builder( + new MultiClusterClientConfig.ClusterConfig[] { clusterConfig }).build(); + + // check for grace period + assertEquals(60000, multiConfig.getGracePeriod()); + + // check for cluster config + assertEquals(clusterConfig, multiConfig.getClusterConfigs()[0]); + + // check healthchecks enabled + assertNotNull(clusterConfig.getHealthCheckStrategySupplier()); + + // check default healthcheck strategy is echo + assertEquals(EchoStrategy.DEFAULT, clusterConfig.getHealthCheckStrategySupplier()); + + // check number of probes + assertEquals(3, + clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getNumProbes()); + + assertEquals(500, clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config) + .getDelayInBetweenProbes()); + + assertEquals(ProbingPolicy.BuiltIn.ALL_SUCCESS, + clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getPolicy()); + + // check health check interval + assertEquals(5000, + clusterConfig.getHealthCheckStrategySupplier().get(fakeEndpoint, config).getInterval()); + + // check lag aware tolerance + LagAwareStrategy.Config lagAwareConfig = LagAwareStrategy.Config + .builder(fakeEndpoint, config.getCredentialsProvider()).build(); + assertEquals(Duration.ofMillis(5000), lagAwareConfig.getAvailabilityLagTolerance()); + + // TODO: check CB number of failures threshold -- 1000 + // assertEquals(1000, multiConfig.circuitBreakerMinNumOfFailures()); + + // check CB failure rate threshold + assertEquals(10, multiConfig.getCircuitBreakerFailureRateThreshold()); + + // check CB sliding window size + assertEquals(2, multiConfig.getCircuitBreakerSlidingWindowSize()); + + // check failback check interval + assertEquals(120000, multiConfig.getFailbackCheckInterval()); + + // check failover max attempts before give up + assertEquals(10, multiConfig.getMaxNumFailoverAttempts()); + + // check delay between failover attempts + assertEquals(12000, multiConfig.getDelayInBetweenFailoverAttempts()); + + } +} diff --git a/src/test/java/redis/clients/jedis/mcf/FailbackMechanismUnitTest.java b/src/test/java/redis/clients/jedis/mcf/FailbackMechanismUnitTest.java index 4e57b3c466..25de5a7c3e 100644 --- a/src/test/java/redis/clients/jedis/mcf/FailbackMechanismUnitTest.java +++ b/src/test/java/redis/clients/jedis/mcf/FailbackMechanismUnitTest.java @@ -32,7 +32,7 @@ void testFailbackCheckIntervalConfiguration() { MultiClusterClientConfig defaultConfig = new MultiClusterClientConfig.Builder( new MultiClusterClientConfig.ClusterConfig[] { clusterConfig }).build(); - assertEquals(5000, defaultConfig.getFailbackCheckInterval()); + assertEquals(120000, defaultConfig.getFailbackCheckInterval()); // Test custom value MultiClusterClientConfig customConfig = new MultiClusterClientConfig.Builder( @@ -105,7 +105,7 @@ void testGracePeriodConfiguration() { MultiClusterClientConfig defaultConfig = new MultiClusterClientConfig.Builder( new MultiClusterClientConfig.ClusterConfig[] { clusterConfig }).build(); - assertEquals(10000, defaultConfig.getGracePeriod()); // Default is 10 seconds + assertEquals(60000, defaultConfig.getGracePeriod()); // Default is 10 seconds // Test custom value MultiClusterClientConfig customConfig = new MultiClusterClientConfig.Builder( diff --git a/src/test/java/redis/clients/jedis/mcf/HealthCheckTest.java b/src/test/java/redis/clients/jedis/mcf/HealthCheckTest.java index d81e012c1e..d7b61dcd20 100644 --- a/src/test/java/redis/clients/jedis/mcf/HealthCheckTest.java +++ b/src/test/java/redis/clients/jedis/mcf/HealthCheckTest.java @@ -531,7 +531,7 @@ void testStrategySupplierPolymorphism() { // Test without config HealthCheckStrategy strategyWithoutConfig = supplier.get(testEndpoint, null); assertNotNull(strategyWithoutConfig); - assertEquals(1000, strategyWithoutConfig.getInterval()); // Default values + assertEquals(5000, strategyWithoutConfig.getInterval()); // Default values assertEquals(1000, strategyWithoutConfig.getTimeout()); } diff --git a/src/test/java/redis/clients/jedis/mcf/LagAwareStrategyUnitTest.java b/src/test/java/redis/clients/jedis/mcf/LagAwareStrategyUnitTest.java index 459b3be8c3..c985c0d06d 100644 --- a/src/test/java/redis/clients/jedis/mcf/LagAwareStrategyUnitTest.java +++ b/src/test/java/redis/clients/jedis/mcf/LagAwareStrategyUnitTest.java @@ -50,7 +50,7 @@ void healthy_when_bdb_available_and_cached_uid_used_on_next_check() throws Excep try (MockedConstruction mockedConstructor = mockConstruction(RedisRestAPI.class, (mock, context) -> { when(mock.getBdbs()).thenReturn(Arrays.asList(bdbInfo)); - when(mock.checkBdbAvailability("1", true, 100L)).thenReturn(true); + when(mock.checkBdbAvailability("1", true, 5000L)).thenReturn(true); reference[0] = mock; })) { Config lagCheckConfig = Config.builder(endpoint, creds).interval(500).timeout(250) @@ -61,7 +61,7 @@ void healthy_when_bdb_available_and_cached_uid_used_on_next_check() throws Excep assertEquals(HealthStatus.HEALTHY, strategy.doHealthCheck(endpoint)); verify(api, times(1)).getBdbs(); // Should not call getBdbs again when cached - verify(api, times(2)).checkBdbAvailability("1", true, 100L); + verify(api, times(2)).checkBdbAvailability("1", true, 5000L); } } } @@ -97,7 +97,7 @@ void exception_and_cache_reset_on_exception_then_recovers_next_time() throws Exc // First call throws exception, second call returns bdbInfo when(mock.getBdbs()).thenThrow(new RuntimeException("boom")) .thenReturn(Arrays.asList(bdbInfo)); - when(mock.checkBdbAvailability("42", true, 100L)).thenReturn(true); + when(mock.checkBdbAvailability("42", true, 5000L)).thenReturn(true); reference[0] = mock; })) { @@ -115,7 +115,7 @@ void exception_and_cache_reset_on_exception_then_recovers_next_time() throws Exc // Verify getBdbs was called twice (once failed, once succeeded) verify(api, times(2)).getBdbs(); // Verify availability check was called only once (on the successful attempt) - verify(api, times(1)).checkBdbAvailability("42", true, 100L); + verify(api, times(1)).checkBdbAvailability("42", true, 5000L); } } } @@ -173,10 +173,10 @@ void exception_when_no_matching_host_found() throws Exception { void config_builder_creates_config_with_default_values() { Config config = Config.builder(endpoint, creds).build(); - assertEquals(1000, config.interval); + assertEquals(5000, config.interval); assertEquals(1000, config.timeout); assertEquals(3, config.numProbes); - assertEquals(Duration.ofMillis(100), config.getAvailabilityLagTolerance()); + assertEquals(Duration.ofMillis(5000), config.getAvailabilityLagTolerance()); assertEquals(endpoint, config.getRestEndpoint()); assertEquals(creds, config.getCredentialsSupplier()); } @@ -288,7 +288,7 @@ void base_config_builder_factory_method_works() { void base_config_create_factory_method_uses_defaults() { HealthCheckStrategy.Config config = HealthCheckStrategy.Config.create(); - assertEquals(1000, config.getInterval()); + assertEquals(5000, config.getInterval()); assertEquals(1000, config.getTimeout()); assertEquals(3, config.getNumProbes()); }