docs/redis-client-components-overview.md
This document provides a high-level overview of the Jedis client architecture, focusing on RedisClient, RedisClusterClient, RedisSentinelClient, and the experimental MultiDbClient. The architecture is built on two core abstractions:
All client implementations extend UnifiedJedis, which delegates command execution to a CommandExecutor that uses a ConnectionProvider to obtain connections.
flowchart TB
subgraph ClientLayer["Client Layer"]
RC["RedisClient
(Standalone)"]
RCC["RedisClusterClient
(Cluster)"]
RSC["RedisSentinelClient
(Sentinel)"]
MDC["MultiDbClient
(Multi-DB Failover)"]
end
UJ["UnifiedJedis
- executor
- provider
- cache"]
CE["CommandExecutor"]
CP["ConnectionProvider"]
RC --> UJ
RCC --> UJ
RSC --> UJ
MDC --> UJ
UJ --> CE
UJ --> CP
Location: src/main/java/redis/clients/jedis/UnifiedJedis.java
Responsibilities:
CommandExecutorKey Fields:
protected final ConnectionProvider provider;
protected final CommandExecutor executor;
protected final CommandObjects commandObjects;
private final Cache cache;
Key Methods:
executor.executeCommand()close() - closes the executor (which closes the provider)Location: src/main/java/redis/clients/jedis/executors/CommandExecutor.java
public interface CommandExecutor extends AutoCloseable {
<T> T executeCommand(CommandObject<T> commandObject);
}
Location: src/main/java/redis/clients/jedis/executors/SimpleCommandExecutor.java
Characteristics:
Connection directlyUnifiedJedis(Connection) constructor (single fixed
connection). Note: Pipeline and Transaction do not use a CommandExecutor at
all — they queue commands directly on their Connection.Execution Flow:
executeCommand() → connection.executeCommand()
Location: src/main/java/redis/clients/jedis/executors/DefaultCommandExecutor.java
Characteristics:
ConnectionProvider to get connectionsExecution Flow:
executeCommand() → provider.getConnection() → connection.executeCommand() → close connection
Location: src/main/java/redis/clients/jedis/executors/RetryableCommandExecutor.java
Characteristics:
maxAttempts and maxTotalRetriesDurationJedisConnectionException with backoff (sleeps only after 2+
consecutive connection failures)UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration) constructor. Not currently created by
RedisClient's builder (see ClusterClientBuilder for builder-level retry
settings).Configuration:
RetryableCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration)
Execution Flow:
executeCommand()
├─ for (attempts = maxAttempts; attempts > 0; attempts--)
│ ├─ provider.getConnection()
│ ├─ connection.executeCommand()
│ └─ catch JedisConnectionException
│ ├─ log failure
│ ├─ backoff sleep (after 2+ consecutive connection failures)
│ └─ retry if deadline not exceeded
└─ throw if all attempts exhausted
Backoff Formula:
sleepMillis = millisLeft / (attemptsLeft * (attemptsLeft + 1))
Location: src/main/java/redis/clients/jedis/executors/ClusterCommandExecutor.java
Characteristics:
Configuration:
ClusterCommandExecutor(provider, maxAttempts, maxTotalRetriesDuration, flags)
Request Policies:
DEFAULT - Single-shard routing based on key hash slotALL_SHARDS - Broadcast to all primary nodesALL_NODES - Broadcast to all nodes (including replicas)MULTI_SHARD - Multi-key commands spanning shardsSPECIAL - Special handling (SCAN, FT.CURSOR, etc.)Execution Flow:
executeCommand()
├─ Determine request policy from command flags
├─ ALL_SHARDS → broadcastCommand(primaryOnly=true)
├─ ALL_NODES → broadcastCommand(primaryOnly=false)
└─ DEFAULT
├─ if keyless → executeKeylessCommand() (round-robin)
└─ else → doExecuteCommand() (slot-based routing)
├─ Calculate hash slot from key
├─ provider.getConnection(slot)
├─ connection.executeCommand()
└─ Handle MOVED/ASK redirections
├─ MOVED → update slot cache, retry
└─ ASK → send ASKING, execute on target
Key Features:
Location: src/main/java/redis/clients/jedis/mcf/MultiDbCommandExecutor.java
Characteristics:
Configuration:
MultiDbCommandExecutor(multiDbConnectionProvider)
Execution Flow:
executeCommand()
├─ database = provider.getDatabase() (active database)
├─ Decorate with Resilience4j:
│ ├─ withCircuitBreaker(database.circuitBreaker)
│ ├─ withRetry(database.retry)
│ └─ withFallback(exceptions, failoverHandler)
├─ Execute: handleExecuteCommand()
│ ├─ database.getConnection()
│ ├─ connection.executeCommand()
│ └─ catch exceptions tracked by circuit breaker
└─ On circuit breaker OPEN
├─ databaseFailover() (switch to next healthy database)
└─ retry on new database
Circuit Breaker States:
CLOSED - Normal operation, tracking failuresOPEN - Too many failures, reject requests, trigger failoverHALF_OPEN - Testing if database recoveredFailover Strategy:
Location: src/main/java/redis/clients/jedis/providers/ConnectionProvider.java
public interface ConnectionProvider extends AutoCloseable {
Connection getConnection();
Connection getConnection(CommandArguments args);
Map<?, ?> getConnectionMap();
Map<?, ?> getPrimaryNodesConnectionMap();
}
Location: src/main/java/redis/clients/jedis/providers/ManagedConnectionProvider.java
Characteristics:
Connection to UnifiedJedis
(advanced/custom wiring). Note: Pipeline and Transaction receive their
Connection directly, without a provider.Key Methods:
setConnection(Connection connection) // Set the managed connection
getConnection() → connection // Return the same connection
Location: src/main/java/redis/clients/jedis/providers/PooledConnectionProvider.java
Characteristics:
Configuration:
PooledConnectionProvider(hostAndPort, clientConfig, poolConfig)
Key Methods:
getConnection() → pool.getResource()
getConnection(args) → pool.getResource() // Ignores args for standalone
Pool Configuration:
maxTotal - Maximum connections in poolmaxIdle - Maximum idle connectionsminIdle - Minimum idle connectionstestOnBorrow - Validate connection before usetestWhileIdle - Validate idle connectionsLocation: src/main/java/redis/clients/jedis/providers/ClusterConnectionProvider.java
Characteristics:
Configuration:
ClusterConnectionProvider(clusterNodes, clientConfig, poolConfig)
Key Methods:
getConnection(CommandArguments args)
├─ Extract hash slots from keys
├─ Determine target slot
└─ getConnectionFromSlot(slot) → pool for that slot's primary node
getConnection(HostAndPort node)
└─ Get connection to specific node
getReplicaConnection(CommandArguments args)
└─ Get connection to replica for read operations
Slot Mapping:
slot → HostAndPort → ConnectionPoolslot = CRC16(key) % 16384{user}:123 → hash only userTopology Management:
CLUSTER SLOTS or CLUSTER NODESLocation: src/main/java/redis/clients/jedis/providers/SentineledConnectionProvider.java
Characteristics:
Configuration:
SentineledConnectionProvider(masterName, masterClientConfig, poolConfig,
sentinels, sentinelClientConfig)
Key Methods:
getConnection() → pool.getResource() // Connection to current master
getCurrentMaster() → HostAndPort // Current master endpoint
Failover Handling:
+switch-master eventsLocation: src/main/java/redis/clients/jedis/mcf/MultiDbConnectionProvider.java
Characteristics:
Configuration:
MultiDbConnectionProvider(multiDbConfig)
├─ DatabaseConfig[] - array of database endpoints
├─ RetryConfig - retry settings per database
├─ CircuitBreakerConfig - failure detection settings
├─ HealthCheckStrategy - PING or lag-aware
└─ Failback settings
Key Concepts:
Database:
TrackingConnectionPool - connection poolCircuitBreaker - failure trackingRetry - retry configurationweight - selection priority (higher weight preferred)HealthCheck - health monitoringActive Database Selection:
getDatabase() → activeDatabase (highest weight healthy database)
Failover Process:
1. Circuit breaker detects failures
2. Circuit breaker transitions to OPEN state
3. MultiDbCommandExecutor triggers failover
4. Select next healthy database by weight
5. Switch activeDatabase
6. Emit DatabaseSwitchEvent
7. Continue operations on new database
Failback Process:
1. Periodic health check (background thread)
2. Detect higher-priority database is healthy
3. Automatic failback to higher-priority database
4. Emit DatabaseSwitchEvent
Health Check Strategies:
PingStrategy - Simple PING commandLagAwareStrategy - Check replication lag (for active-active)Key Methods:
getDatabase() → Database // Get active database
getConnection() → activeDatabase.getConnection()
setActiveDatabase(endpoint) // Manual failover
setDatabaseSwitchListener(listener) // Listen to switch events
Location: src/main/java/redis/clients/jedis/RedisClient.java
Builder: StandaloneClientBuilder
Default Configuration:
ConnectionProvider: PooledConnectionProvider
CommandExecutor: DefaultCommandExecutor
Usage:
RedisClient client = RedisClient.builder()
.hostAndPort("localhost", 6379)
.clientConfig(DefaultJedisClientConfig.builder()
.password("secret")
.build())
.poolConfig(poolConfig)
.build();
String value = client.get("key");
client.close();
Retries: builder-level retry settings (maxAttempts,
maxTotalRetriesDuration) are currently exposed only on ClusterClientBuilder.
For standalone retries, construct UnifiedJedis(provider, maxAttempts, maxTotalRetriesDuration) directly, which wires a RetryableCommandExecutor.
Location: src/main/java/redis/clients/jedis/RedisClusterClient.java
Builder: ClusterClientBuilder
Default Configuration:
ConnectionProvider: ClusterConnectionProvider
CommandExecutor: ClusterCommandExecutor
Usage:
Set<HostAndPort> nodes = new HashSet<>();
nodes.add(new HostAndPort("localhost", 7000));
nodes.add(new HostAndPort("localhost", 7001));
RedisClusterClient client = RedisClusterClient.builder()
.nodes(nodes)
.maxAttempts(5)
.maxTotalRetriesDuration(Duration.ofSeconds(30))
.build();
String value = client.get("key");
client.close();
Cluster-Specific Features:
Location: src/main/java/redis/clients/jedis/RedisSentinelClient.java
Builder: SentinelClientBuilder
Default Configuration:
ConnectionProvider: SentineledConnectionProvider
CommandExecutor: DefaultCommandExecutor
Usage:
Set<HostAndPort> sentinels = new HashSet<>();
sentinels.add(new HostAndPort("localhost", 26379));
sentinels.add(new HostAndPort("localhost", 26380));
RedisSentinelClient client = RedisSentinelClient.builder()
.masterName("mymaster")
.sentinels(sentinels)
.build();
String value = client.get("key");
client.close();
Sentinel-Specific Features:
+switch-mastersentinelClientConfig(...) for connecting to the sentinel nodesLocation: src/main/java/redis/clients/jedis/MultiDbClient.java
Builder: MultiDbClientBuilder
Default Configuration:
ConnectionProvider: MultiDbConnectionProvider
CommandExecutor: MultiDbCommandExecutor
Usage:
DatabaseConfig primary = DatabaseConfig.builder(
new HostAndPort("primary.redis.com", 6379), clientConfig)
.weight(100.0f) // Highest priority
.build();
DatabaseConfig dr = DatabaseConfig.builder(
new HostAndPort("dr.redis.com", 6379), clientConfig)
.weight(50.0f) // Lower priority
.build();
MultiDbConfig multiDbConfig = MultiDbConfig.builder()
.database(primary)
.database(dr)
.failureDetector(CircuitBreakerConfig.builder()
.failureRateThreshold(50.0f)
.build())
.commandRetry(RetryConfig.builder()
.maxAttempts(3)
.build())
.failbackCheckInterval(30_000) // milliseconds
.build();
MultiDbClient client = MultiDbClient.builder()
.multiDbConfig(multiDbConfig)
.databaseSwitchListener(event -> {
System.out.println("Switched to: " + event.getEndpoint());
})
.build();
String value = client.get("key"); // Automatically fails over on errors
client.close();
Multi-DB Features:
Location: src/main/java/redis/clients/jedis/builders/AbstractClientBuilder.java
Common Configuration:
clientConfig - JedisClientConfig (auth, SSL, timeout, protocol)poolConfig - GenericObjectPoolConfig (pool settings)cacheConfig / cache - Client-side caching configurationconnectionProvider / commandExecutor - custom component overrides(Retry settings maxAttempts / maxTotalRetriesDuration are declared on
ClusterClientBuilder, not on the abstract base.)
Template Methods:
// Abstract — every builder must implement these
protected abstract T self();
protected abstract ConnectionProvider createDefaultConnectionProvider();
protected abstract C createClient();
protected abstract void validateSpecificConfiguration();
// Concrete — defaults to DefaultCommandExecutor; overridden by
// ClusterClientBuilder and MultiDbClientBuilder
protected CommandExecutor createDefaultCommandExecutor();
Builder Hierarchy:
AbstractClientBuilder
├─ StandaloneClientBuilder → RedisClient
├─ ClusterClientBuilder → RedisClusterClient
├─ SentinelClientBuilder → RedisSentinelClient
└─ MultiDbClientBuilder → MultiDbClient
client.get("mykey")
↓
UnifiedJedis.get("mykey")
↓
executor.executeCommand(CommandObject<String>)
↓
DefaultCommandExecutor.executeCommand()
├─ provider.getConnection()
│ ↓
│ PooledConnectionProvider.getConnection()
│ └─ pool.getResource() → Connection
├─ connection.executeCommand(GET mykey)
└─ connection.close() (return to pool)
↓
Return "value"
client.get("mykey")
↓
UnifiedJedis.get("mykey")
↓
executor.executeCommand(CommandObject<String>)
↓
RetryableCommandExecutor.executeCommand()
├─ Attempt 1:
│ ├─ provider.getConnection()
│ ├─ connection.executeCommand(GET mykey)
│ └─ JedisConnectionException thrown
├─ Backoff sleep (after 2+ consecutive connection failures)
├─ Attempt 2:
│ ├─ provider.getConnection()
│ ├─ connection.executeCommand(GET mykey)
│ └─ Success!
└─ Return "value"
client.get("mykey")
↓
UnifiedJedis.get("mykey")
↓
executor.executeCommand(CommandObject<String>)
↓
ClusterCommandExecutor.executeCommand()
├─ Calculate slot: CRC16("mykey") % 16384 = 14687
├─ provider.getConnection(args)
│ ↓
│ ClusterConnectionProvider.getConnection(args)
│ ├─ Extract slot from args: 14687
│ ├─ Lookup node for slot: 192.168.1.10:7000
│ └─ cache.getPool(node).getResource() → Connection
├─ connection.executeCommand(GET mykey)
├─ Receive MOVED 14687 192.168.1.11:7001
├─ Update slot cache
├─ provider.getConnection(192.168.1.11:7001)
├─ connection.executeCommand(GET mykey)
└─ Return "value"
client.get("mykey")
↓
UnifiedJedis.get("mykey")
↓
executor.executeCommand(CommandObject<String>)
↓
MultiDbCommandExecutor.executeCommand()
├─ database = provider.getDatabase() → primary (weight=1.0)
├─ Decorate with Resilience4j:
│ ├─ CircuitBreaker (state=CLOSED)
│ ├─ Retry (maxAttempts=3)
│ └─ Fallback (on circuit breaker OPEN)
├─ Attempt 1 on primary:
│ ├─ database.getConnection()
│ ├─ connection.executeCommand(GET mykey)
│ └─ JedisConnectionException (primary is down!)
├─ Circuit breaker records failure
├─ Retry attempt 2 on primary:
│ └─ JedisConnectionException
├─ Circuit breaker records failure
├─ Retry attempt 3 on primary:
│ └─ JedisConnectionException
├─ Circuit breaker transitions to OPEN
├─ Fallback triggered:
│ ├─ databaseFailover()
│ ├─ Select next healthy database → dr (weight=0.5)
│ ├─ setActiveDatabase(dr)
│ └─ Emit DatabaseSwitchEvent
├─ Recursive call: executeCommand() on dr
│ ├─ database.getConnection()
│ ├─ connection.executeCommand(GET mykey)
│ └─ Success!
└─ Return "value"