com.lambdaworks.redis.cluster.ClusterScanSupport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lettuce Show documentation
Show all versions of lettuce Show documentation
Advanced and thread-safe Java Redis client for synchronous, asynchronous, and
reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs
and much more.
/*
* Copyright 2011-2016 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 com.lambdaworks.redis.cluster;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Function;
import rx.Observable;
import rx.functions.Func1;
import com.lambdaworks.redis.*;
import com.lambdaworks.redis.cluster.api.StatefulRedisClusterConnection;
import com.lambdaworks.redis.cluster.models.partitions.RedisClusterNode;
import com.lambdaworks.redis.models.role.RedisNodeDescription;
/**
* Methods to support a Cluster-wide SCAN operation over multiple hosts.
*
* @author Mark Paluch
*/
class ClusterScanSupport {
/**
* Map a {@link RedisFuture} of {@link KeyScanCursor} to a {@link RedisFuture} of {@link ClusterKeyScanCursor}.
*/
static final ScanCursorMapper>> futureKeyScanCursorMapper = new ScanCursorMapper>>() {
@Override
public RedisFuture> map(List nodeIds, String currentNodeId,
RedisFuture> cursor) {
return new PipelinedRedisFuture<>(cursor, new Function, KeyScanCursor>() {
@Override
public KeyScanCursor apply(KeyScanCursor result) {
return new ClusterKeyScanCursor<>(nodeIds, currentNodeId, result);
}
});
}
};
/**
* Map a {@link RedisFuture} of {@link StreamScanCursor} to a {@link RedisFuture} of {@link ClusterStreamScanCursor}.
*/
static final ScanCursorMapper> futureStreamScanCursorMapper = new ScanCursorMapper>() {
@Override
public RedisFuture map(List nodeIds, String currentNodeId,
RedisFuture cursor) {
return new PipelinedRedisFuture<>(cursor, new Function() {
@Override
public StreamScanCursor apply(StreamScanCursor result) {
return new ClusterStreamScanCursor(nodeIds, currentNodeId, result);
}
});
}
};
/**
* Map a {@link Observable} of {@link KeyScanCursor} to a {@link Observable} of {@link ClusterKeyScanCursor}.
*/
final static ScanCursorMapper>> reactiveKeyScanCursorMapper = new ScanCursorMapper>>() {
@Override
public Observable> map(List nodeIds, String currentNodeId, Observable> cursor) {
return cursor.map(new Func1, KeyScanCursor>() {
@Override
public KeyScanCursor call(KeyScanCursor keyScanCursor) {
return new ClusterKeyScanCursor<>(nodeIds, currentNodeId, keyScanCursor);
}
});
}
};
/**
* Map a {@link Observable} of {@link StreamScanCursor} to a {@link Observable} of {@link ClusterStreamScanCursor}.
*/
final static ScanCursorMapper> reactiveStreamScanCursorMapper = new ScanCursorMapper>() {
@Override
public Observable map(List nodeIds, String currentNodeId, Observable cursor) {
return cursor.map(new Func1() {
@Override
public StreamScanCursor call(StreamScanCursor streamScanCursor) {
return new ClusterStreamScanCursor(nodeIds, currentNodeId, streamScanCursor);
}
});
}
};
/**
* Retrieve the cursor to continue the scan.
*
* @param scanCursor can be {@literal null}.
* @return
*/
static ScanCursor getContinuationCursor(ScanCursor scanCursor) {
if (ScanCursor.INITIAL.equals(scanCursor)) {
return scanCursor;
}
assertClusterScanCursor(scanCursor);
ClusterScanCursor clusterScanCursor = (ClusterScanCursor) scanCursor;
if (clusterScanCursor.isScanOnCurrentNodeFinished()) {
return ScanCursor.INITIAL;
}
return scanCursor;
}
static List getNodeIds(StatefulRedisClusterConnection connection, ScanCursor cursor) {
if (ScanCursor.INITIAL.equals(cursor)) {
List nodeIds = getNodeIds(connection);
assertHasNodes(nodeIds);
return nodeIds;
}
assertClusterScanCursor(cursor);
ClusterScanCursor clusterScanCursor = (ClusterScanCursor) cursor;
return clusterScanCursor.getNodeIds();
}
static String getCurrentNodeId(ScanCursor cursor, List nodeIds) {
if (ScanCursor.INITIAL.equals(cursor)) {
assertHasNodes(nodeIds);
return nodeIds.get(0);
}
assertClusterScanCursor(cursor);
return getNodeIdForNextScanIteration(nodeIds, (ClusterScanCursor) cursor);
}
/**
* Retrieve a list of node Ids to use for the SCAN operation.
*
* @param connection
* @return
*/
private static List getNodeIds(StatefulRedisClusterConnection connection) {
List nodeIds = new ArrayList<>();
PartitionAccessor partitionAccessor = new PartitionAccessor(connection.getPartitions());
for (RedisClusterNode redisClusterNode : partitionAccessor.getMasters()) {
if (connection.getReadFrom() != null) {
List readCandidates = (List) partitionAccessor.getReadCandidates(redisClusterNode);
List selection = connection.getReadFrom().select(new ReadFrom.Nodes() {
@Override
public List getNodes() {
return readCandidates;
}
@Override
public Iterator iterator() {
return readCandidates.iterator();
}
});
if (!selection.isEmpty()) {
RedisClusterNode selectedNode = (RedisClusterNode) selection.get(0);
nodeIds.add(selectedNode.getNodeId());
continue;
}
}
nodeIds.add(redisClusterNode.getNodeId());
}
return nodeIds;
}
private static String getNodeIdForNextScanIteration(List nodeIds, ClusterScanCursor clusterKeyScanCursor) {
if (clusterKeyScanCursor.isScanOnCurrentNodeFinished()) {
if (clusterKeyScanCursor.isFinished()) {
throw new IllegalStateException("Cluster scan is finished");
}
int nodeIndex = nodeIds.indexOf(clusterKeyScanCursor.getCurrentNodeId());
return nodeIds.get(nodeIndex + 1);
}
return clusterKeyScanCursor.getCurrentNodeId();
}
private static void assertClusterScanCursor(ScanCursor cursor) {
if (!(cursor instanceof ClusterScanCursor)) {
throw new IllegalArgumentException(
"A scan in Redis Cluster mode requires to reuse the resulting cursor from the previous scan invocation");
}
}
private static void assertHasNodes(List nodeIds) {
if (nodeIds.isEmpty()) {
throw new RedisException("No available nodes for a scan");
}
}
static ScanCursorMapper>> asyncClusterKeyScanCursorMapper() {
return (ScanCursorMapper) futureKeyScanCursorMapper;
}
static ScanCursorMapper> asyncClusterStreamScanCursorMapper() {
return futureStreamScanCursorMapper;
}
static ScanCursorMapper>> reactiveClusterKeyScanCursorMapper() {
return (ScanCursorMapper) reactiveKeyScanCursorMapper;
}
static ScanCursorMapper> reactiveClusterStreamScanCursorMapper() {
return reactiveStreamScanCursorMapper;
}
/**
* Mapper between the node operation cursor and the cluster scan cursor.
*
* @param
*/
interface ScanCursorMapper {
T map(List nodeIds, String currentNodeId, T cursor);
}
/**
* Marker for a cluster scan cursor.
*/
interface ClusterScanCursor {
List getNodeIds();
String getCurrentNodeId();
boolean isScanOnCurrentNodeFinished();
boolean isFinished();
}
/**
* State object for a cluster-wide SCAN using Key results.
*
* @param
*/
private static class ClusterKeyScanCursor extends KeyScanCursor implements ClusterScanCursor {
final List nodeIds;
final String currentNodeId;
final KeyScanCursor cursor;
public ClusterKeyScanCursor(List nodeIds, String currentNodeId, KeyScanCursor cursor) {
super();
this.nodeIds = nodeIds;
this.currentNodeId = currentNodeId;
this.cursor = cursor;
setCursor(cursor.getCursor());
getKeys().addAll(cursor.getKeys());
if (cursor.isFinished()) {
int nodeIndex = nodeIds.indexOf(currentNodeId);
if (nodeIndex == -1 || nodeIndex == nodeIds.size() - 1) {
setFinished(true);
}
}
}
@Override
public List getNodeIds() {
return nodeIds;
}
@Override
public String getCurrentNodeId() {
return currentNodeId;
}
public boolean isScanOnCurrentNodeFinished() {
return cursor.isFinished();
}
}
/**
* State object for a cluster-wide SCAN using streaming.
*/
private static class ClusterStreamScanCursor extends StreamScanCursor implements ClusterScanCursor {
final List nodeIds;
final String currentNodeId;
final StreamScanCursor cursor;
public ClusterStreamScanCursor(List nodeIds, String currentNodeId, StreamScanCursor cursor) {
super();
this.nodeIds = nodeIds;
this.currentNodeId = currentNodeId;
this.cursor = cursor;
setCursor(cursor.getCursor());
setCount(cursor.getCount());
if (cursor.isFinished()) {
int nodeIndex = nodeIds.indexOf(currentNodeId);
if (nodeIndex == -1 || nodeIndex == nodeIds.size() - 1) {
setFinished(true);
}
}
}
@Override
public List getNodeIds() {
return nodeIds;
}
@Override
public String getCurrentNodeId() {
return currentNodeId;
}
public boolean isScanOnCurrentNodeFinished() {
return cursor.isFinished();
}
}
}