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