
com.lambdaworks.redis.masterslave.MasterSlaveConnectionProvider 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.findNodeByHostAndPort;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import com.lambdaworks.redis.ReadFrom;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisException;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.api.StatefulConnection;
import com.lambdaworks.redis.api.StatefulRedisConnection;
import com.lambdaworks.redis.cluster.models.partitions.Partitions;
import com.lambdaworks.redis.codec.RedisCodec;
import com.lambdaworks.redis.internal.LettuceSets;
import com.lambdaworks.redis.models.role.RedisInstance;
import com.lambdaworks.redis.models.role.RedisNodeDescription;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* Connection provider for master/slave setups. The connection provider
*
* @author Mark Paluch
* @since 4.1
*/
public class MasterSlaveConnectionProvider {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(MasterSlaveConnectionProvider.class);
private final boolean debugEnabled = logger.isDebugEnabled();
// Contains HostAndPort-identified connections.
private final Map> connections = new ConcurrentHashMap<>();
private final ConnectionFactory connectionFactory;
private final RedisURI initialRedisUri;
private List knownNodes = new ArrayList<>();
private boolean autoFlushCommands = true;
private Object stateLock = new Object();
private ReadFrom readFrom;
MasterSlaveConnectionProvider(RedisClient redisClient, RedisCodec redisCodec, RedisURI initialRedisUri,
Map> initialConnections) {
this.initialRedisUri = initialRedisUri;
this.connectionFactory = new ConnectionFactory<>(redisClient, redisCodec);
for (Map.Entry> entry : initialConnections.entrySet()) {
connections.put(toConnectionKey(entry.getKey()), entry.getValue());
}
}
/**
* Retrieve a {@link StatefulRedisConnection} by the intent.
* {@link com.lambdaworks.redis.masterslave.MasterSlaveConnectionProvider.Intent#WRITE} intentions use the master
* connection, {@link com.lambdaworks.redis.masterslave.MasterSlaveConnectionProvider.Intent#READ} intentions lookup one or
* more read candidates using the {@link ReadFrom} setting.
*
* @param intent command intent
* @return the connection.
*/
public StatefulRedisConnection getConnection(Intent intent) {
if (debugEnabled) {
logger.debug("getConnection(" + intent + ")");
}
if (readFrom != null && intent == Intent.READ) {
List selection = readFrom.select(new ReadFrom.Nodes() {
@Override
public List getNodes() {
return knownNodes;
}
@Override
public Iterator iterator() {
return knownNodes.iterator();
}
});
if (selection.isEmpty()) {
throw new RedisException(String.format("Cannot determine a node to read (Known nodes: %s) with setting %s",
knownNodes, readFrom));
}
try {
for (RedisNodeDescription redisNodeDescription : selection) {
StatefulRedisConnection readerCandidate = getConnection(redisNodeDescription);
if (!readerCandidate.isOpen()) {
continue;
}
return readerCandidate;
}
return getConnection(selection.get(0));
} catch (RuntimeException e) {
throw new RedisException(e);
}
}
return getConnection(getMaster());
}
protected StatefulRedisConnection getConnection(RedisNodeDescription redisNodeDescription) {
return connections.computeIfAbsent(
new ConnectionKey(redisNodeDescription.getUri().getHost(), redisNodeDescription.getUri().getPort()),
connectionFactory);
}
/**
*
* @return number of connections.
*/
protected long getConnectionCount() {
return connections.size();
}
/**
* Retrieve a set of PoolKey's for all pooled connections that are within the pool but not within the {@link Partitions}.
*
* @return Set of {@link ConnectionKey}s
*/
private Set getStaleConnectionKeys() {
Map> map = new HashMap<>(connections);
Set stale = new HashSet<>();
for (ConnectionKey connectionKey : map.keySet()) {
if (connectionKey.host != null
&& findNodeByHostAndPort(knownNodes, connectionKey.host, connectionKey.port) != null) {
continue;
}
stale.add(connectionKey);
}
return stale;
}
/**
* Close stale connections.
*/
public void closeStaleConnections() {
logger.debug("closeStaleConnections() count before expiring: {}", getConnectionCount());
Set stale = getStaleConnectionKeys();
for (ConnectionKey connectionKey : stale) {
StatefulRedisConnection connection = connections.get(connectionKey);
if (connection != null) {
connections.remove(connectionKey);
connection.close();
}
}
logger.debug("closeStaleConnections() count after expiring: {}", getConnectionCount());
}
public void reset() {
allConnections().forEach(StatefulRedisConnection::reset);
}
/**
* Close all connections.
*/
public void close() {
allConnections().forEach(StatefulRedisConnection::close);
connections.clear();
}
public void flushCommands() {
allConnections().forEach(StatefulConnection::flushCommands);
}
public void setAutoFlushCommands(boolean autoFlushCommands) {
synchronized (stateLock) {
}
allConnections().forEach(connection -> connection.setAutoFlushCommands(autoFlushCommands));
}
protected Collection> allConnections() {
Set> connections = LettuceSets.newHashSet(this.connections.values());
return (Collection) connections;
}
/**
*
* @param knownNodes
*/
public void setKnownNodes(Collection knownNodes) {
synchronized (stateLock) {
this.knownNodes.clear();
this.knownNodes.addAll(knownNodes);
closeStaleConnections();
}
}
public ReadFrom getReadFrom() {
return readFrom;
}
public void setReadFrom(ReadFrom readFrom) {
synchronized (stateLock) {
this.readFrom = readFrom;
}
}
public RedisNodeDescription getMaster() {
for (RedisNodeDescription knownNode : knownNodes) {
if (knownNode.getRole() == RedisInstance.Role.MASTER) {
return knownNode;
}
}
throw new RedisException(String.format("Master is currently unknown: %s", knownNodes));
}
private class ConnectionFactory implements Function> {
private final RedisClient redisClient;
private final RedisCodec redisCodec;
public ConnectionFactory(RedisClient redisClient, RedisCodec redisCodec) {
this.redisClient = redisClient;
this.redisCodec = redisCodec;
}
@Override
public StatefulRedisConnection apply(ConnectionKey key) {
RedisURI.Builder builder = RedisURI.Builder.redis(key.host, key.port);
if (initialRedisUri.getPassword() != null && initialRedisUri.getPassword().length != 0) {
builder.withPassword(new String(initialRedisUri.getPassword()));
}
builder.withDatabase(initialRedisUri.getDatabase());
StatefulRedisConnection connection = redisClient.connect(redisCodec, builder.build());
synchronized (stateLock) {
connection.setAutoFlushCommands(autoFlushCommands);
}
return connection;
}
}
private ConnectionKey toConnectionKey(RedisURI redisURI) {
return new ConnectionKey(redisURI.getHost(), redisURI.getPort());
}
/**
* Connection to identify a connection by host/port.
*/
private static class ConnectionKey {
private final String host;
private final int port;
public ConnectionKey(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof ConnectionKey))
return false;
ConnectionKey that = (ConnectionKey) o;
if (port != that.port)
return false;
return !(host != null ? !host.equals(that.host) : that.host != null);
}
@Override
public int hashCode() {
int result = (host != null ? host.hashCode() : 0);
result = 31 * result + port;
return result;
}
}
enum Intent {
READ, WRITE;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy