Skip to content

Commit 03387cf

Browse files
committed
[automatic failover] feat: Add MultiDbClient with multi-endpoint failover and circuit breaker support (#4300)
* feat: introduce ResilientRedisClient with multi-endpoint failover support Add ResilientRedisClient extending UnifiedJedis with automatic failover capabilities across multiple weighted Redis endpoints. Includes circuit breaker pattern, health monitoring, and configurable retry logic for high-availability Redis deployments. * format * mark ResilientRedisClientTest as integration one * fix test - make sure endpoint is healthy before activating it * Rename ResilientClient to align with design - ResilientClient -> MultiDbClient (builder, tests, etc) * Rename setActiveEndpoint to setActiveDatabaseEndpoint * Rename clusterSwitchListener to databaseSwitchListener * Rename multiClusterConfig to multiDbConfig * fix api doc's error * fix compilation error after rebase * format * fix example in javadoc * Update ActiveActiveFailoverTest scenariou test to use builder's # Conflicts: # src/test/java/redis/clients/jedis/scenario/ActiveActiveFailoverTest.java * rename setActiveDatabaseEndpoint -. setActiveDatabase * is healthy throw exception if cluster does not exists * format
1 parent 3c702e8 commit 03387cf

File tree

9 files changed

+798
-39
lines changed

9 files changed

+798
-39
lines changed

pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,9 @@
491491
<include>src/main/java/redis/clients/jedis/MultiClusterClientConfig.java</include>
492492
<include>src/main/java/redis/clients/jedis/HostAndPort.java</include>
493493
<include>**/builders/*.java</include>
494+
<include>**/MultiDb*.java</include>
495+
<include>**/ClientTestUtil.java</include>
496+
<include>**/ReflectionTestUtil.java</include>
494497
</includes>
495498
</configuration>
496499
<executions>

src/main/java/redis/clients/jedis/MultiClusterClientConfig.java

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
44
import java.time.Duration;
5+
import java.util.ArrayList;
56
import java.util.Arrays;
67
import java.util.List;
78

@@ -450,8 +451,10 @@ public static interface StrategySupplier {
450451
* @see Builder#Builder(ClusterConfig[])
451452
*/
452453
public MultiClusterClientConfig(ClusterConfig[] clusterConfigs) {
454+
453455
if (clusterConfigs == null || clusterConfigs.length < 1) throw new JedisValidationException(
454456
"ClusterClientConfigs are required for MultiClusterPooledConnectionProvider");
457+
455458
for (ClusterConfig clusterConfig : clusterConfigs) {
456459
if (clusterConfig == null)
457460
throw new IllegalArgumentException("ClusterClientConfigs must not contain null elements");
@@ -634,6 +637,20 @@ public boolean isFastFailover() {
634637
return fastFailover;
635638
}
636639

640+
/**
641+
* Creates a new Builder instance for configuring MultiClusterClientConfig.
642+
* <p>
643+
* At least one cluster configuration must be added to the builder before calling build(). Use the
644+
* endpoint() methods to add cluster configurations.
645+
* </p>
646+
* @return new Builder instance
647+
* @throws JedisValidationException if clusterConfigs is null or empty
648+
* @see Builder#Builder(ClusterConfig[])
649+
*/
650+
public static Builder builder() {
651+
return new Builder();
652+
}
653+
637654
/**
638655
* Creates a new Builder instance for configuring MultiClusterClientConfig.
639656
* @param clusterConfigs array of cluster configurations defining available Redis endpoints
@@ -751,6 +768,7 @@ public HostAndPort getHostAndPort() {
751768
* @return new Builder instance
752769
* @throws IllegalArgumentException if hostAndPort or clientConfig is null
753770
*/
771+
// TODO : Replace HostAndPort with Endpoint
754772
public static Builder builder(HostAndPort hostAndPort, JedisClientConfig clientConfig) {
755773
return new Builder(hostAndPort, clientConfig);
756774
}
@@ -974,7 +992,7 @@ public ClusterConfig build() {
974992
public static class Builder {
975993

976994
/** Array of cluster configurations defining available Redis endpoints. */
977-
private ClusterConfig[] clusterConfigs;
995+
private final List<ClusterConfig> clusterConfigs = new ArrayList<>();
978996

979997
// ============ Retry Configuration Fields ============
980998
/** Maximum number of retry attempts including the initial call. */
@@ -1033,17 +1051,20 @@ public static class Builder {
10331051
/** Delay in milliseconds between failover attempts. */
10341052
private int delayInBetweenFailoverAttempts = DELAY_IN_BETWEEN_FAILOVER_ATTEMPTS_DEFAULT;
10351053

1054+
/**
1055+
* Constructs a new Builder with the specified cluster configurations.
1056+
*/
1057+
public Builder() {
1058+
}
1059+
10361060
/**
10371061
* Constructs a new Builder with the specified cluster configurations.
10381062
* @param clusterConfigs array of cluster configurations defining available Redis endpoints
10391063
* @throws JedisValidationException if clusterConfigs is null or empty
10401064
*/
10411065
public Builder(ClusterConfig[] clusterConfigs) {
10421066

1043-
if (clusterConfigs == null || clusterConfigs.length < 1) throw new JedisValidationException(
1044-
"ClusterClientConfigs are required for MultiClusterPooledConnectionProvider");
1045-
1046-
this.clusterConfigs = clusterConfigs;
1067+
this(Arrays.asList(clusterConfigs));
10471068
}
10481069

10491070
/**
@@ -1052,7 +1073,47 @@ public Builder(ClusterConfig[] clusterConfigs) {
10521073
* @throws JedisValidationException if clusterConfigs is null or empty
10531074
*/
10541075
public Builder(List<ClusterConfig> clusterConfigs) {
1055-
this(clusterConfigs.toArray(new ClusterConfig[0]));
1076+
this.clusterConfigs.addAll(clusterConfigs);
1077+
}
1078+
1079+
/**
1080+
* Adds a pre-configured endpoint configuration.
1081+
* <p>
1082+
* This method allows adding a fully configured ClusterConfig instance, providing maximum
1083+
* flexibility for advanced configurations including custom health check strategies, connection
1084+
* pool settings, etc.
1085+
* </p>
1086+
* @param clusterConfig the pre-configured cluster configuration
1087+
* @return this builder
1088+
*/
1089+
public Builder endpoint(ClusterConfig clusterConfig) {
1090+
this.clusterConfigs.add(clusterConfig);
1091+
return this;
1092+
}
1093+
1094+
/**
1095+
* Adds a Redis endpoint with custom client configuration.
1096+
* <p>
1097+
* This method allows specifying endpoint-specific configuration such as authentication, SSL
1098+
* settings, timeouts, etc. This configuration will override the default client configuration
1099+
* for this specific endpoint.
1100+
* </p>
1101+
* @param endpoint the Redis server endpoint
1102+
* @param weight the weight for this endpoint (higher values = higher priority)
1103+
* @param clientConfig the client configuration for this endpoint
1104+
* @return this builder
1105+
*/
1106+
public Builder endpoint(Endpoint endpoint, float weight, JedisClientConfig clientConfig) {
1107+
// Convert Endpoint to HostAndPort for ClusterConfig
1108+
// TODO : Refactor ClusterConfig to accept Endpoint directly
1109+
HostAndPort hostAndPort = (endpoint instanceof HostAndPort) ? (HostAndPort) endpoint
1110+
: new HostAndPort(endpoint.getHost(), endpoint.getPort());
1111+
1112+
ClusterConfig clusterConfig = ClusterConfig.builder(hostAndPort, clientConfig).weight(weight)
1113+
.build();
1114+
1115+
this.clusterConfigs.add(clusterConfig);
1116+
return this;
10561117
}
10571118

10581119
// ============ Retry Configuration Methods ============
@@ -1453,7 +1514,9 @@ public Builder delayInBetweenFailoverAttempts(int delayInBetweenFailoverAttempts
14531514
* @return a new MultiClusterClientConfig instance with the configured settings
14541515
*/
14551516
public MultiClusterClientConfig build() {
1456-
MultiClusterClientConfig config = new MultiClusterClientConfig(this.clusterConfigs);
1517+
1518+
MultiClusterClientConfig config = new MultiClusterClientConfig(
1519+
this.clusterConfigs.toArray(new ClusterConfig[0]));
14571520

14581521
// Copy retry configuration
14591522
config.retryMaxAttempts = this.retryMaxAttempts;

0 commit comments

Comments
 (0)