Skip to content

Commit 840eb9e

Browse files
atakavciggivo
andauthored
[automatic failover] Automatic failover client improvements (part 4) (#4317)
* [automatic failover] Replace EchoStrategy with PingStrategy (#4313) * - replace EchoStrategy with PingStrategy * - fix doc * [automatic failover] Remove UnifiedJedis experimental constructor accepting MultiDbConnectionProvider (#4316) * Remove UnifiedJedis(MultiDbConnectionProvider) experimental constructor The recommended way to create MultiDbClient is using MultiDbClient.builder(). Update all tests to use the builder pattern. Fixes #4307 * revert unintentional change in MultiDbClientBuilder * Squashed commit of the following: commit 297279e Author: Igor Malinovskiy <u.glide@gmail.com> Date: Thu Oct 9 13:06:10 2025 +0200 Add v6 and v7 migration guides (#4315) * Add migration guide for v7 * Move all migration guides to subfolder * Update readme: - Bump client version - Update list of supported Redis versions - Remove references to JedisPool - Remove reference to Google Group * Remove duplicated content in the migration guide * Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add missing v5 to v6 migration guide * Fix broken link * Update outdated modules section in README --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> commit 3645601 Author: Ivo Gaydazhiev <ivo.gaydazhiev@redis.com> Date: Thu Oct 9 13:53:02 2025 +0300 Dedicated profile for running Scenario tests (#4312) * Dedicated profile for running Scenario tests to run only scenario tests mvn -Pscenario-tests clean verify * format * remove import after rebase conflict * fix RedisRestAPIIT scenario test - increase expected bdb's count to account for additional db added to test env - fix incorrect bdbid used for lag-aware availablity checks now using bdb of'"re-active-active" * fix LagAwareStrategySslIT scenario test - Now exception is propagated in case of ssl connection errors instead of returning UNHEALTHY - updated the test to current expectations - Enhanced test to ensure untrusted default certificate * java 8 compatibility * remove @tag("integration") from IntegrationTest* to avoid multiple runs of same test --------- Co-authored-by: Igor Malinovskiy <u.glide@gmail.com> commit 07bf3b0 Author: Igor Malinovskiy <u.glide@gmail.com> Date: Thu Oct 9 12:00:36 2025 +0200 [automatic failover] Update failover docs (#4314) Update failover docs - Add migration guide from 6.x to 7.0 - Add instructions on optional deps - Clean up wording to refer to multiDbClient instead of connection provider commit 158e726 Author: Igor Malinovskiy <u.glide@gmail.com> Date: Thu Oct 9 11:14:28 2025 +0200 Remove deprecated constructors, classes and JedisSharding (#4311) * Remove JedisSharding * Remove deprecated constructors in UnifiedJedis * Clean up UnifiedJedisConstructorReflectionTest * Remove remaining ShardedCommandArguments * Remove deprecated PipelineBase * Remove deprecated TransactionBase * Remove unused attr in MultiDbTransaction commit 5db1a39 Author: Igor Malinovskiy <u.glide@gmail.com> Date: Thu Oct 9 10:04:06 2025 +0200 Remove spellcheck (#4309) Nowadays, AI reviewing tools like CoPilot catch spellcheck issues better than pyspelling without a burden of maintaining wordlist.txt --------- Co-authored-by: Ivo Gaydazhiev <ivo.gaydazhiev@redis.com>
1 parent 297279e commit 840eb9e

13 files changed

+101
-103
lines changed

docs/failover.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,18 @@ The health check system operates independently of your application traffic, runn
209209

210210
#### Available Health Check Types
211211

212-
##### 1. EchoStrategy (Default)
212+
##### 1. PingStrategy (Default)
213213

214-
The `EchoStrategy` is the default health check implementation that uses Redis's `ECHO` command to verify both connectivity and write capability.
214+
The `PingStrategy` is the default health check implementation that uses Redis's `PING` command to verify both connectivity and write capability.
215215

216216
**Use Cases:**
217217
- General-purpose health checking for most Redis deployments
218218
- Verifying both read and write operations
219219
- Simple connectivity validation
220220

221221
**How it works:**
222-
- Sends `ECHO "HealthCheck"` command to the Redis server
223-
- Expects exact response `"HealthCheck"` to consider the server healthy
222+
- Sends `PING` command to the Redis server
223+
- Expects exact response `"PONG"` to consider the server healthy
224224
- Any exception or unexpected response marks the server as unhealthy
225225

226226
##### 2. LagAwareStrategy [PREVIEW] (Redis Enterprise)

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import redis.clients.jedis.exceptions.JedisConnectionException;
1212
import redis.clients.jedis.exceptions.JedisValidationException;
1313
import redis.clients.jedis.mcf.ConnectionFailoverException;
14-
import redis.clients.jedis.mcf.EchoStrategy;
14+
import redis.clients.jedis.mcf.PingStrategy;
1515
import redis.clients.jedis.mcf.HealthCheckStrategy;
1616

1717
/**
@@ -71,7 +71,7 @@
7171
* </p>
7272
* @see redis.clients.jedis.mcf.MultiDbConnectionProvider
7373
* @see redis.clients.jedis.mcf.HealthCheckStrategy
74-
* @see redis.clients.jedis.mcf.EchoStrategy
74+
* @see redis.clients.jedis.mcf.PingStrategy
7575
* @see redis.clients.jedis.mcf.LagAwareStrategy
7676
* @since 7.0
7777
*/
@@ -90,13 +90,13 @@ public final class MultiDbConfig {
9090
* <strong>Common Implementations:</strong>
9191
* </p>
9292
* <ul>
93-
* <li>{@link redis.clients.jedis.mcf.EchoStrategy#DEFAULT} - Uses Redis ECHO command for health
93+
* <li>{@link redis.clients.jedis.mcf.PingStrategy#DEFAULT} - Uses Redis PING command for health
9494
* checks</li>
9595
* <li>Custom implementations for specific monitoring requirements</li>
9696
* <li>Redis Enterprise implementations using REST API monitoring</li>
9797
* </ul>
9898
* @see redis.clients.jedis.mcf.HealthCheckStrategy
99-
* @see redis.clients.jedis.mcf.EchoStrategy
99+
* @see redis.clients.jedis.mcf.PingStrategy
100100
* @see redis.clients.jedis.mcf.LagAwareStrategy
101101
*/
102102
public static interface StrategySupplier {
@@ -839,15 +839,15 @@ public static class DatabaseConfig {
839839

840840
/**
841841
* Strategy supplier for creating health check instances for this database. Default is
842-
* EchoStrategy.DEFAULT.
842+
* PingStrategy.DEFAULT.
843843
*/
844844
private StrategySupplier healthCheckStrategySupplier;
845845

846846
/**
847847
* Constructs a DatabaseConfig with basic endpoint and client configuration.
848848
* <p>
849849
* This constructor creates a database configuration with default settings: weight of 1.0f and
850-
* EchoStrategy for health checks. Use the {@link Builder} for more advanced configuration
850+
* PingStrategy for health checks. Use the {@link Builder} for more advanced configuration
851851
* options.
852852
* </p>
853853
* @param endpoint the Redis endpoint (host and port)
@@ -863,7 +863,7 @@ public DatabaseConfig(Endpoint endpoint, JedisClientConfig clientConfig) {
863863
* Constructs a DatabaseConfig with endpoint, client, and connection pool configuration.
864864
* <p>
865865
* This constructor allows specification of connection pool settings in addition to basic
866-
* endpoint configuration. Default weight of 1.0f and EchoStrategy for health checks are used.
866+
* endpoint configuration. Default weight of 1.0f and PingStrategy for health checks are used.
867867
* </p>
868868
* @param endpoint the Redis endpoint (host and port)
869869
* @param clientConfig the Jedis client configuration
@@ -963,7 +963,7 @@ public StrategySupplier getHealthCheckStrategySupplier() {
963963
* </p>
964964
* <ul>
965965
* <li><strong>Weight:</strong> 1.0f (standard priority)</li>
966-
* <li><strong>Health Check:</strong> {@link redis.clients.jedis.mcf.EchoStrategy#DEFAULT}</li>
966+
* <li><strong>Health Check:</strong> {@link redis.clients.jedis.mcf.PingStrategy#DEFAULT}</li>
967967
* <li><strong>Connection Pool:</strong> null (uses default pooling)</li>
968968
* </ul>
969969
*/
@@ -980,8 +980,8 @@ public static class Builder {
980980
/** Weight for database selection priority. Default: 1.0f */
981981
private float weight = 1.0f;
982982

983-
/** Health check strategy supplier. Default: EchoStrategy.DEFAULT */
984-
private StrategySupplier healthCheckStrategySupplier = EchoStrategy.DEFAULT;
983+
/** Health check strategy supplier. Default: PingStrategy.DEFAULT */
984+
private StrategySupplier healthCheckStrategySupplier = PingStrategy.DEFAULT;
985985

986986
/**
987987
* Constructs a new Builder with required endpoint and client configuration.
@@ -1089,7 +1089,7 @@ public Builder healthCheckStrategy(HealthCheckStrategy healthCheckStrategy) {
10891089
* </ul>
10901090
* <p>
10911091
* When health checks are enabled (true) and no strategy supplier was previously set, the
1092-
* default {@link redis.clients.jedis.mcf.EchoStrategy#DEFAULT} will be used.
1092+
* default {@link redis.clients.jedis.mcf.PingStrategy#DEFAULT} will be used.
10931093
* </p>
10941094
* @param healthCheckEnabled true to enable health checks, false to disable
10951095
* @return this builder instance for method chaining
@@ -1098,7 +1098,7 @@ public Builder healthCheckEnabled(boolean healthCheckEnabled) {
10981098
if (!healthCheckEnabled) {
10991099
this.healthCheckStrategySupplier = null;
11001100
} else if (healthCheckStrategySupplier == null) {
1101-
this.healthCheckStrategySupplier = EchoStrategy.DEFAULT;
1101+
this.healthCheckStrategySupplier = PingStrategy.DEFAULT;
11021102
}
11031103
return this;
11041104
}

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -194,18 +194,6 @@ public UnifiedJedis(ConnectionProvider provider, int maxAttempts, Duration maxTo
194194
this(new RetryableCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration), provider);
195195
}
196196

197-
/**
198-
* Constructor which supports multiple cluster/database endpoints each with their own isolated connection pool.
199-
* <p>
200-
* With this Constructor users can seamlessly failover to Disaster Recovery (DR), Backup, and Active-Active cluster(s)
201-
* by using simple configuration which is passed through from Resilience4j - https://resilience4j.readme.io/docs
202-
* <p>
203-
*/
204-
@Experimental
205-
public UnifiedJedis(MultiDbConnectionProvider provider) {
206-
this(new MultiDbCommandExecutor(provider), provider);
207-
}
208-
209197
/**
210198
* The constructor to use a custom {@link CommandExecutor}.
211199
* <p>

src/main/java/redis/clients/jedis/mcf/EchoStrategy.java renamed to src/main/java/redis/clients/jedis/mcf/PingStrategy.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,17 @@
1010
import redis.clients.jedis.UnifiedJedis;
1111
import redis.clients.jedis.MultiDbConfig.StrategySupplier;
1212

13-
public class EchoStrategy implements HealthCheckStrategy {
13+
public class PingStrategy implements HealthCheckStrategy {
1414
private static final int MAX_HEALTH_CHECK_POOL_SIZE = 2;
1515

1616
private final UnifiedJedis jedis;
1717
private final HealthCheckStrategy.Config config;
1818

19-
public EchoStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig) {
19+
public PingStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig) {
2020
this(hostAndPort, jedisClientConfig, HealthCheckStrategy.Config.create());
2121
}
2222

23-
public EchoStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig,
23+
public PingStrategy(HostAndPort hostAndPort, JedisClientConfig jedisClientConfig,
2424
HealthCheckStrategy.Config config) {
2525
GenericObjectPoolConfig<Connection> poolConfig = new GenericObjectPoolConfig<>();
2626
poolConfig.setMaxTotal(MAX_HEALTH_CHECK_POOL_SIZE);
@@ -55,15 +55,14 @@ public int getDelayInBetweenProbes() {
5555

5656
@Override
5757
public HealthStatus doHealthCheck(Endpoint endpoint) {
58-
return "HealthCheck".equals(jedis.echo("HealthCheck")) ? HealthStatus.HEALTHY
59-
: HealthStatus.UNHEALTHY;
58+
return "PONG".equals(jedis.ping()) ? HealthStatus.HEALTHY : HealthStatus.UNHEALTHY;
6059
}
6160

6261
@Override
6362
public void close() {
6463
jedis.close();
6564
}
6665

67-
public static final StrategySupplier DEFAULT = EchoStrategy::new;
66+
public static final StrategySupplier DEFAULT = PingStrategy::new;
6867

6968
}

src/test/java/redis/clients/jedis/failover/FailoverIntegrationTest.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import redis.clients.jedis.EndpointConfig;
1717
import redis.clients.jedis.HostAndPorts;
1818
import redis.clients.jedis.JedisClientConfig;
19+
import redis.clients.jedis.MultiDbClient;
1920
import redis.clients.jedis.MultiDbConfig;
2021
import redis.clients.jedis.UnifiedJedis;
2122
import redis.clients.jedis.exceptions.JedisConnectionException;
@@ -58,7 +59,7 @@ public class FailoverIntegrationTest {
5859
private static String JEDIS1_ID = "";
5960
private static String JEDIS2_ID = "";
6061
private MultiDbConnectionProvider provider;
61-
private UnifiedJedis failoverClient;
62+
private MultiDbClient failoverClient;
6263

6364
@BeforeAll
6465
public static void setupAdminClients() throws IOException {
@@ -110,7 +111,7 @@ public void setup() throws IOException {
110111

111112
// Create default provider and client for most tests
112113
provider = createProvider();
113-
failoverClient = new UnifiedJedis(provider);
114+
failoverClient = MultiDbClient.builder().connectionProvider(provider).build();
114115
}
115116

116117
@AfterEach
@@ -272,7 +273,7 @@ public void testCircuitBreakerCountsEachConnectionErrorSeparately() throws IOExc
272273
.build();
273274

274275
MultiDbConnectionProvider provider = new MultiDbConnectionProvider(failoverConfig);
275-
try (UnifiedJedis client = new UnifiedJedis(provider)) {
276+
try (MultiDbClient client = MultiDbClient.builder().connectionProvider(provider).build()) {
276277
// Verify initial connection to first endpoint
277278
assertThat(getNodeId(client.info("server")), equalTo(JEDIS1_ID));
278279

@@ -321,7 +322,8 @@ public void testInflightCommandsAreRetriedAfterFailover() throws Exception {
321322
builder -> builder.retryOnFailover(true));
322323

323324
// Create a custom client with retryOnFailover enabled for this specific test
324-
try (UnifiedJedis customClient = new UnifiedJedis(customProvider)) {
325+
try (MultiDbClient customClient = MultiDbClient.builder().connectionProvider(customProvider)
326+
.build()) {
325327

326328
assertThat(getNodeId(customClient.info("server")), equalTo(JEDIS1_ID));
327329
Thread.sleep(1000);
@@ -362,7 +364,8 @@ public void testInflightCommandsAreNotRetriedAfterFailover() throws Exception {
362364
MultiDbConnectionProvider customProvider = createProvider(
363365
builder -> builder.retryOnFailover(false));
364366

365-
try (UnifiedJedis customClient = new UnifiedJedis(customProvider)) {
367+
try (MultiDbClient customClient = MultiDbClient.builder().connectionProvider(customProvider)
368+
.build()) {
366369

367370
assertThat(getNodeId(customClient.info("server")), equalTo(JEDIS1_ID));
368371
Future<List<String>> blpop = executor.submit(() -> customClient.blpop(500, "test-list-2"));

src/test/java/redis/clients/jedis/mcf/ActiveActiveLocalFailoverTest.java

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import redis.clients.jedis.scenario.RecommendedSettings;
2424
import redis.clients.jedis.scenario.FaultInjectionClient.TriggerActionResponse;
2525
import redis.clients.jedis.exceptions.JedisConnectionException;
26+
import redis.clients.jedis.util.ClientTestUtil;
2627

2728
import java.io.IOException;
2829
import java.time.Duration;
@@ -152,13 +153,12 @@ public void accept(DatabaseSwitchEvent e) {
152153
ensureEndpointAvailability(endpoint1.getHostAndPort(), config);
153154
ensureEndpointAvailability(endpoint2.getHostAndPort(), config);
154155

155-
// Create the connection provider
156-
MultiDbConnectionProvider provider = new MultiDbConnectionProvider(builder.build());
157156
FailoverReporter reporter = new FailoverReporter();
158-
provider.setDatabaseSwitchListener(reporter);
159-
provider.setActiveDatabase(endpoint1.getHostAndPort());
160157

161-
UnifiedJedis client = new UnifiedJedis(provider);
158+
MultiDbClient multiDbClient = MultiDbClient.builder().multiDbConfig(builder.build())
159+
.databaseSwitchListener(reporter).build();
160+
161+
multiDbClient.setActiveDatabase(endpoint1.getHostAndPort());
162162

163163
AtomicLong retryingThreadsCounter = new AtomicLong(0);
164164
AtomicLong failedCommandsAfterFailover = new AtomicLong(0);
@@ -168,33 +168,32 @@ public void accept(DatabaseSwitchEvent e) {
168168
AtomicBoolean unexpectedErrors = new AtomicBoolean(false);
169169
AtomicReference<Exception> lastException = new AtomicReference<Exception>();
170170
AtomicLong stopRunningAt = new AtomicLong();
171-
String database2Id = provider.getDatabase(endpoint2.getHostAndPort()).getCircuitBreaker()
172-
.getName();
171+
Endpoint db2Endpoint = endpoint2.getHostAndPort();
173172

174-
// Start thread that imitates an application that uses the client
173+
// Start thread that imitates an application that uses the multiDbClient
175174
RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom().limitForPeriod(100)
176175
.limitRefreshPeriod(Duration.ofSeconds(1)).timeoutDuration(Duration.ofSeconds(1)).build();
177176

178-
MultiThreadedFakeApp fakeApp = new MultiThreadedFakeApp(client, (UnifiedJedis c) -> {
177+
MultiThreadedFakeApp fakeApp = new MultiThreadedFakeApp(multiDbClient, (UnifiedJedis c) -> {
179178

180179
long threadId = Thread.currentThread().getId();
181180

182181
int attempt = 0;
183182
int maxTries = 500;
184183
int retryingDelay = 5;
185-
String currentDatabaseId = null;
184+
String currentDbKey = null;
186185
while (true) {
187186
try {
188187
if (System.currentTimeMillis() > stopRunningAt.get()) break;
189-
currentDatabaseId = provider.getDatabase().getCircuitBreaker().getName();
188+
currentDbKey = dbKey(multiDbClient.getActiveDatabaseEndpoint());
190189
Map<String, String> executionInfo = new HashMap<String, String>() {
191190
{
192191
put("threadId", String.valueOf(threadId));
193192
put("database", reporter.getCurrentDatabaseName());
194193
}
195194
};
196195

197-
client.xadd("execution_log", StreamEntryID.NEW_ENTRY, executionInfo);
196+
multiDbClient.xadd("execution_log", StreamEntryID.NEW_ENTRY, executionInfo);
198197

199198
if (attempt > 0) {
200199
log.info("Thread {} recovered after {} ms. Threads still not recovered: {}", threadId,
@@ -203,7 +202,7 @@ public void accept(DatabaseSwitchEvent e) {
203202

204203
break;
205204
} catch (JedisConnectionException e) {
206-
if (database2Id.equals(currentDatabaseId)) {
205+
if (dbKey(db2Endpoint).equals(currentDbKey)) {
207206
break;
208207
}
209208
lastException.set(e);
@@ -231,7 +230,7 @@ public void accept(DatabaseSwitchEvent e) {
231230
}
232231
if (++attempt == maxTries) throw e;
233232
} catch (Exception e) {
234-
if (database2Id.equals(currentDatabaseId)) {
233+
if (dbKey(db2Endpoint).equals(currentDbKey)) {
235234
break;
236235
}
237236
lastException.set(e);
@@ -275,6 +274,7 @@ public boolean isCompleted(Duration checkInterval, Duration delayAfter, Duration
275274
}
276275
log.info("Fake app completed");
277276

277+
MultiDbConnectionProvider provider = ClientTestUtil.getConnectionProvider(multiDbClient);
278278
ConnectionPool pool = provider.getDatabase(endpoint1.getHostAndPort()).getConnectionPool();
279279

280280
log.info("First connection pool state: active: {}, idle: {}", pool.getNumActive(),
@@ -305,7 +305,11 @@ public boolean isCompleted(Duration checkInterval, Duration delayAfter, Duration
305305
}
306306
assertFalse(unexpectedErrors.get());
307307

308-
client.close();
308+
multiDbClient.close();
309+
}
310+
311+
private String dbKey(Endpoint endpoint) {
312+
return endpoint.getHost() + ":" + endpoint.getPort();
309313
}
310314

311315
private static void ensureEndpointAvailability(HostAndPort endpoint, JedisClientConfig config) {

src/test/java/redis/clients/jedis/mcf/DefaultValuesTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ void testDefaultValuesInConfig() {
3333
// check healthchecks enabled
3434
assertNotNull(databaseConfig.getHealthCheckStrategySupplier());
3535

36-
// check default healthcheck strategy is echo
37-
assertEquals(EchoStrategy.DEFAULT, databaseConfig.getHealthCheckStrategySupplier());
36+
// check default healthcheck strategy is PingStrategy
37+
assertEquals(PingStrategy.DEFAULT, databaseConfig.getHealthCheckStrategySupplier());
3838

3939
// check number of probes
4040
assertEquals(3,

0 commit comments

Comments
 (0)