/** * 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 } }
/** * @param callback must not be {@literal null}. * @param nodes must not be {@literal null}. * @return never {@literal null}. * @throws ClusterCommandExecutionFailureException * @throws IllegalArgumentException in case the node could not be resolved to a topology-known node */ public <S, T> MultiNodeResult<T> executeCommandAsyncOnNodes(ClusterCommandCallback<S, T> callback, Iterable<RedisClusterNode> nodes) { Assert.notNull(callback, "Callback must not be null!"); Assert.notNull(nodes, "Nodes must not be null!"); List<RedisClusterNode> resolvedRedisClusterNodes = new ArrayList<>(); ClusterTopology topology = topologyProvider.getTopology(); for (RedisClusterNode node : nodes) { try { resolvedRedisClusterNodes.add(topology.lookup(node)); } catch (ClusterStateFailureException e) { throw new IllegalArgumentException(String.format("Node %s is unknown to cluster", node), e); } } Map<NodeExecution, Future<NodeResult<T>>> futures = new LinkedHashMap<>(); for (RedisClusterNode node : resolvedRedisClusterNodes) { futures.put(new NodeExecution(node), executor.submit(() -> executeCommandOnSingleNode(callback, node))); } return collectResults(futures); }
private <S, T> NodeResult<T> executeMultiKeyCommandOnSingleNode(MultiKeyClusterCommandCallback<S, T> cmd, RedisClusterNode node, byte[] key) { Assert.notNull(cmd, "MultiKeyCommandCallback must not be null!"); Assert.notNull(node, "RedisClusterNode must not be null!"); Assert.notNull(key, "Keys for execution must not be null!"); S client = this.resourceProvider.getResourceForSpecificNode(node); Assert.notNull(client, "Could not acquire resource for node. Is your cluster info up to date?"); try { return new NodeResult<>(node, cmd.doInCluster(client, key), key); } catch (RuntimeException ex) { RuntimeException translatedException = convertToDataAccessException(ex); throw translatedException != null ? translatedException : ex; } finally { this.resourceProvider.returnResourceForSpecificNode(node, client); } }
/** * Run {@link ClusterCommandCallback} on all reachable master nodes. * * @param cmd must not be {@literal null}. * @return never {@literal null}. * @throws ClusterCommandExecutionFailureException */ public <S, T> MultiNodeResult<T> executeCommandOnAllNodes(final ClusterCommandCallback<S, T> cmd) { return executeCommandAsyncOnNodes(cmd, getClusterTopology().getActiveMasterNodes()); }
/** * Run {@link MultiKeyClusterCommandCallback} with on a curated set of nodes serving one or more keys. * * @param cmd must not be {@literal null}. * @return never {@literal null}. * @throws ClusterCommandExecutionFailureException */ public <S, T> MultiNodeResult<T> executeMultiKeyCommand(MultiKeyClusterCommandCallback<S, T> cmd, Iterable<byte[]> keys) { Map<RedisClusterNode, PositionalKeys> nodeKeyMap = new HashMap<>(); int index = 0; for (byte[] key : keys) { for (RedisClusterNode node : getClusterTopology().getKeyServingNodes(key)) { nodeKeyMap.computeIfAbsent(node, val -> PositionalKeys.empty()).append(PositionalKey.of(key, index++)); } } Map<NodeExecution, Future<NodeResult<T>>> futures = new LinkedHashMap<>(); for (Entry<RedisClusterNode, PositionalKeys> entry : nodeKeyMap.entrySet()) { if (entry.getKey().isMaster()) { for (PositionalKey key : entry.getValue()) { futures.put(new NodeExecution(entry.getKey(), key), executor.submit(() -> executeMultiKeyCommandOnSingleNode(cmd, entry.getKey(), key.getBytes()))); } } } return collectResults(futures); }
@Override public List<byte[]> mGet(byte[]... keys) { Assert.notNull(keys, "Keys must not be null!"); Assert.noNullElements(keys, "Keys must not contain null elements!"); if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) { return connection.getCluster().mget(keys); } return connection.getClusterCommandExecutor() .executeMultiKeyCommand((JedisMultiKeyClusterCommandCallback<byte[]>) BinaryJedis::get, Arrays.asList(keys)) .resultsAsListSortBy(keys); }
@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<String>) client -> client.clusterMeet(node.getHost(), node.getPort())); }
@Override public List<byte[]> bRPop(int timeout, byte[]... keys) { Assert.notNull(keys, "Keys must not be null!"); Assert.noNullElements(keys, "Keys must not contain null elements!"); if (ClusterSlotHashUtil.isSameSlotForAllKeys(keys)) { return super.bRPop(timeout, keys); } List<KeyValue<byte[], byte[]>> resultList = connection.getClusterCommandExecutor().executeMultiKeyCommand( (LettuceMultiKeyClusterCommandCallback<KeyValue<byte[], byte[]>>) (client, key) -> client.brpop(timeout, key), Arrays.asList(keys)).resultsAsList(); for (KeyValue<byte[], byte[]> kv : resultList) { if (kv != null) { return LettuceConverters.toBytesList(kv); } } return Collections.emptyList(); }
private <S, T> NodeResult<T> executeCommandOnSingleNode(ClusterCommandCallback<S, T> cmd, RedisClusterNode node, int redirectCount) { Assert.notNull(cmd, "ClusterCommandCallback must not be null!"); Assert.notNull(node, "RedisClusterNode must not be null!"); if (redirectCount > maxRedirects) { throw new TooManyClusterRedirectionsException(String.format( "Cannot follow Cluster Redirects over more than %s legs. Please consider increasing the number of redirects to follow. Current value is: %s.", redirectCount, maxRedirects)); } RedisClusterNode nodeToUse = lookupNode(node); S client = this.resourceProvider.getResourceForSpecificNode(nodeToUse); Assert.notNull(client, "Could not acquire resource for node. Is your cluster info up to date?"); try { return new NodeResult<>(node, cmd.doInCluster(client)); } catch (RuntimeException ex) { RuntimeException translatedException = convertToDataAccessException(ex); if (translatedException instanceof ClusterRedirectException) { ClusterRedirectException cre = (ClusterRedirectException) translatedException; return executeCommandOnSingleNode(cmd, topologyProvider.getTopology().lookup(cre.getTargetHost(), cre.getTargetPort()), redirectCount + 1); } else { throw translatedException != null ? translatedException : ex; } } finally { this.resourceProvider.returnResourceForSpecificNode(nodeToUse, client); } }
@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<Object>) client -> JedisClientUtils.execute(command, EMPTY_2D_BYTE_ARRAY, args, () -> client)) .getValue(); }
public Set<byte[]> keys(RedisClusterNode node, byte[] pattern) { Assert.notNull(node, "RedisClusterNode must not be null!"); Assert.notNull(pattern, "Pattern must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode((JedisClusterCommandCallback<Set<byte[]>>) client -> client.keys(pattern), node) .getValue(); }
@Override public Set<byte[]> keys(byte[] pattern) { Assert.notNull(pattern, "Pattern must not be null!"); Collection<Set<byte[]>> keysPerNode = connection.getClusterCommandExecutor() .executeCommandOnAllNodes((JedisClusterCommandCallback<Set<byte[]>>) client -> client.keys(pattern)) .resultsAsList(); Set<byte[]> keys = new HashSet<>(); for (Set<byte[]> keySet : keysPerNode) { keys.addAll(keySet); } return keys; }
@Override public List<byte[]> bRPop(int timeout, byte[]... keys) { Assert.notNull(keys, "Key must not be null!"); Assert.noNullElements(keys, "Keys must not contain null elements!"); return connection.getClusterCommandExecutor() .executeMultiKeyCommand( (JedisMultiKeyClusterCommandCallback<List<byte[]>>) (client, key) -> client.brpop(timeout, key), Arrays.asList(keys)) .getFirstNonNullNotEmptyOrDefault(Collections.<byte[]> emptyList()); }
@Override public Long pTtl(byte[] key) { Assert.notNull(key, "Key must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode((JedisClusterCommandCallback<Long>) client -> client.pttl(key), connection.getTopologyProvider().getTopology().getKeyServingMasterNode(key)) .getValue(); }
@Override public void setConfig(String param, String value) { Assert.notNull(param, "Parameter must not be null!"); Assert.notNull(value, "Value must not be null!"); connection.getClusterCommandExecutor() .executeCommandOnAllNodes((JedisClusterCommandCallback<String>) client -> client.configSet(param, value)); }
public byte[] randomKey(RedisClusterNode node) { Assert.notNull(node, "RedisClusterNode must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode((JedisClusterCommandCallback<byte[]>) client -> client.randomBinaryKey(), node) .getValue(); }
@Nullable @Override public Long refcount(byte[] key) { Assert.notNull(key, "Key must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode((JedisClusterCommandCallback<Long>) client -> client.objectRefcount(key), connection.clusterGetNodeForKey(key)) .getValue(); }
@Nullable @Override public Duration idletime(byte[] key) { Assert.notNull(key, "Key must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode((JedisClusterCommandCallback<Long>) client -> client.objectIdletime(key), connection.clusterGetNodeForKey(key)) .mapValue(Converters::secondsToDuration); }
@Nullable @Override public ValueEncoding encodingOf(byte[] key) { Assert.notNull(key, "Key must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode((JedisClusterCommandCallback<byte[]>) client -> client.objectEncoding(key), connection.clusterGetNodeForKey(key)) .mapValue(JedisConverters::toEncoding); }
@Override public Properties getConfig(RedisClusterNode node, String pattern) { Assert.notNull(pattern, "Pattern must not be null!"); return connection.getClusterCommandExecutor() .executeCommandOnSingleNode( (JedisClusterCommandCallback<Properties>) client -> Converters.toProperties(client.configGet(pattern)), node) .getValue(); }