Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.springframework.data.redis.connection.jedis.JedisClusterConnection Maven / Gradle / Ivy
/*
* Copyright 2015-2020 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
*
* https://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.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
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
* @author Tao Chen
* @author Chen Guanqun
* @author Pavel Khokhlov
* @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 ClusterTopologyProvider 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}. Uses {@link JedisClusterTopologyProvider} by default.
*
* @param cluster must not be {@literal null}.
* @param executor must not be {@literal null}.
*/
public JedisClusterConnection(JedisCluster cluster, ClusterCommandExecutor executor) {
this(cluster, executor, new JedisClusterTopologyProvider(cluster));
}
/**
* Create new {@link JedisClusterConnection} utilizing native connections via {@link JedisCluster} running commands
* across the cluster via given {@link ClusterCommandExecutor} and using the given {@link ClusterTopologyProvider}.
*
* @param cluster must not be {@literal null}.
* @param executor must not be {@literal null}.
* @param topologyProvider must not be {@literal null}.
* @since 2.2
*/
public JedisClusterConnection(JedisCluster cluster, ClusterCommandExecutor executor,
ClusterTopologyProvider topologyProvider) {
Assert.notNull(cluster, "JedisCluster must not be null.");
Assert.notNull(executor, "ClusterCommandExecutor must not be null.");
Assert.notNull(topologyProvider, "ClusterTopologyProvider must not be null.");
this.closed = false;
this.cluster = cluster;
this.topologyProvider = topologyProvider;
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 new JedisClusterScriptingCommands(this);
}
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("MULTI 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
* @author Mark Paluch
* @since 1.7
*/
public static class JedisClusterTopologyProvider implements ClusterTopologyProvider {
private final Object lock = new Object();
private final JedisCluster cluster;
private final long cacheTimeMs;
private long time = 0;
private @Nullable ClusterTopology cached;
/**
* Create new {@link JedisClusterTopologyProvider}. Uses a default cache timeout of 100 milliseconds.
*
* @param cluster must not be {@literal null}.
*/
public JedisClusterTopologyProvider(JedisCluster cluster) {
this(cluster, Duration.ofMillis(100));
}
/**
* Create new {@link JedisClusterTopologyProvider}.
*
* @param cluster must not be {@literal null}.
* @param cacheTimeout must not be {@literal null}.
* @since 2.2
*/
public JedisClusterTopologyProvider(JedisCluster cluster, Duration cacheTimeout) {
Assert.notNull(cluster, "JedisCluster must not be null!");
Assert.notNull(cacheTimeout, "Cache timeout must not be null!");
Assert.isTrue(!cacheTimeout.isNegative(), "Cache timeout must not be negative.");
this.cluster = cluster;
this.cacheTimeMs = cacheTimeout.toMillis();
}
/*
* (non-Javadoc)
* @see org.springframework.data.redis.connection.ClusterTopologyProvider#getTopology()
*/
@Override
public ClusterTopology getTopology() {
if (cached != null && shouldUseCachedValue()) {
return cached;
}
Map errors = new LinkedHashMap<>();
List> list = new ArrayList<>(cluster.getClusterNodes().entrySet());
Collections.shuffle(list);
for (Entry entry : list) {
try (Jedis 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);
}
}
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());
}
/**
* Returns whether {@link #getTopology()} should return the cached {@link ClusterTopology}. Uses a time-based
* caching.
*
* @return {@literal true} to use the cached {@link ClusterTopology}; {@literal false} to fetch a new cluster
* topology.
* @see #JedisClusterTopologyProvider(JedisCluster, Duration)
* @since 2.2
*/
protected boolean shouldUseCachedValue() {
return time + cacheTimeMs > System.currentTimeMillis();
}
}
protected JedisCluster getCluster() {
return cluster;
}
protected ClusterCommandExecutor getClusterCommandExecutor() {
return clusterCommandExecutor;
}
protected ClusterTopologyProvider getTopologyProvider() {
return topologyProvider;
}
}