/* * 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<RedisNodeDescription> getNodes(RedisURI seed) { List<RedisNodeDescription> 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<RedisNodeDescription> nodes, RedisURI seed) { if (seed.getPassword() != null && seed.getPassword().length != 0) { for (RedisNodeDescription node : nodes) { node.getUri().setPassword(new String(seed.getPassword())); } } } private List<RedisNodeDescription> getNodeSpecificViews(Requests requestedPing, List<RedisNodeDescription> nodes, RedisURI seed) throws InterruptedException { List<RedisNodeDescription> result = new ArrayList<>(); long timeout = seed.getUnit().toNanos(seed.getTimeout()); Map<RedisNodeDescription, Long> latencies = new HashMap<>(); requestedPing.await(timeout, TimeUnit.NANOSECONDS); for (RedisNodeDescription node : nodes) { TimedAsyncCommand<String, String, String> 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<RedisNodeDescription> 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<StatefulRedisConnection<String, String>> connectionFuture = nodeConnectionFactory .connectToNodeAsync(CODEC, redisURI); CompletableFuture<StatefulRedisConnection<String, String>> 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; } }