All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.lettuce.core.masterreplica.MasterReplica Maven / Gradle / Ivy

Go to download

Advanced and thread-safe Java Redis client for synchronous, asynchronous, and reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs and much more.

The newest version!
package io.lettuce.core.masterreplica;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

import io.lettuce.core.ConnectionFuture;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisConnectionException;
import io.lettuce.core.RedisURI;
import io.lettuce.core.codec.RedisCodec;
import io.lettuce.core.internal.Futures;
import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.internal.LettuceLists;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * Master-Replica connection API.
 * 

* This API allows connections to Redis Master/Replica setups which run either in a static Master/Replica setup or are managed * by Redis Sentinel. Master-Replica connections can discover topologies and select a source for read operations using * {@link io.lettuce.core.ReadFrom}. *

*

* * Connections can be obtained by providing the {@link RedisClient}, a {@link RedisURI} and a {@link RedisCodec}. * *

 * RedisClient client = RedisClient.create();
 * StatefulRedisMasterReplicaConnection<String, String> connection = MasterReplica.connect(client,
 *         RedisURI.create("redis://localhost"), StringCodec.UTF8);
 * // ...
 *
 * connection.close();
 * client.shutdown();
 * 
* *

*

Topology Discovery

*

* Master-Replica topologies are either static or semi-static. Redis Standalone instances with attached replicas provide no * failover/HA mechanism. Redis Sentinel managed instances are controlled by Redis Sentinel and allow failover (which include * master promotion). The {@link MasterReplica} API supports both mechanisms. The topology is provided by a * {@link TopologyProvider}: * *

    *
  • {@link ReplicaTopologyProvider}: Dynamic topology lookup using the {@code INFO REPLICATION} output. Replicas are listed * as {@code replicaN=...} entries. The initial connection can either point to a master or a replica and the topology provider * will discover nodes. The connection needs to be re-established outside of lettuce in a case of Master/Replica failover or * topology changes.
  • *
  • {@link StaticMasterReplicaTopologyProvider}: Topology is defined by the list of {@link RedisURI URIs} and the * {@code ROLE} output. MasterReplica uses only the supplied nodes and won't discover additional nodes in the setup. The * connection needs to be re-established outside of lettuce in a case of Master/Replica failover or topology changes.
  • *
  • {@link SentinelTopologyProvider}: Dynamic topology lookup using the Redis Sentinel API. In particular, * {@code SENTINEL MASTER} and {@code SENTINEL SLAVES} output. Master/Replica failover is handled by lettuce.
  • *
* *

Topology Updates

*
    *
  • Standalone Master/Replica: Performs a one-time topology lookup which remains static afterward
  • *
  • Redis Sentinel: Subscribes to all Sentinels and listens for Pub/Sub messages to trigger topology refreshing
  • *
* *

Connection Fault-Tolerance

Connecting to Master/Replica bears the possibility that individual nodes are not * reachable. {@link MasterReplica} can still connect to a partially-available set of nodes. * *
    *
  • Redis Sentinel: At least one Sentinel must be reachable, the masterId must be registered and at least one host must be * available (master or replica). Allows for runtime-recovery based on Sentinel Events.
  • *
  • Static Setup (auto-discovery): The initial endpoint must be reachable. No recovery/reconfiguration during runtime.
  • *
  • Static Setup (provided hosts): All endpoints must be reachable. No recovery/reconfiguration during runtime.
  • *
* * @author Mark Paluch * @since 5.2 */ public class MasterReplica { /** * Open a new connection to a Redis Master-Replica server/servers using the supplied {@link RedisURI} and the supplied * {@link RedisCodec codec} to encode/decode keys. *

* This {@link MasterReplica} performs auto-discovery of nodes using either Redis Sentinel or Master/Replica. A * {@link RedisURI} can point to either a master or a replica host. *

* * @param redisClient the Redis client. * @param codec Use this codec to encode/decode keys and values, must not be {@code null}. * @param redisURI the Redis server to connect to, must not be {@code null}. * @param Key type. * @param Value type. * @return a new connection. */ public static StatefulRedisMasterReplicaConnection connect(RedisClient redisClient, RedisCodec codec, RedisURI redisURI) { return getConnection(connectAsyncSentinelOrAutodiscovery(redisClient, codec, redisURI), redisURI); } /** * Open asynchronously a new connection to a Redis Master-Replica server/servers using the supplied {@link RedisURI} and the * supplied {@link RedisCodec codec} to encode/decode keys. *

* This {@link MasterReplica} performs auto-discovery of nodes using either Redis Sentinel or Master/Replica. A * {@link RedisURI} can point to either a master or a replica host. *

* * @param redisClient the Redis client. * @param codec Use this codec to encode/decode keys and values, must not be {@code null}. * @param redisURI the Redis server to connect to, must not be {@code null}. * @param Key type. * @param Value type. * @return {@link CompletableFuture} that is notified once the connect is finished. * @since 6.0 */ public static CompletableFuture> connectAsync(RedisClient redisClient, RedisCodec codec, RedisURI redisURI) { return transformAsyncConnectionException(connectAsyncSentinelOrAutodiscovery(redisClient, codec, redisURI), redisURI); } private static CompletableFuture> connectAsyncSentinelOrAutodiscovery( RedisClient redisClient, RedisCodec codec, RedisURI redisURI) { LettuceAssert.notNull(redisClient, "RedisClient must not be null"); LettuceAssert.notNull(codec, "RedisCodec must not be null"); LettuceAssert.notNull(redisURI, "RedisURI must not be null"); if (isSentinel(redisURI)) { return new SentinelConnector<>(redisClient, codec, redisURI).connectAsync(); } return new AutodiscoveryConnector<>(redisClient, codec, redisURI).connectAsync(); } /** * Open a new connection to a Redis Master-Replica server/servers using the supplied {@link RedisURI} and the supplied * {@link RedisCodec codec} to encode/decode keys. *

* This {@link MasterReplica} performs auto-discovery of nodes if the URI is a Redis Sentinel URI. Master/Replica URIs will * be treated as static topology and no additional hosts are discovered in such case. Redis Standalone Master/Replica will * discover the roles of the supplied {@link RedisURI URIs} and issue commands to the appropriate node. *

*

* When using Redis Sentinel, ensure that {@link Iterable redisURIs} contains only a single entry as only the first URI is * considered. {@link RedisURI} pointing to multiple Sentinels can be configured through * {@link RedisURI.Builder#withSentinel}. *

* * @param redisClient the Redis client. * @param codec Use this codec to encode/decode keys and values, must not be {@code null}. * @param redisURIs the Redis server(s) to connect to, must not be {@code null}. * @param Key type. * @param Value type. * @return a new connection. * @since 6.0 */ public static StatefulRedisMasterReplicaConnection connect(RedisClient redisClient, RedisCodec codec, Iterable redisURIs) { return getConnection(connectAsyncSentinelOrStaticSetup(redisClient, codec, redisURIs), redisURIs); } /** * Open asynchronously a new connection to a Redis Master-Replica server/servers using the supplied {@link RedisURI} and the * supplied {@link RedisCodec codec} to encode/decode keys. *

* This {@link MasterReplica} performs auto-discovery of nodes if the URI is a Redis Sentinel URI. Master/Replica URIs will * be treated as static topology and no additional hosts are discovered in such case. Redis Standalone Master/Replica will * discover the roles of the supplied {@link RedisURI URIs} and issue commands to the appropriate node. *

*

* When using Redis Sentinel, ensure that {@link Iterable redisURIs} contains only a single entry as only the first URI is * considered. {@link RedisURI} pointing to multiple Sentinels can be configured through * {@link RedisURI.Builder#withSentinel}. *

* * @param redisClient the Redis client. * @param codec Use this codec to encode/decode keys and values, must not be {@code null}. * @param redisURIs the Redis server(s) to connect to, must not be {@code null}. * @param Key type. * @param Value type. * @return {@link CompletableFuture} that is notified once the connect is finished. * @since 6.0 */ public static CompletableFuture> connectAsync(RedisClient redisClient, RedisCodec codec, Iterable redisURIs) { return transformAsyncConnectionException(connectAsyncSentinelOrStaticSetup(redisClient, codec, redisURIs), redisURIs); } private static CompletableFuture> connectAsyncSentinelOrStaticSetup( RedisClient redisClient, RedisCodec codec, Iterable redisURIs) { LettuceAssert.notNull(redisClient, "RedisClient must not be null"); LettuceAssert.notNull(codec, "RedisCodec must not be null"); LettuceAssert.notNull(redisURIs, "RedisURIs must not be null"); List uriList = LettuceLists.newList(redisURIs); LettuceAssert.isTrue(!uriList.isEmpty(), "RedisURIs must not be empty"); RedisURI first = uriList.get(0); if (isSentinel(first)) { if (uriList.size() > 1) { InternalLogger logger = InternalLoggerFactory.getInstance(MasterReplica.class); logger.warn( "RedisURIs contains multiple endpoints of which the first is configured for Sentinel usage. Using only the first URI [{}] without considering the remaining URIs. Make sure to include all Sentinel endpoints in a single RedisURI.", first); } return new SentinelConnector<>(redisClient, codec, first).connectAsync(); } return new StaticMasterReplicaConnector<>(redisClient, codec, uriList).connectAsync(); } private static boolean isSentinel(RedisURI redisURI) { return !redisURI.getSentinels().isEmpty(); } /** * Retrieve the connection from {@link ConnectionFuture}. Performs a blocking {@link ConnectionFuture#get()} to synchronize * the channel/connection initialization. Any exception is rethrown as {@link RedisConnectionException}. * * @param connectionFuture must not be null. * @param context context information (single RedisURI, multiple URIs), used as connection target in the reported exception. * @param Connection type. * @return the connection. * @throws RedisConnectionException in case of connection failures. */ private static T getConnection(CompletableFuture connectionFuture, Object context) { try { return connectionFuture.get(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw RedisConnectionException.create(context.toString(), e); } catch (Exception e) { if (e instanceof ExecutionException) { // filter intermediate RedisConnectionException exceptions that bloat the stack trace if (e.getCause() instanceof RedisConnectionException && e.getCause().getCause() instanceof RedisConnectionException) { throw RedisConnectionException.create(context.toString(), e.getCause().getCause()); } throw RedisConnectionException.create(context.toString(), e.getCause()); } throw RedisConnectionException.create(context.toString(), e); } } private static CompletableFuture transformAsyncConnectionException(CompletionStage future, Object context) { return ConnectionFuture.from(null, future.toCompletableFuture()).thenCompose((v, e) -> { if (e != null) { // filter intermediate RedisConnectionException exceptions that bloat the stack trace if (e.getCause() instanceof RedisConnectionException && e.getCause().getCause() instanceof RedisConnectionException) { return Futures.failed(RedisConnectionException.create(context.toString(), e.getCause())); } return Futures.failed(RedisConnectionException.create(context.toString(), e)); } return CompletableFuture.completedFuture(v); }).toCompletableFuture(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy