package com.lambdaworks.redis.cluster.topology;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode;
import com.lambdaworks.redis.internal.LettuceAssert;
import com.lambdaworks.redis.internal.LettuceLists;
import com.lambdaworks.redis.internal.LettuceSets;
/**
* Comparators for {@link RedisClusterNode} and {@link RedisURI}.
*
* @author Mark Paluch
*/
public class TopologyComparators {
/**
* Sort partitions by a {@code fixedOrder} and by {@link RedisURI}. Nodes are sorted as provided in {@code fixedOrder}.
* {@link RedisURI RedisURIs}s not contained in {@code fixedOrder} are ordered after the fixed sorting and sorted wihin the
* block by comparing {@link RedisURI}.
*
* @param clusterNodes the sorting input
* @param fixedOrder the fixed order part
* @return List containing {@link RedisClusterNode}s ordered by {@code fixedOrder} and {@link RedisURI}
* @see #sortByUri(Iterable)
*/
public static List<RedisClusterNode> predefinedSort(Iterable<RedisClusterNode> clusterNodes,
Iterable<RedisURI> fixedOrder) {
LettuceAssert.notNull(clusterNodes, "Cluster nodes must not be null");
LettuceAssert.notNull(fixedOrder, "Fixed order must not be null");
List<RedisURI> fixedOrderList = LettuceLists.newList(fixedOrder);
List<RedisClusterNode> withOrderSpecification = LettuceLists.newList(clusterNodes)//
.stream()//
.filter(redisClusterNode -> fixedOrderList.contains(redisClusterNode.getUri()))//
.collect(Collectors.toList());
List<RedisClusterNode> withoutSpecification = LettuceLists.newList(clusterNodes)//
.stream()//
.filter(redisClusterNode -> !fixedOrderList.contains(redisClusterNode.getUri()))//
.collect(Collectors.toList());
Collections.sort(withOrderSpecification, new PredefinedRedisClusterNodeComparator(fixedOrderList));
Collections.sort(withoutSpecification, (o1, o2) -> RedisURIComparator.INSTANCE.compare(o1.getUri(), o2.getUri()));
withOrderSpecification.addAll(withoutSpecification);
return withOrderSpecification;
}
/**
* Sort partitions by RedisURI.
*
* @param clusterNodes
* @return List containing {@link RedisClusterNode}s ordered by {@link RedisURI}
*/
public static List<RedisClusterNode> sortByUri(Iterable<RedisClusterNode> clusterNodes) {
LettuceAssert.notNull(clusterNodes, "Cluster nodes must not be null");
List<RedisClusterNode> ordered = LettuceLists.newList(clusterNodes);
Collections.sort(ordered, (o1, o2) -> RedisURIComparator.INSTANCE.compare(o1.getUri(), o2.getUri()));
return ordered;
}
/**
* Sort partitions by client count.
*
* @param clusterNodes
* @return List containing {@link RedisClusterNode}s ordered by client count
*/
public static List<RedisClusterNode> sortByClientCount(Iterable<RedisClusterNode> clusterNodes) {
LettuceAssert.notNull(clusterNodes, "Cluster nodes must not be null");
List<RedisClusterNode> ordered = LettuceLists.newList(clusterNodes);
Collections.sort(ordered, ClientCountComparator.INSTANCE);
return ordered;
}
/**
* Sort partitions by latency.
*
* @param clusterNodes
* @return List containing {@link RedisClusterNode}s ordered by latency
*/
public static List<RedisClusterNode> sortByLatency(Iterable<RedisClusterNode> clusterNodes) {
List<RedisClusterNode> ordered = LettuceLists.newList(clusterNodes);
Collections.sort(ordered, LatencyComparator.INSTANCE);
return ordered;
}
/**
* Check if properties changed which are essential for cluster operations.
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return {@literal true} if {@code MASTER} or {@code SLAVE} flags changed or the responsible slots changed.
*/
public static boolean isChanged(Partitions o1, Partitions o2) {
if (o1.size() != o2.size()) {
return true;
}
for (RedisClusterNode base : o2) {
if (!essentiallyEqualsTo(base, o1.getPartitionByNodeId(base.getNodeId()))) {
return true;
}
}
return false;
}
/**
* Check for {@code MASTER} or {@code SLAVE} flags and whether the responsible slots changed.
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return {@literal true} if {@code MASTER} or {@code SLAVE} flags changed or the responsible slots changed.
*/
static boolean essentiallyEqualsTo(RedisClusterNode o1, RedisClusterNode o2) {
if (o2 == null) {
return false;
}
if (!sameFlags(o1, o2, RedisClusterNode.NodeFlag.MASTER)) {
return false;
}
if (!sameFlags(o1, o2, RedisClusterNode.NodeFlag.SLAVE)) {
return false;
}
if (!LettuceSets.newHashSet(o1.getSlots()).equals(LettuceSets.newHashSet(o2.getSlots()))) {
return false;
}
return true;
}
private static boolean sameFlags(RedisClusterNode base, RedisClusterNode other, RedisClusterNode.NodeFlag flag) {
if (base.getFlags().contains(flag)) {
if (!other.getFlags().contains(flag)) {
return false;
}
} else {
if (other.getFlags().contains(flag)) {
return false;
}
}
return true;
}
static class PredefinedRedisClusterNodeComparator implements Comparator<RedisClusterNode> {
private final List<RedisURI> fixedOrder;
public PredefinedRedisClusterNodeComparator(List<RedisURI> fixedOrder) {
this.fixedOrder = fixedOrder;
}
@Override
public int compare(RedisClusterNode o1, RedisClusterNode o2) {
int index1 = fixedOrder.indexOf(o1.getUri());
int index2 = fixedOrder.indexOf(o2.getUri());
return Integer.compare(index1, index2);
}
}
/**
* Compare {@link RedisClusterNodeSnapshot} based on their latency. Lowest comes first. Objects of type
* {@link RedisClusterNode} cannot be compared and yield to a result of {@literal 0}.
*/
enum LatencyComparator implements Comparator<RedisClusterNode> {
INSTANCE;
@Override
public int compare(RedisClusterNode o1, RedisClusterNode o2) {
if (o1 instanceof RedisClusterNodeSnapshot && o2 instanceof RedisClusterNodeSnapshot) {
RedisClusterNodeSnapshot w1 = (RedisClusterNodeSnapshot) o1;
RedisClusterNodeSnapshot w2 = (RedisClusterNodeSnapshot) o2;
if (w1.getLatencyNs() != null && w2.getLatencyNs() != null) {
return w1.getLatencyNs().compareTo(w2.getLatencyNs());
}
if (w1.getLatencyNs() != null && w2.getLatencyNs() == null) {
return -1;
}
if (w1.getLatencyNs() == null && w2.getLatencyNs() != null) {
return 1;
}
}
return 0;
}
}
/**
* Compare {@link RedisClusterNodeSnapshot} based on their client count. Lowest comes first. Objects of type
* {@link RedisClusterNode} cannot be compared and yield to a result of {@literal 0}.
*/
enum ClientCountComparator implements Comparator<RedisClusterNode> {
INSTANCE;
@Override
public int compare(RedisClusterNode o1, RedisClusterNode o2) {
if (o1 instanceof RedisClusterNodeSnapshot && o2 instanceof RedisClusterNodeSnapshot) {
RedisClusterNodeSnapshot w1 = (RedisClusterNodeSnapshot) o1;
RedisClusterNodeSnapshot w2 = (RedisClusterNodeSnapshot) o2;
if (w1.getConnectedClients() != null && w2.getConnectedClients() != null) {
return w1.getConnectedClients().compareTo(w2.getConnectedClients());
}
if (w1.getConnectedClients() == null && w2.getConnectedClients() != null) {
return 1;
}
if (w1.getConnectedClients() != null && w2.getConnectedClients() == null) {
return -1;
}
}
return 0;
}
}
/**
* Compare {@link RedisURI} based on their host and port representation.
*/
enum RedisURIComparator implements Comparator<RedisURI> {
INSTANCE;
@Override
public int compare(RedisURI o1, RedisURI o2) {
String h1 = "";
String h2 = "";
if (o1 != null) {
h1 = o1.getHost() + ":" + o1.getPort();
}
if (o2 != null) {
h2 = o2.getHost() + ":" + o2.getPort();
}
return h1.compareToIgnoreCase(h2);
}
}
}