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

com.lambdaworks.redis.masterslave.MasterSlave 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!
/*
 * Copyright 2011-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambdaworks.redis.masterslave;

import java.util.*;

import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.internal.LettuceAssert;
import com.lambdaworks.redis.internal.LettuceLists;
import com.lambdaworks.redis.models.role.RedisInstance;
import com.lambdaworks.redis.models.role.RedisNodeDescription;

import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * Master-Slave connection API.
 * 

* This API allows connections to Redis Master/Slave setups which run either Redis Standalone or are managed by Redis Sentinel. * Master-Slave connections can discover topologies and select a source for read operations using * {@link com.lambdaworks.redis.ReadFrom}. *

*

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

 *  @code
 *   RedisClient client = RedisClient.create();
 *   StatefulRedisMasterSlaveConnection connection = MasterSlave.connect(client,
 *                                                                      RedisURI.create("redis://localhost"),
 *                                                                      new Utf8StringCodec());
 *   // ...
 *
 *   connection.close();
 *   client.shutdown();
 *   }
 * 
*

*

Topology Discovery

*

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

    *
  • {@link MasterSlaveTopologyProvider}: Dynamic topology lookup using the {@code INFO REPLICATION} output. Slaves are listed * as {@code slaveN=...} entries. The initial connection can either point to a master or a slave and the topology provider will * discover nodes. The connection needs to be re-established outside of lettuce in a case of Master/Slave failover or topology * changes.
  • *
  • {@link StaticMasterSlaveTopologyProvider}: Topology is defined by the list of {@link RedisURI URIs} and the {@code ROLE} * output. MasterSlave 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/Slave 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/Slave failover is handled by lettuce.
  • *
* *

* Topology Updates *

*
    *
  • Standalone Master/Slave: 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
  • *
*

* * @author Mark Paluch * @since 4.1 */ public class MasterSlave { private static final InternalLogger LOG = InternalLoggerFactory.getInstance(MasterSlave.class); /** * Open a new connection to a Redis Master-Slave server/servers using the supplied {@link RedisURI} and the supplied * {@link RedisCodec codec} to encode/decode keys. *

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

* * @param redisClient the Redis client * @param codec Use this codec to encode/decode keys and values, must not be {@literal null} * @param redisURI the Redis server to connect to, must not be {@literal null} * @param Key type * @param Value type * @return A new connection */ public static StatefulRedisMasterSlaveConnection connect(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 connectSentinel(redisClient, codec, redisURI); } else { return connectMasterSlave(redisClient, codec, redisURI); } } /** * Open a new connection to a Redis Master-Slave server/servers using the supplied {@link RedisURI} and the supplied * {@link RedisCodec codec} to encode/decode keys. *

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

* * @param redisClient the Redis client * @param codec Use this codec to encode/decode keys and values, must not be {@literal null} * @param redisURIs the Redis server to connect to, must not be {@literal null} * @param Key type * @param Value type * @return A new connection */ public static StatefulRedisMasterSlaveConnection connect(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"); if (isSentinel(uriList.get(0))) { return connectSentinel(redisClient, codec, uriList.get(0)); } else { return connectStaticMasterSlave(redisClient, codec, uriList); } } private static StatefulRedisMasterSlaveConnection connectSentinel(RedisClient redisClient, RedisCodec codec, RedisURI redisURI) { TopologyProvider topologyProvider = new SentinelTopologyProvider(redisURI.getSentinelMasterId(), redisClient, redisURI); SentinelTopologyRefresh sentinelTopologyRefresh = new SentinelTopologyRefresh(redisClient, redisURI.getSentinelMasterId(), redisURI.getSentinels()); MasterSlaveTopologyRefresh refresh = new MasterSlaveTopologyRefresh(redisClient, topologyProvider); MasterSlaveConnectionProvider connectionProvider = new MasterSlaveConnectionProvider<>(redisClient, codec, redisURI, Collections.emptyMap()); connectionProvider.setKnownNodes(refresh.getNodes(redisURI)); MasterSlaveChannelWriter channelWriter = new MasterSlaveChannelWriter<>(connectionProvider); StatefulRedisMasterSlaveConnectionImpl connection = new StatefulRedisMasterSlaveConnectionImpl<>(channelWriter, codec, redisURI.getTimeout(), redisURI.getUnit()); Runnable runnable = () -> { try { LOG.debug("Refreshing topology"); List nodes = refresh.getNodes(redisURI); LOG.debug("New topology: {}", nodes); connectionProvider.setKnownNodes(nodes); } catch (Exception e) { LOG.error("Error during background refresh", e); } }; try { connection.registerCloseables(new ArrayList<>(), sentinelTopologyRefresh); sentinelTopologyRefresh.bind(runnable); } catch (RuntimeException e) { connection.close(); throw e; } return connection; } private static StatefulRedisMasterSlaveConnection connectMasterSlave(RedisClient redisClient, RedisCodec codec, RedisURI redisURI) { Map> initialConnections = new HashMap<>(); try { StatefulRedisConnection nodeConnection = redisClient.connect(codec, redisURI); initialConnections.put(redisURI, nodeConnection); TopologyProvider topologyProvider = new MasterSlaveTopologyProvider(nodeConnection, redisURI); List nodes = topologyProvider.getNodes(); RedisNodeDescription node = getConnectedNode(redisURI, nodes); if (node.getRole() != RedisInstance.Role.MASTER) { RedisNodeDescription master = lookupMaster(nodes); nodeConnection = redisClient.connect(codec, master.getUri()); initialConnections.put(master.getUri(), nodeConnection); topologyProvider = new MasterSlaveTopologyProvider(nodeConnection, master.getUri()); } MasterSlaveTopologyRefresh refresh = new MasterSlaveTopologyRefresh(redisClient, topologyProvider); MasterSlaveConnectionProvider connectionProvider = new MasterSlaveConnectionProvider<>(redisClient, codec, redisURI, initialConnections); connectionProvider.setKnownNodes(refresh.getNodes(redisURI)); MasterSlaveChannelWriter channelWriter = new MasterSlaveChannelWriter<>(connectionProvider); StatefulRedisMasterSlaveConnectionImpl connection = new StatefulRedisMasterSlaveConnectionImpl<>( channelWriter, codec, redisURI.getTimeout(), redisURI.getUnit()); return connection; } catch (RuntimeException e) { for (StatefulRedisConnection connection : initialConnections.values()) { connection.close(); } throw e; } } private static StatefulRedisMasterSlaveConnection connectStaticMasterSlave(RedisClient redisClient, RedisCodec codec, Iterable redisURIs) { Map> initialConnections = new HashMap<>(); try { TopologyProvider topologyProvider = new StaticMasterSlaveTopologyProvider(redisClient, redisURIs); RedisURI seedNode = redisURIs.iterator().next(); MasterSlaveTopologyRefresh refresh = new MasterSlaveTopologyRefresh(redisClient, topologyProvider); MasterSlaveConnectionProvider connectionProvider = new MasterSlaveConnectionProvider<>(redisClient, codec, seedNode, initialConnections); List nodes = refresh.getNodes(seedNode); if (nodes.isEmpty()) { throw new RedisException(String.format("Cannot determine topology from %s", redisURIs)); } connectionProvider.setKnownNodes(nodes); MasterSlaveChannelWriter channelWriter = new MasterSlaveChannelWriter<>(connectionProvider); StatefulRedisMasterSlaveConnectionImpl connection = new StatefulRedisMasterSlaveConnectionImpl<>( channelWriter, codec, seedNode.getTimeout(), seedNode.getUnit()); return connection; } catch (RuntimeException e) { for (StatefulRedisConnection connection : initialConnections.values()) { connection.close(); } throw e; } } private static RedisNodeDescription lookupMaster(List nodes) { Optional first = nodes.stream().filter(n -> n.getRole() == RedisInstance.Role.MASTER).findFirst(); return first.orElseThrow(() -> new IllegalStateException("Cannot lookup master from " + nodes)); } private static RedisNodeDescription getConnectedNode(RedisURI redisURI, List nodes) { Optional first = nodes.stream().filter(n -> equals(redisURI, n)).findFirst(); return first.orElseThrow( () -> new IllegalStateException("Cannot lookup node descriptor for connected node at " + redisURI)); } private static boolean equals(RedisURI redisURI, RedisNodeDescription node) { return node.getUri().getHost().equals(redisURI.getHost()) && node.getUri().getPort() == redisURI.getPort(); } private static boolean isSentinel(RedisURI redisURI) { return !redisURI.getSentinels().isEmpty(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy