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

com.lambdaworks.redis.masterslave.MasterSlaveTopologyRefresh 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.

The 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.masterslave;

import static com.lambdaworks.redis.masterslave.MasterSlaveUtils.findNodeByUri;

import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisCommandInterruptedException;
import com.lambdaworks.redis.RedisFuture;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.models.role.RedisNodeDescription;
import com.lambdaworks.redis.output.StatusOutput;
import com.lambdaworks.redis.protocol.*;

import io.netty.buffer.ByteBuf;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

/**
 * Utility to refresh the Master-Slave topology view based on {@link RedisNodeDescription}.
 * 
 * @author Mark Paluch
 */
class MasterSlaveTopologyRefresh {

    private static final InternalLogger logger = InternalLoggerFactory.getInstance(MasterSlaveTopologyRefresh.class);

    private final RedisClient client;
    private final TopologyProvider topologyProvider;

    public MasterSlaveTopologyRefresh(RedisClient client, TopologyProvider topologyProvider) {
        this.client = client;
        this.topologyProvider = topologyProvider;
    }

    /**
     * Load master slave nodes. Result contains an ordered list of {@link RedisNodeDescription}s. The sort key is the latency.
     * Nodes with lower latency come first.
     * 
     * @param seed collection of {@link RedisURI}s
     * @return mapping between {@link RedisURI} and {@link Partitions}
     */
    public List getNodes(RedisURI seed) {

        List nodes = topologyProvider.getNodes();

        addPasswordIfNeeded(nodes, seed);

        Map> connections = getConnections(nodes);
        Map> rawViews = requestPing(connections);
        List result = getNodeSpecificViews(rawViews, nodes, seed);
        close(connections);

        return result;
    }

    private void addPasswordIfNeeded(List nodes, RedisURI seed) {

        if (seed.getPassword() != null && seed.getPassword().length != 0) {
            for (RedisNodeDescription node : nodes) {
                node.getUri().setPassword(new String(seed.getPassword()));
            }
        }
    }

    protected List getNodeSpecificViews(
            Map> rawViews, List nodes, RedisURI seed) {
        List result = new ArrayList<>();

        long timeout = seed.getUnit().toNanos(seed.getTimeout());
        long waitTime = 0;
        Map latencies = new HashMap<>();

        for (Map.Entry> entry : rawViews.entrySet()) {
            long timeoutLeft = timeout - waitTime;

            if (timeoutLeft <= 0) {
                break;
            }

            long startWait = System.nanoTime();
            RedisFuture future = entry.getValue();

            try {

                if (!future.await(timeoutLeft, TimeUnit.NANOSECONDS)) {
                    break;
                }
                waitTime += System.nanoTime() - startWait;

                future.get();

                RedisNodeDescription redisNodeDescription = findNodeByUri(nodes, entry.getKey());
                latencies.put(redisNodeDescription, entry.getValue().duration());
                result.add(redisNodeDescription);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RedisCommandInterruptedException(e);
            } catch (ExecutionException e) {
                logger.warn("Cannot retrieve partition view from " + entry.getKey(), e);
            }
        }

        LatencyComparator comparator = new LatencyComparator(latencies);

        Collections.sort(result, comparator);

        return result;
    }

    /*
     * Async request of views.
     */
    @SuppressWarnings("unchecked")
    private Map> requestPing(
            Map> connections) {
        Map> rawViews = new TreeMap<>(RedisUriComparator.INSTANCE);
        for (Map.Entry> entry : connections.entrySet()) {

            TimedAsyncCommand timed = createPingCommand();

            entry.getValue().dispatch(timed);
            rawViews.put(entry.getKey(), timed);
        }
        return rawViews;
    }

    protected TimedAsyncCommand createPingCommand() {
        CommandArgs args = new CommandArgs<>(MasterSlaveUtils.CODEC);
        Command command = new Command<>(CommandType.PING, new StatusOutput<>(MasterSlaveUtils.CODEC),
                args);
        return new TimedAsyncCommand<>(command);
    }

    private void close(Map> connections) {
        for (StatefulRedisConnection connection : connections.values()) {
            connection.close();
        }
    }

    /*
     * Open connections where an address can be resolved.
     */
    private Map> getConnections(Iterable nodes) {
        Map> connections = new TreeMap<>(RedisUriComparator.INSTANCE);

        for (RedisNodeDescription node : nodes) {

            try {
                StatefulRedisConnection connection = client.connect(node.getUri());
                connections.put(node.getUri(), connection);
            } catch (RuntimeException e) {
                logger.warn("Cannot connect to " + node.getUri(), e);
            }
        }
        return connections;
    }

    /**
     * Compare {@link RedisURI} based on their host and port representation.
     */
    static class RedisUriComparator implements Comparator {

        public static final RedisUriComparator INSTANCE = new RedisUriComparator();

        @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);
        }
    }

    /**
     * Timed command that records the time at which the command was encoded and completed.
     * 
     * @param  Key type
     * @param  Value type
     * @param  Result type
     */
    static class TimedAsyncCommand extends AsyncCommand {

        long encodedAtNs = -1;
        long completedAtNs = -1;

        public TimedAsyncCommand(RedisCommand command) {
            super(command);
        }

        @Override
        public void encode(ByteBuf buf) {
            completedAtNs = -1;
            encodedAtNs = -1;

            super.encode(buf);
            encodedAtNs = System.nanoTime();
        }

        @Override
        public void complete() {
            completedAtNs = System.nanoTime();
            super.complete();
        }

        public long duration() {
            if (completedAtNs == -1 || encodedAtNs == -1) {
                return -1;
            }
            return completedAtNs - encodedAtNs;
        }
    }

    static class LatencyComparator implements Comparator {

        private final Map latencies;

        public LatencyComparator(Map latencies) {
            this.latencies = latencies;
        }

        @Override
        public int compare(RedisNodeDescription o1, RedisNodeDescription o2) {

            Long latency1 = latencies.get(o1);
            Long latency2 = latencies.get(o2);

            if (latency1 != null && latency2 != null) {
                return latency1.compareTo(latency2);
            }

            if (latency1 != null && latency2 == null) {
                return -1;
            }

            if (latency1 == null && latency2 != null) {
                return 1;
            }

            return 0;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy