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

org.springframework.data.redis.connection.jedis.JedisClusterConnection Maven / Gradle / Ivy

There is a newer version: 3.2.5
Show newest version
/*
 * Copyright 2015-2018 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 org.springframework.data.redis.connection.jedis;

import redis.clients.jedis.BinaryJedis;
import redis.clients.jedis.BinaryJedisPubSub;
import redis.clients.jedis.Client;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisClusterConnectionHandler;
import redis.clients.jedis.JedisPool;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.PropertyAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.redis.ClusterStateFailureException;
import org.springframework.data.redis.ExceptionTranslationStrategy;
import org.springframework.data.redis.FallbackExceptionTranslationStrategy;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.*;
import org.springframework.data.redis.connection.ClusterCommandExecutor.ClusterCommandCallback;
import org.springframework.data.redis.connection.ClusterCommandExecutor.MultiKeyClusterCommandCallback;
import org.springframework.data.redis.connection.ClusterCommandExecutor.NodeResult;
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
import org.springframework.data.redis.connection.convert.Converters;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * {@link RedisClusterConnection} implementation on top of {@link JedisCluster}.
* Uses the native {@link JedisCluster} api where possible and falls back to direct node communication using * {@link Jedis} where needed. * * @author Christoph Strobl * @author Mark Paluch * @author Ninad Divadkar * @since 1.7 */ public class JedisClusterConnection implements DefaultedRedisClusterConnection { private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new FallbackExceptionTranslationStrategy( JedisConverters.exceptionConverter()); private static final byte[][] EMPTY_2D_BYTE_ARRAY = new byte[0][]; private final Log log = LogFactory.getLog(getClass()); private final JedisCluster cluster; private boolean closed; private final JedisClusterTopologyProvider topologyProvider; private ClusterCommandExecutor clusterCommandExecutor; private final boolean disposeClusterCommandExecutorOnClose; private volatile @Nullable JedisSubscription subscription; /** * Create new {@link JedisClusterConnection} utilizing native connections via {@link JedisCluster}. * * @param cluster must not be {@literal null}. */ public JedisClusterConnection(JedisCluster cluster) { Assert.notNull(cluster, "JedisCluster must not be null."); this.cluster = cluster; closed = false; topologyProvider = new JedisClusterTopologyProvider(cluster); clusterCommandExecutor = new ClusterCommandExecutor(topologyProvider, new JedisClusterNodeResourceProvider(cluster, topologyProvider), EXCEPTION_TRANSLATION); disposeClusterCommandExecutorOnClose = true; try { DirectFieldAccessor dfa = new DirectFieldAccessor(cluster); clusterCommandExecutor.setMaxRedirects((Integer) dfa.getPropertyValue("maxRedirections")); } catch (Exception e) { // ignore it and work with the executor default } } /** * Create new {@link JedisClusterConnection} utilizing native connections via {@link JedisCluster} running commands * across the cluster via given {@link ClusterCommandExecutor}. * * @param cluster must not be {@literal null}. * @param executor must not be {@literal null}. */ public JedisClusterConnection(JedisCluster cluster, ClusterCommandExecutor executor) { Assert.notNull(cluster, "JedisCluster must not be null."); Assert.notNull(executor, "ClusterCommandExecutor must not be null."); this.closed = false; this.cluster = cluster; this.topologyProvider = new JedisClusterTopologyProvider(cluster); this.clusterCommandExecutor = executor; this.disposeClusterCommandExecutorOnClose = false; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisCommands#execute(java.lang.String, byte[][]) */ @Nullable @Override public Object execute(String command, byte[]... args) { Assert.notNull(command, "Command must not be null!"); Assert.notNull(args, "Args must not be null!"); return clusterCommandExecutor .executeCommandOnArbitraryNode((JedisClusterCommandCallback) client -> JedisClientUtils.execute(command, EMPTY_2D_BYTE_ARRAY, args, () -> client)) .getValue(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterConnection#execute(String, byte[], java.util.Collection) */ @Nullable @Override public T execute(String command, byte[] key, Collection args) { return execute(command, key, args, it -> (T) it.getOne()); } @Nullable T execute(String command, byte[] key, Collection args, Function responseMapper) { Assert.notNull(command, "Command must not be null!"); Assert.notNull(key, "Key must not be null!"); Assert.notNull(args, "Args must not be null!"); byte[][] commandArgs = getCommandArguments(key, args); RedisClusterNode keyMaster = topologyProvider.getTopology().getKeyServingMasterNode(key); return clusterCommandExecutor.executeCommandOnSingleNode((JedisClusterCommandCallback) client -> JedisClientUtils .execute(command, EMPTY_2D_BYTE_ARRAY, commandArgs, () -> client, responseMapper), keyMaster).getValue(); } private static byte[][] getCommandArguments(byte[] key, Collection args) { byte[][] commandArgs = new byte[args.size() + 1][]; commandArgs[0] = key; int targetIndex = 1; for (byte[] binaryArgument : args) { commandArgs[targetIndex++] = binaryArgument; } return commandArgs; } /** * Execute the given command for each key in {@code keys} provided appending all {@code args} on each invocation. *
* This method, other than {@link #execute(String, byte[]...)}, dispatches the command to the {@code key} serving * master node and appends the {@code key} as first command argument to the {@code command}. {@code keys} are not * required to share the same slot for single-key commands. Multi-key commands carrying their keys in {@code args} * still require to share the same slot as the {@code key}. * *
	 * 
	 * // SET foo bar EX 10 NX
	 * execute("SET", "foo".getBytes(), asBinaryList("bar", "EX", 10, "NX"))
	 * 
	 * 
* * @param command must not be {@literal null}. * @param keys must not be {@literal null}. * @param args must not be {@literal null}. * @return command result as delivered by the underlying Redis driver. Can be {@literal null}. * @since 2.1 */ @Nullable public List execute(String command, Collection keys, Collection args) { Assert.notNull(command, "Command must not be null!"); Assert.notNull(keys, "Key must not be null!"); Assert.notNull(args, "Args must not be null!"); return clusterCommandExecutor.executeMultiKeyCommand((JedisMultiKeyClusterCommandCallback) (client, key) -> { return JedisClientUtils.execute(command, new byte[][] { key }, args.toArray(new byte[args.size()][]), () -> client); }, keys).resultsAsList(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#geoCommands() */ @Override public RedisGeoCommands geoCommands() { return new JedisClusterGeoCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#hashCommands() */ @Override public RedisHashCommands hashCommands() { return new JedisClusterHashCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#hyperLogLogCommands() */ @Override public RedisHyperLogLogCommands hyperLogLogCommands() { return new JedisClusterHyperLogLogCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#keyCommands() */ @Override public RedisKeyCommands keyCommands() { return doGetKeyCommands(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#stringCommands() */ @Override public RedisStringCommands stringCommands() { return new JedisClusterStringCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#listCommands() */ @Override public RedisListCommands listCommands() { return new JedisClusterListCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#setCommands() */ @Override public RedisSetCommands setCommands() { return new JedisClusterSetCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#zSetCommands() */ @Override public RedisZSetCommands zSetCommands() { return new JedisClusterZSetCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterConnection#serverCommands() */ @Override public RedisClusterServerCommands serverCommands() { return new JedisClusterServerCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#scriptingCommands() */ @Override public RedisScriptingCommands scriptingCommands() { return JedisClusterScriptingCommands.INSTANCE; } private JedisClusterKeyCommands doGetKeyCommands() { return new JedisClusterKeyCommands(this); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterConnection#keys(org.springframework.data.redis.connection.RedisClusterNode, byte[]) */ @Override public Set keys(RedisClusterNode node, byte[] pattern) { return doGetKeyCommands().keys(node, pattern); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterConnection#scan(org.springframework.data.redis.connection.RedisClusterNode, org.springframework.data.redis.core.ScanOptions) */ @Override public Cursor scan(RedisClusterNode node, ScanOptions options) { return doGetKeyCommands().scan(node, options); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterConnection#randomKey(org.springframework.data.redis.connection.RedisClusterNode) */ @Override public byte[] randomKey(RedisClusterNode node) { return doGetKeyCommands().randomKey(node); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisTxCommands#multi() */ @Override public void multi() { throw new InvalidDataAccessApiUsageException("MUTLI is currently not supported in cluster mode."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisTxCommands#exec() */ @Override public List exec() { throw new InvalidDataAccessApiUsageException("EXEC is currently not supported in cluster mode."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisTxCommands#discard() */ @Override public void discard() { throw new InvalidDataAccessApiUsageException("DISCARD is currently not supported in cluster mode."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisTxCommands#watch(byte[][]) */ @Override public void watch(byte[]... keys) { throw new InvalidDataAccessApiUsageException("WATCH is currently not supported in cluster mode."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisTxCommands#unwatch() */ @Override public void unwatch() { throw new InvalidDataAccessApiUsageException("UNWATCH is currently not supported in cluster mode."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisPubSubCommands#isSubscribed() */ @Override public boolean isSubscribed() { return (subscription != null && subscription.isAlive()); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisPubSubCommands#getSubscription() */ @Override public Subscription getSubscription() { return subscription; } @Override public Long publish(byte[] channel, byte[] message) { try { return cluster.publish(channel, message); } catch (Exception ex) { throw convertJedisAccessException(ex); } } @Override public void subscribe(MessageListener listener, byte[]... channels) { if (isSubscribed()) { throw new RedisSubscribedConnectionException( "Connection already subscribed; use the connection Subscription to cancel or add new channels"); } try { BinaryJedisPubSub jedisPubSub = new JedisMessageListener(listener); subscription = new JedisSubscription(listener, jedisPubSub, channels, null); cluster.subscribe(jedisPubSub, channels); } catch (Exception ex) { throw convertJedisAccessException(ex); } } @Override public void pSubscribe(MessageListener listener, byte[]... patterns) { if (isSubscribed()) { throw new RedisSubscribedConnectionException( "Connection already subscribed; use the connection Subscription to cancel or add new channels"); } try { BinaryJedisPubSub jedisPubSub = new JedisMessageListener(listener); subscription = new JedisSubscription(listener, jedisPubSub, null, patterns); cluster.psubscribe(jedisPubSub, patterns); } catch (Exception ex) { throw convertJedisAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnectionCommands#select(int) */ @Override public void select(int dbIndex) { if (dbIndex != 0) { throw new InvalidDataAccessApiUsageException("Cannot SELECT non zero index in cluster mode."); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnectionCommands#echo(byte[]) */ @Override public byte[] echo(byte[] message) { try { return cluster.echo(message); } catch (Exception ex) { throw convertJedisAccessException(ex); } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnectionCommands#ping() */ @Override public String ping() { return !clusterCommandExecutor.executeCommandOnAllNodes((JedisClusterCommandCallback) BinaryJedis::ping) .resultsAsList().isEmpty() ? "PONG" : null; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterConnection#ping(org.springframework.data.redis.connection.RedisClusterNode) */ @Override public String ping(RedisClusterNode node) { return clusterCommandExecutor .executeCommandOnSingleNode((JedisClusterCommandCallback) BinaryJedis::ping, node).getValue(); } /* * --> Cluster Commands */ /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterSetSlot(org.springframework.data.redis.connection.RedisClusterNode, int, org.springframework.data.redis.connection.RedisClusterCommands.AddSlots) */ @Override public void clusterSetSlot(RedisClusterNode node, int slot, AddSlots mode) { Assert.notNull(node, "Node must not be null."); Assert.notNull(mode, "AddSlots mode must not be null."); RedisClusterNode nodeToUse = topologyProvider.getTopology().lookup(node); String nodeId = nodeToUse.getId(); clusterCommandExecutor.executeCommandOnSingleNode((JedisClusterCommandCallback) client -> { switch (mode) { case IMPORTING: return client.clusterSetSlotImporting(slot, nodeId); case MIGRATING: return client.clusterSetSlotMigrating(slot, nodeId); case STABLE: return client.clusterSetSlotStable(slot); case NODE: return client.clusterSetSlotNode(slot, nodeId); } throw new IllegalArgumentException(String.format("Unknown AddSlots mode '%s'.", mode)); }, node); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetKeysInSlot(int, java.lang.Integer) */ @Override public List clusterGetKeysInSlot(int slot, Integer count) { RedisClusterNode node = clusterGetNodeForSlot(slot); clusterCommandExecutor .executeCommandOnSingleNode( (JedisClusterCommandCallback>) client -> JedisConverters.stringListToByteList() .convert(client.clusterGetKeysInSlot(slot, count != null ? count.intValue() : Integer.MAX_VALUE)), node); return null; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterAddSlots(org.springframework.data.redis.connection.RedisClusterNode, int[]) */ @Override public void clusterAddSlots(RedisClusterNode node, int... slots) { clusterCommandExecutor.executeCommandOnSingleNode( (JedisClusterCommandCallback) client -> client.clusterAddSlots(slots), node); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterAddSlots(org.springframework.data.redis.connection.RedisClusterNode, org.springframework.data.redis.connection.RedisClusterNode.SlotRange) */ @Override public void clusterAddSlots(RedisClusterNode node, SlotRange range) { Assert.notNull(range, "Range must not be null."); clusterAddSlots(node, range.getSlotsArray()); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterCountKeysInSlot(int) */ @Override public Long clusterCountKeysInSlot(int slot) { RedisClusterNode node = clusterGetNodeForSlot(slot); return clusterCommandExecutor.executeCommandOnSingleNode( (JedisClusterCommandCallback) client -> client.clusterCountKeysInSlot(slot), node).getValue(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterDeleteSlots(org.springframework.data.redis.connection.RedisClusterNode, int[]) */ @Override public void clusterDeleteSlots(RedisClusterNode node, int... slots) { clusterCommandExecutor.executeCommandOnSingleNode( (JedisClusterCommandCallback) client -> client.clusterDelSlots(slots), node); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterDeleteSlotsInRange(org.springframework.data.redis.connection.RedisClusterNode, org.springframework.data.redis.connection.RedisClusterNode.SlotRange) */ @Override public void clusterDeleteSlotsInRange(RedisClusterNode node, SlotRange range) { Assert.notNull(range, "Range must not be null."); clusterDeleteSlots(node, range.getSlotsArray()); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterForget(org.springframework.data.redis.connection.RedisClusterNode) */ @Override public void clusterForget(RedisClusterNode node) { Set nodes = new LinkedHashSet<>(topologyProvider.getTopology().getActiveMasterNodes()); RedisClusterNode nodeToRemove = topologyProvider.getTopology().lookup(node); nodes.remove(nodeToRemove); clusterCommandExecutor.executeCommandAsyncOnNodes( (JedisClusterCommandCallback) client -> client.clusterForget(node.getId()), nodes); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterMeet(org.springframework.data.redis.connection.RedisClusterNode) */ @Override public void clusterMeet(RedisClusterNode node) { Assert.notNull(node, "Cluster node must not be null for CLUSTER MEET command!"); Assert.hasText(node.getHost(), "Node to meet cluster must have a host!"); Assert.isTrue(node.getPort() > 0, "Node to meet cluster must have a port greater 0!"); clusterCommandExecutor.executeCommandOnAllNodes( (JedisClusterCommandCallback) client -> client.clusterMeet(node.getHost(), node.getPort())); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterReplicate(org.springframework.data.redis.connection.RedisClusterNode, org.springframework.data.redis.connection.RedisClusterNode) */ @Override public void clusterReplicate(RedisClusterNode master, RedisClusterNode replica) { RedisClusterNode masterNode = topologyProvider.getTopology().lookup(master); clusterCommandExecutor.executeCommandOnSingleNode( (JedisClusterCommandCallback) client -> client.clusterReplicate(masterNode.getId()), replica); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#getClusterSlotForKey(byte[]) */ @Override public Integer clusterGetSlotForKey(byte[] key) { return clusterCommandExecutor.executeCommandOnArbitraryNode((JedisClusterCommandCallback) client -> client .clusterKeySlot(JedisConverters.toString(key)).intValue()).getValue(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetNodeForSlot(int) */ @Override public RedisClusterNode clusterGetNodeForSlot(int slot) { for (RedisClusterNode node : topologyProvider.getTopology().getSlotServingNodes(slot)) { if (node.isMaster()) { return node; } } return null; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetNodes() */ @Override public Set clusterGetNodes() { return topologyProvider.getTopology().getNodes(); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetSlaves(org.springframework.data.redis.connection.RedisClusterNode) */ @Override public Set clusterGetSlaves(RedisClusterNode master) { Assert.notNull(master, "Master cannot be null!"); RedisClusterNode nodeToUse = topologyProvider.getTopology().lookup(master); return JedisConverters.toSetOfRedisClusterNodes(clusterCommandExecutor .executeCommandOnSingleNode( (JedisClusterCommandCallback>) client -> client.clusterSlaves(nodeToUse.getId()), master) .getValue()); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetMasterSlaveMap() */ @Override public Map> clusterGetMasterSlaveMap() { List>> nodeResults = clusterCommandExecutor .executeCommandAsyncOnNodes((JedisClusterCommandCallback>) client -> { // TODO: remove client.eval as soon as Jedis offers support for myid return JedisConverters.toSetOfRedisClusterNodes( client.clusterSlaves((String) client.eval("return redis.call('cluster', 'myid')", 0))); }, topologyProvider.getTopology().getActiveMasterNodes()).getResults(); Map> result = new LinkedHashMap<>(); for (NodeResult> nodeResult : nodeResults) { result.put(nodeResult.getNode(), nodeResult.getValue()); } return result; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetNodeForKey(byte[]) */ @Override public RedisClusterNode clusterGetNodeForKey(byte[] key) { return clusterGetNodeForSlot(clusterGetSlotForKey(key)); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisClusterCommands#clusterGetClusterInfo() */ @Override public ClusterInfo clusterGetClusterInfo() { return new ClusterInfo(JedisConverters.toProperties(clusterCommandExecutor .executeCommandOnArbitraryNode((JedisClusterCommandCallback) Jedis::clusterInfo).getValue())); } /* * --> Little helpers to make it work */ protected DataAccessException convertJedisAccessException(Exception ex) { DataAccessException translated = EXCEPTION_TRANSLATION.translate(ex); return translated != null ? translated : new RedisSystemException(ex.getMessage(), ex); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#close() */ @Override public void close() throws DataAccessException { if (!closed && disposeClusterCommandExecutorOnClose) { try { clusterCommandExecutor.destroy(); } catch (Exception ex) { log.warn("Cannot properly close cluster command executor", ex); } } closed = true; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#isClosed() */ @Override public boolean isClosed() { return closed; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#getNativeConnection() */ @Override public JedisCluster getNativeConnection() { return cluster; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#isQueueing() */ @Override public boolean isQueueing() { return false; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#isPipelined() */ @Override public boolean isPipelined() { return false; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#openPipeline() */ @Override public void openPipeline() { throw new UnsupportedOperationException("Pipeline is currently not supported for JedisClusterConnection."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#closePipeline() */ @Override public List closePipeline() throws RedisPipelineException { throw new UnsupportedOperationException("Pipeline is currently not supported for JedisClusterConnection."); } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.RedisConnection#getSentinelConnection() */ @Override public RedisSentinelConnection getSentinelConnection() { throw new UnsupportedOperationException("Sentinel is currently not supported for JedisClusterConnection."); } /** * {@link Jedis} specific {@link ClusterCommandCallback}. * * @author Christoph Strobl * @param * @since 1.7 */ protected interface JedisClusterCommandCallback extends ClusterCommandCallback {} /** * {@link Jedis} specific {@link MultiKeyClusterCommandCallback}. * * @author Christoph Strobl * @param * @since 1.7 */ protected interface JedisMultiKeyClusterCommandCallback extends MultiKeyClusterCommandCallback {} /** * Jedis specific implementation of {@link ClusterNodeResourceProvider}. * * @author Christoph Strobl * @author Mark Paluch * @since 1.7 */ static class JedisClusterNodeResourceProvider implements ClusterNodeResourceProvider { private final JedisCluster cluster; private final ClusterTopologyProvider topologyProvider; private final JedisClusterConnectionHandler connectionHandler; /** * Creates new {@link JedisClusterNodeResourceProvider}. * * @param cluster must not be {@literal null}. * @param topologyProvider must not be {@literal null}. */ JedisClusterNodeResourceProvider(JedisCluster cluster, ClusterTopologyProvider topologyProvider) { this.cluster = cluster; this.topologyProvider = topologyProvider; if (cluster != null) { PropertyAccessor accessor = new DirectFieldAccessFallbackBeanWrapper(cluster); this.connectionHandler = accessor.isReadableProperty("connectionHandler") ? (JedisClusterConnectionHandler) accessor.getPropertyValue("connectionHandler") : null; } else { this.connectionHandler = null; } } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ClusterNodeResourceProvider#getResourceForSpecificNode(org.springframework.data.redis.connection.RedisClusterNode) */ @Override @SuppressWarnings("unchecked") public Jedis getResourceForSpecificNode(RedisClusterNode node) { Assert.notNull(node, "Cannot get Pool for 'null' node!"); JedisPool pool = getResourcePoolForSpecificNode(node); if (pool != null) { return pool.getResource(); } Jedis connection = getConnectionForSpecificNode(node); if (connection != null) { return connection; } throw new DataAccessResourceFailureException(String.format("Node %s is unknown to cluster", node)); } private JedisPool getResourcePoolForSpecificNode(RedisClusterNode node) { Map clusterNodes = cluster.getClusterNodes(); if (clusterNodes.containsKey(node.asString())) { return clusterNodes.get(node.asString()); } return null; } private Jedis getConnectionForSpecificNode(RedisClusterNode node) { RedisClusterNode member = topologyProvider.getTopology().lookup(node); if (member != null && connectionHandler != null) { return connectionHandler.getConnectionFromNode(new HostAndPort(member.getHost(), member.getPort())); } return null; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ClusterNodeResourceProvider#returnResourceForSpecificNode(org.springframework.data.redis.connection.RedisClusterNode, java.lang.Object) */ @Override public void returnResourceForSpecificNode(RedisClusterNode node, Object client) { ((Jedis) client).close(); } } /** * Jedis specific implementation of {@link ClusterTopologyProvider}. * * @author Christoph Strobl * @since 1.7 */ static class JedisClusterTopologyProvider implements ClusterTopologyProvider { private final Object lock = new Object(); private final JedisCluster cluster; private long time = 0; private @Nullable ClusterTopology cached; /** * Create new {@link JedisClusterTopologyProvider}.s * * @param cluster */ public JedisClusterTopologyProvider(JedisCluster cluster) { this.cluster = cluster; } /* * (non-Javadoc) * @see org.springframework.data.redis.connection.ClusterTopologyProvider#getTopology() */ @Override public ClusterTopology getTopology() { if (cached != null && time + 100 > System.currentTimeMillis()) { return cached; } Map errors = new LinkedHashMap<>(); for (Entry entry : cluster.getClusterNodes().entrySet()) { Jedis jedis = null; try { jedis = entry.getValue().getResource(); time = System.currentTimeMillis(); Set nodes = Converters.toSetOfRedisClusterNodes(jedis.clusterNodes()); synchronized (lock) { cached = new ClusterTopology(nodes); } return cached; } catch (Exception ex) { errors.put(entry.getKey(), ex); } finally { if (jedis != null) { jedis.close(); } } } StringBuilder sb = new StringBuilder(); for (Entry entry : errors.entrySet()) { sb.append(String.format("\r\n\t- %s failed: %s", entry.getKey(), entry.getValue().getMessage())); } throw new ClusterStateFailureException( "Could not retrieve cluster information. CLUSTER NODES returned with error." + sb.toString()); } } protected JedisCluster getCluster() { return cluster; } protected ClusterCommandExecutor getClusterCommandExecutor() { return clusterCommandExecutor; } protected JedisClusterTopologyProvider getTopologyProvider() { return topologyProvider; } }