All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.lambdaworks.redis.cluster.ClusterScanSupport Maven / Gradle / Ivy

Go to download

Advanced and thread-safe Java Redis client for synchronous, asynchronous, and reactive usage. Supports Cluster, Sentinel, Pipelining, Auto-Reconnect, Codecs and much more.

There is a newer version: 5.0.0.Beta1
Show newest version
/*
 * 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();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy