MultiDbClient.java
package redis.clients.jedis;
import redis.clients.jedis.MultiDbConfig.DatabaseConfig;
import redis.clients.jedis.annots.Experimental;
import redis.clients.jedis.builders.MultiDbClientBuilder;
import redis.clients.jedis.csc.Cache;
import redis.clients.jedis.executors.CommandExecutor;
import redis.clients.jedis.mcf.MultiDbCommandExecutor;
import redis.clients.jedis.mcf.MultiDbPipeline;
import redis.clients.jedis.mcf.MultiDbTransaction;
import redis.clients.jedis.providers.ConnectionProvider;
import redis.clients.jedis.mcf.MultiDbConnectionProvider;
import java.util.Set;
/**
* MultiDbClient provides high-availability Redis connectivity with automatic failover and failback
* capabilities across multiple weighted endpoints.
* <p>
* This client extends UnifiedJedis to support resilient operations with:
* <ul>
* <li><strong>Multi-Endpoint Support:</strong> Configure multiple Redis endpoints with individual
* weights</li>
* <li><strong>Automatic Failover:</strong> Seamless switching to backup endpoints when primary
* becomes unavailable</li>
* <li><strong>Circuit Breaker Pattern:</strong> Built-in circuit breaker to prevent cascading
* failures</li>
* <li><strong>Weight-Based Selection:</strong> Intelligent endpoint selection based on configured
* weights</li>
* <li><strong>Health Monitoring:</strong> Continuous health checks with automatic failback to
* recovered endpoints</li>
* <li><strong>Retry Logic:</strong> Configurable retry mechanisms with exponential backoff</li>
* </ul>
* <p>
* <strong>Usage Example:</strong>
* </p>
*
* <pre>
* // Create multi-db client with multiple endpoints
* HostAndPort primary = new HostAndPort("localhost", 29379);
* HostAndPort secondary = new HostAndPort("localhost", 29380);
*
*
* MultiDbClient client = MultiDbClient.builder()
* .multiDbConfig(
* MultiDbConfig.builder()
* .database(
* DatabaseConfig.builder(
* primary,
* DefaultJedisClientConfig.builder().build())
* .weight(100.0f)
* .build())
* .database(DatabaseConfig.builder(
* secondary,
* DefaultJedisClientConfig.builder().build())
* .weight(50.0f).build())
* .failureDetector(MultiDbConfig.CircuitBreakerConfig.builder()
* .failureRateThreshold(50.0f)
* .build())
* .commandRetry(MultiDbConfig.RetryConfig.builder()
* .maxAttempts(3)
* .build())
* .build()
* )
* .databaseSwitchListener(event ->
* System.out.println("Switched to: " + event.getEndpoint()))
* .build();
*
* // Use like any other Jedis client
* client.set("key", "value");
* String value = client.get("key");
*
* // Automatic failover happens transparently
* client.close();
* </pre>
* <p>
* The client automatically handles endpoint failures and recoveries, providing transparent high
* availability for Redis operations. All standard Jedis operations are supported with the added
* resilience features.
* </p>
* @author Ivo Gaydazhiev
* @since 7.0.0
* @see MultiDbConnectionProvider
* @see MultiDbCommandExecutor
* @see MultiDbConfig
*/
@Experimental
public class MultiDbClient extends UnifiedJedis {
/**
* Creates a MultiDbClient with custom components.
* <p>
* This constructor allows full customization of the client components and is primarily used by
* the builder pattern for advanced configurations. For most use cases, prefer using
* {@link #builder()} to create instances.
* </p>
* @param commandExecutor the command executor (typically MultiDbCommandExecutor)
* @param connectionProvider the connection provider (typically MultiDbConnectionProvider)
* @param commandObjects the command objects
* @param redisProtocol the Redis protocol version
* @param cache the client-side cache (may be null)
*/
MultiDbClient(CommandExecutor commandExecutor, ConnectionProvider connectionProvider,
CommandObjects commandObjects, RedisProtocol redisProtocol, Cache cache) {
super(commandExecutor, connectionProvider, commandObjects, redisProtocol, cache);
}
/**
* Returns the underlying MultiDbConnectionProvider.
* <p>
* This provides access to multi-database specific operations like manual failover, health status
* monitoring, and database switch event handling.
* </p>
* @return the multi-db connection provider
* @throws ClassCastException if the provider is not a MultiDbConnectionProvider
*/
private MultiDbConnectionProvider getMultiDbConnectionProvider() {
return (MultiDbConnectionProvider) this.provider;
}
/**
* Manually switches to the specified endpoint.
* <p>
* This method allows manual failover to a specific endpoint, bypassing the automatic weight-based
* selection. The switch will only succeed if the target endpoint is healthy.
* </p>
* @param endpoint the endpoint to switch to
*/
public void setActiveDatabase(Endpoint endpoint) {
getMultiDbConnectionProvider().setActiveDatabase(endpoint);
}
/**
* Adds a pre-configured database configuration.
* <p>
* This method allows adding a fully configured DatabaseConfig instance, providing maximum
* flexibility for advanced configurations including custom health check strategies, connection
* pool settings, etc.
* </p>
* @param databaseConfig the pre-configured database configuration
*/
public void addDatabase(DatabaseConfig databaseConfig) {
getMultiDbConnectionProvider().add(databaseConfig);
}
/**
* Dynamically adds a new database endpoint to the multi-database client.
* <p>
* This allows adding new database endpoints at runtime without recreating the client. The new
* endpoint will be available for failover operations immediately after being added and passing
* health checks (if configured).
* </p>
* @param endpoint the Redis server endpoint
* @param weight the weight for this endpoint (higher values = higher priority)
* @param clientConfig the client configuration for this endpoint
* @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint already exists
*/
public void addDatabase(Endpoint endpoint, float weight, JedisClientConfig clientConfig) {
DatabaseConfig databaseConfig = DatabaseConfig.builder(endpoint, clientConfig).weight(weight)
.build();
getMultiDbConnectionProvider().add(databaseConfig);
}
/**
* Returns the set of all configured database endpoints.
* <p>
* This method provides a view of all database endpoints currently configured in the
* multi-database client. These are the endpoints that can be used for failover operations.
* </p>
* @return the set of all configured database endpoints
*/
public Set<Endpoint> getDatabaseEndpoints() {
return getMultiDbConnectionProvider().getEndpoints();
}
/**
* Returns the health status of the specified database.
* <p>
* This method provides the current health status of a specific endpoint.
* </p>
* @param endpoint the endpoint to check
* @return the health status of the endpoint
*/
public boolean isHealthy(Endpoint endpoint) {
return getMultiDbConnectionProvider().isHealthy(endpoint);
}
/**
* Dynamically removes a database endpoint from the multi-database client.
* <p>
* This allows removing database endpoints at runtime. If the removed endpoint is currently
* active, the client will automatically failover to the next available healthy endpoint based on
* weight priority.
* </p>
* @param endpoint the endpoint to remove
* @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint doesn't exist
* @throws redis.clients.jedis.exceptions.JedisException if removing the endpoint would leave no
* healthy databases available
*/
public void removeDatabase(Endpoint endpoint) {
getMultiDbConnectionProvider().remove(endpoint);
}
/**
* Forces the client to switch to a specific database for a duration.
* <p>
* This method forces the client to use the specified database endpoint and puts all other
* endpoints in a grace period, preventing automatic failover for the specified duration. This is
* useful for maintenance scenarios or testing specific database endpoints.
* </p>
* @param endpoint the endpoint to force as active
* @param forcedActiveDurationMs the duration in milliseconds to keep this endpoint forced
* @throws redis.clients.jedis.exceptions.JedisValidationException if the endpoint is not healthy
* or doesn't exist
*/
public void forceActiveDatabase(Endpoint endpoint, long forcedActiveDurationMs) {
getMultiDbConnectionProvider().forceActiveDatabase(endpoint, forcedActiveDurationMs);
}
/**
* Creates a new pipeline for batch operations with multi-db support.
* <p>
* The returned pipeline supports the same resilience features as the main client, including
* automatic failover during batch execution.
* </p>
* @return a new MultiDbPipeline instance
*/
@Override
public MultiDbPipeline pipelined() {
return new MultiDbPipeline(getMultiDbConnectionProvider(), commandObjects);
}
/**
* Creates a new transaction with multi-database support.
* <p>
* The returned transaction supports the same resilience features as the main client, including
* automatic failover during transaction execution.
* </p>
* @return a new MultiDbTransaction instance
*/
@Override
public MultiDbTransaction multi() {
return new MultiDbTransaction((MultiDbConnectionProvider) provider, true, commandObjects);
}
/**
* @param doMulti {@code false} should be set to enable manual WATCH, UNWATCH and MULTI
* @return transaction object
*/
@Override
public MultiDbTransaction transaction(boolean doMulti) {
if (provider == null) {
throw new IllegalStateException(
"It is not allowed to create Transaction from this " + getClass());
}
return new MultiDbTransaction(getMultiDbConnectionProvider(), doMulti, commandObjects);
}
/**
* Returns the currently active database endpoint.
* <p>
* The active endpoint is the one currently being used for all operations. It can change at any
* time due to health checks, failover, failback, or manual switching.
* </p>
* @return the active database endpoint
*/
public Endpoint getActiveDatabaseEndpoint() {
return getMultiDbConnectionProvider().getDatabase().getEndpoint();
}
/**
* Fluent builder for {@link MultiDbClient}.
* <p>
* Obtain an instance via {@link #builder()}.
* </p>
*/
public static class Builder extends MultiDbClientBuilder<MultiDbClient> {
@Override
protected MultiDbClient createClient() {
return new MultiDbClient(commandExecutor, connectionProvider, commandObjects,
clientConfig.getRedisProtocol(), cache);
}
}
/**
* Create a new builder for configuring MultiDbClient instances.
* @return a new {@link MultiDbClient.Builder} instance
*/
public static Builder builder() {
return new Builder();
}
}