@Override public ClusterTopology getTopology() { return new ClusterTopology(new LinkedHashSet<>(LettuceConverters.partitionsToClusterNodes(client.getPartitions()))); } }
@Override public void clusterForget(RedisClusterNode node) { Set<RedisClusterNode> nodes = new LinkedHashSet<>(topologyProvider.getTopology().getActiveMasterNodes()); RedisClusterNode nodeToRemove = topologyProvider.getTopology().lookup(node); nodes.remove(nodeToRemove); clusterCommandExecutor.executeCommandAsyncOnNodes( (JedisClusterCommandCallback<String>) client -> client.clusterForget(node.getId()), nodes); }
/** * Run {@link ClusterCommandCallback} on a random node. * * @param cmd must not be {@literal null}. * @return never {@literal null}. */ public <T> NodeResult<T> executeCommandOnArbitraryNode(ClusterCommandCallback<?, T> cmd) { Assert.notNull(cmd, "ClusterCommandCallback must not be null!"); List<RedisClusterNode> nodes = new ArrayList<>(getClusterTopology().getActiveNodes()); return executeCommandOnSingleNode(cmd, nodes.get(new Random().nextInt(nodes.size()))); }
@Override public void clusterReplicate(RedisClusterNode master, RedisClusterNode replica) { RedisClusterNode masterNode = topologyProvider.getTopology().lookup(master); clusterCommandExecutor.executeCommandOnSingleNode( (LettuceClusterCommandCallback<String>) client -> client.clusterReplicate(masterNode.getId()), replica); }
@Nullable <T> T execute(String command, byte[] key, Collection<byte[]> args, Function<Client, T> 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<T>) client -> JedisClientUtils .execute(command, EMPTY_2D_BYTE_ARRAY, commandArgs, () -> client, responseMapper), keyMaster).getValue(); }
/** * Get the {@link RedisClusterNode} matching matching either {@link RedisClusterNode#getHost() host} and * {@link RedisClusterNode#getPort() port} or {@link RedisClusterNode#getId() nodeId} * * @param node must not be {@literal null} * @return never {@literal null}. * @throws ClusterStateFailureException */ public RedisClusterNode lookup(RedisClusterNode node) { Assert.notNull(node, "RedisClusterNode must not be null!"); if (nodes.contains(node) && StringUtils.hasText(node.getHost()) && StringUtils.hasText(node.getId())) { return node; } if (StringUtils.hasText(node.getHost()) && node.getPort() != null) { return lookup(node.getHost(), node.getPort()); } if (StringUtils.hasText(node.getId())) { return lookup(node.getId()); } throw new ClusterStateFailureException( String.format("Could not find node at %s. Have you provided either host and port or the nodeId?", node)); }
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); } }
/** * @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); }
@Override public void migrate(byte[] key, RedisNode target, int dbIndex, @Nullable MigrateOption option, long timeout) { Assert.notNull(key, "Key must not be null!"); Assert.notNull(target, "Target node must not be null!"); int timeoutToUse = timeout <= Integer.MAX_VALUE ? (int) timeout : Integer.MAX_VALUE; RedisClusterNode node = connection.getTopologyProvider().getTopology().lookup(target.getHost(), target.getPort()); executeCommandOnSingleNode(client -> client.migrate(target.getHost(), target.getPort(), key, dbIndex, timeoutToUse), node); }
/** * @param key must not be {@literal null}. * @return {@literal null}. */ public Set<RedisClusterNode> getKeyServingNodes(byte[] key) { Assert.notNull(key, "Key must not be null for Cluster Node lookup."); return getSlotServingNodes(ClusterSlotHashUtil.calculateSlot(key)); } }
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; }
/** * 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 void clusterForget(RedisClusterNode node) { List<RedisClusterNode> nodes = new ArrayList<>(clusterGetNodes()); RedisClusterNode nodeToRemove = topologyProvider.getTopology().lookup(node); nodes.remove(nodeToRemove); this.clusterCommandExecutor.executeCommandAsyncOnNodes( (LettuceClusterCommandCallback<String>) client -> client.clusterForget(nodeToRemove.getId()), nodes); }
@Override public void clusterReplicate(RedisClusterNode master, RedisClusterNode replica) { RedisClusterNode masterNode = topologyProvider.getTopology().lookup(master); clusterCommandExecutor.executeCommandOnSingleNode( (JedisClusterCommandCallback<String>) client -> client.clusterReplicate(masterNode.getId()), replica); }
@Override public RedisClusterNode clusterGetNodeForSlot(int slot) { for (RedisClusterNode node : topologyProvider.getTopology().getSlotServingNodes(slot)) { if (node.isMaster()) { return node; } } return null; }
/** * 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()); }
@Override public Map<RedisClusterNode, Collection<RedisClusterNode>> clusterGetMasterSlaveMap() { List<NodeResult<Collection<RedisClusterNode>>> nodeResults = clusterCommandExecutor.executeCommandAsyncOnNodes( (LettuceClusterCommandCallback<Collection<RedisClusterNode>>) client -> Converters .toSetOfRedisClusterNodes(client.clusterSlaves(client.clusterMyId())), topologyProvider.getTopology().getActiveMasterNodes()).getResults(); Map<RedisClusterNode, Collection<RedisClusterNode>> result = new LinkedHashMap<>(); for (NodeResult<Collection<RedisClusterNode>> nodeResult : nodeResults) { result.put(nodeResult.getNode(), nodeResult.getValue()); } return result; }
private <T> Collection<Publisher<Tuple2<RedisClusterNode, T>>> executeOnAllNodesMany( Function<RedisClusterNode, Flux<T>> callback) { Set<RedisClusterNode> nodes = topologyProvider.getTopology().getNodes(); List<Publisher<Tuple2<RedisClusterNode, T>>> pipeline = new ArrayList<>(nodes.size()); for (RedisClusterNode redisClusterNode : nodes) { pipeline.add(callback.apply(redisClusterNode).map(p -> Tuples.of(redisClusterNode, p))); } return pipeline; }
@Override public Map<RedisClusterNode, Collection<RedisClusterNode>> clusterGetMasterSlaveMap() { List<NodeResult<Collection<RedisClusterNode>>> nodeResults = clusterCommandExecutor .executeCommandAsyncOnNodes((JedisClusterCommandCallback<Collection<RedisClusterNode>>) 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<RedisClusterNode, Collection<RedisClusterNode>> result = new LinkedHashMap<>(); for (NodeResult<Collection<RedisClusterNode>> nodeResult : nodeResults) { result.put(nodeResult.getNode(), nodeResult.getValue()); } return result; }
/** * Lookup node from the topology. * * @param node must not be {@literal null}. * @return never {@literal null}. * @throws IllegalArgumentException in case the node could not be resolved to a topology-known node */ private RedisClusterNode lookupNode(RedisClusterNode node) { try { return topologyProvider.getTopology().lookup(node); } catch (ClusterStateFailureException e) { throw new IllegalArgumentException(String.format("Node %s is unknown to cluster", node), e); } }