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.
/*
* 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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisCommandInterruptedException;
import com.lambdaworks.redis.RedisConnectionException;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.codec.StringCodec;
import com.lambdaworks.redis.models.role.RedisNodeDescription;
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 static final StringCodec CODEC = StringCodec.UTF8;
private final NodeConnectionFactory nodeConnectionFactory;
private final TopologyProvider topologyProvider;
public MasterSlaveTopologyRefresh(RedisClient client, TopologyProvider topologyProvider) {
this.nodeConnectionFactory = new ReflectiveNodeConnectionFactory(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);
AsyncConnections asyncConnections = getConnections(nodes);
Connections connections = null;
try {
connections = asyncConnections.get(seed.getTimeout(), seed.getUnit());
Requests requestedPing = connections.requestPing();
return getNodeSpecificViews(requestedPing, nodes, seed);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RedisCommandInterruptedException(e);
} finally {
if (connections != null) {
connections.close();
}
}
}
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()));
}
}
}
private List getNodeSpecificViews(Requests requestedPing, List nodes,
RedisURI seed) throws InterruptedException {
List result = new ArrayList<>();
long timeout = seed.getUnit().toNanos(seed.getTimeout());
Map latencies = new HashMap<>();
requestedPing.await(timeout, TimeUnit.NANOSECONDS);
for (RedisNodeDescription node : nodes) {
TimedAsyncCommand future = requestedPing.getRequest(node.getUri());
if (!future.isDone()) {
continue;
}
RedisNodeDescription redisNodeDescription = findNodeByUri(nodes, node.getUri());
latencies.put(redisNodeDescription, future.duration());
result.add(redisNodeDescription);
}
TopologyComparators.LatencyComparator comparator = new TopologyComparators.LatencyComparator(latencies);
result.sort(comparator);
return result;
}
/*
* Establish connections asynchronously.
*/
private AsyncConnections getConnections(Iterable nodes) {
AsyncConnections connections = new AsyncConnections();
for (RedisNodeDescription node : nodes) {
RedisURI redisURI = node.getUri();
String message = String.format("Unable to connect to %s", redisURI);
try {
CompletableFuture> connectionFuture = nodeConnectionFactory
.connectToNodeAsync(CODEC, redisURI);
CompletableFuture> sync = new CompletableFuture<>();
connectionFuture.whenComplete((connection, throwable) -> {
if (throwable != null) {
if (throwable instanceof RedisConnectionException) {
if (logger.isDebugEnabled()) {
logger.debug(throwable.getMessage(), throwable);
} else {
logger.warn(throwable.getMessage());
}
} else {
logger.warn(message, throwable);
}
sync.completeExceptionally(new RedisConnectionException(message, throwable));
} else {
connection.async().clientSetname("lettuce#MasterSlaveTopologyRefresh");
sync.complete(connection);
}
});
connections.addConnection(redisURI, sync);
} catch (RuntimeException e) {
logger.warn(String.format(message, redisURI), e);
}
}
return connections;
}
}