DATAREDIS-580 - Polishing.
Fix tests not closing connections properly. Remove usage of Optional in constructors, avoid recreating DirectFieldAccessor on every getConnection call and update documentation. Original Pull Request: #287
This commit is contained in:
@@ -7,6 +7,7 @@ New and noteworthy in the latest releases.
|
||||
== New in Spring Data Redis 2.1
|
||||
|
||||
* Unix domain socket connections using <<redis:connectors:lettuce,Lettuce>>.
|
||||
* <<redis:write-to-master-read-from-slave, Write to Master read from Slave>> support using Lettuce.
|
||||
|
||||
[[new-in-2.0.0]]
|
||||
== New in Spring Data Redis 2.0
|
||||
|
||||
@@ -111,6 +111,32 @@ class RedisConfiguration {
|
||||
}
|
||||
----
|
||||
|
||||
[[redis:write-to-master-read-from-slave]]
|
||||
=== Write to Master read from Slave
|
||||
|
||||
Redis Master/Slave setup, without automatic failover (for automatic failover see: <<redis:sentinel, Sentinel>>), not only allows data to be savely stored at more nodes. It also allows, using <<redis:connectors:lettuce, Lettuce>>, reading data from slaves while pushing writes to the master.
|
||||
Set the read/write strategy to be used via `LettuceClientConfiguration`.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
class WriteToMasterReadFromSlaveConfiguration {
|
||||
|
||||
@Bean
|
||||
public LettuceConnectionFactory redisConnectionFactory() {
|
||||
|
||||
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
|
||||
.readFrom(SLAVE_PREFERRED)
|
||||
.build();
|
||||
|
||||
RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration("server", 6379);
|
||||
|
||||
return new LettuceConnectionFactory(serverConfig, clientConfig);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[redis:sentinel]]
|
||||
== Redis Sentinel Support
|
||||
|
||||
|
||||
@@ -21,10 +21,12 @@ import io.lettuce.core.cluster.RedisClusterClient;
|
||||
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
|
||||
import io.lettuce.core.codec.RedisCodec;
|
||||
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Connection provider for Cluster connections.
|
||||
*
|
||||
@@ -32,13 +34,40 @@ import java.util.Optional;
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class ClusterConnectionProvider implements LettuceConnectionProvider {
|
||||
|
||||
private final RedisClusterClient client;
|
||||
private final RedisCodec<?, ?> codec;
|
||||
private final Optional<ReadFrom> readFrom;
|
||||
|
||||
/**
|
||||
* Create new {@link ClusterConnectionProvider}.
|
||||
*
|
||||
* @param client must not be {@literal null}.
|
||||
* @param codec must not be {@literal null}.
|
||||
*/
|
||||
ClusterConnectionProvider(RedisClusterClient client, RedisCodec<?, ?> codec) {
|
||||
this(client, codec, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link ClusterConnectionProvider}.
|
||||
*
|
||||
* @param client must not be {@literal null}.
|
||||
* @param codec must not be {@literal null}.
|
||||
* @param readFrom can be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
ClusterConnectionProvider(RedisClusterClient client, RedisCodec<?, ?> codec, @Nullable ReadFrom readFrom) {
|
||||
|
||||
Assert.notNull(client, "Client must not be null!");
|
||||
Assert.notNull(codec, "Codec must not be null!");
|
||||
|
||||
this.client = client;
|
||||
this.codec = codec;
|
||||
this.readFrom = Optional.ofNullable(readFrom);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class)
|
||||
|
||||
@@ -95,7 +95,8 @@ class DefaultLettuceClientConfiguration implements LettuceClientConfiguration {
|
||||
return clientResources;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions()
|
||||
*/
|
||||
@Override
|
||||
|
||||
@@ -30,7 +30,6 @@ import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -75,7 +74,7 @@ public class LettuceClusterConnection extends LettuceConnection implements Defau
|
||||
* @param clusterClient must not be {@literal null}.
|
||||
*/
|
||||
public LettuceClusterConnection(RedisClusterClient clusterClient) {
|
||||
this(new ClusterConnectionProvider(clusterClient, CODEC, Optional.empty()));
|
||||
this(new ClusterConnectionProvider(clusterClient, CODEC));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +98,7 @@ public class LettuceClusterConnection extends LettuceConnection implements Defau
|
||||
* @since 2.0
|
||||
*/
|
||||
public LettuceClusterConnection(RedisClusterClient clusterClient, ClusterCommandExecutor executor, Duration timeout) {
|
||||
this(new ClusterConnectionProvider(clusterClient, CODEC, Optional.empty()), executor, timeout);
|
||||
this(new ClusterConnectionProvider(clusterClient, CODEC), executor, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,6 @@ import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
@@ -225,7 +224,7 @@ public class LettuceConnection extends AbstractRedisConnection {
|
||||
if (pool != null) {
|
||||
this.connectionProvider = new LettucePoolConnectionProvider(pool);
|
||||
} else {
|
||||
this.connectionProvider = new StandaloneConnectionProvider((RedisClient) client, CODEC, Optional.empty());
|
||||
this.connectionProvider = new StandaloneConnectionProvider((RedisClient) client, CODEC);
|
||||
}
|
||||
|
||||
this.asyncSharedConn = sharedConnection;
|
||||
|
||||
@@ -899,7 +899,7 @@ public class LettuceConnectionFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link LettuceConnectionProvider } given {@link AbstractRedisClient} and {@link RedisCodec}. Configuration
|
||||
* Create a {@link LettuceConnectionProvider} given {@link AbstractRedisClient} and {@link RedisCodec}. Configuration
|
||||
* of this connection factory specifies the type of the created connection provider. This method creates either a
|
||||
* {@link LettuceConnectionProvider} for either {@link RedisClient} or {@link RedisClusterClient}. Subclasses may
|
||||
* override this method to decorate the connection provider.
|
||||
@@ -909,10 +909,11 @@ public class LettuceConnectionFactory
|
||||
* Reactive connections require a {@link java.nio.ByteBuffer} codec.
|
||||
* @return the connection provider.
|
||||
* @since 2.1
|
||||
* @see io.lettuce.core.codec.ByteArrayCodec
|
||||
* @see org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection.ByteBufferCodec
|
||||
*/
|
||||
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {Optional<ReadFrom> readFrom = getClientConfiguration().getReadFrom();
|
||||
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {
|
||||
|
||||
ReadFrom readFrom = getClientConfiguration().getReadFrom().orElse(null);
|
||||
|
||||
if (isClusterAware()) {
|
||||
return new ClusterConnectionProvider((RedisClusterClient) client, codec, readFrom);
|
||||
}
|
||||
@@ -1216,7 +1217,8 @@ public class LettuceConnectionFactory
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getReadFrom()
|
||||
*/
|
||||
@Override
|
||||
|
||||
@@ -19,29 +19,77 @@ import io.lettuce.core.ReadFrom;
|
||||
import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.RedisURI;
|
||||
import io.lettuce.core.api.StatefulConnection;
|
||||
import io.lettuce.core.api.StatefulRedisConnection;
|
||||
import io.lettuce.core.codec.RedisCodec;
|
||||
import io.lettuce.core.masterslave.MasterSlave;
|
||||
import io.lettuce.core.masterslave.StatefulRedisMasterSlaveConnection;
|
||||
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
|
||||
import io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider.TargetAware;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link LettuceConnectionProvider} implementation for a standalone Redis setup.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
* @since 2.0
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetAware {
|
||||
|
||||
private final RedisClient client;
|
||||
private final RedisCodec<?, ?> codec;
|
||||
private final Optional<ReadFrom> readFrom;
|
||||
private final Supplier<RedisURI> redisURISupplier;
|
||||
|
||||
/**
|
||||
* Create new {@link StandaloneConnectionProvider}.
|
||||
*
|
||||
* @param client must not be {@literal null}.
|
||||
* @param codec must not be {@literal null}.
|
||||
*/
|
||||
StandaloneConnectionProvider(RedisClient client, RedisCodec<?, ?> codec) {
|
||||
this(client, codec, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new {@link StandaloneConnectionProvider}.
|
||||
*
|
||||
* @param client must not be {@literal null}.
|
||||
* @param codec must not be {@literal null}.
|
||||
* @param readFrom can be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
StandaloneConnectionProvider(RedisClient client, RedisCodec<?, ?> codec, @Nullable ReadFrom readFrom) {
|
||||
|
||||
this.client = client;
|
||||
this.codec = codec;
|
||||
this.readFrom = Optional.ofNullable(readFrom);
|
||||
|
||||
redisURISupplier = new Supplier<RedisURI>() {
|
||||
|
||||
AtomicReference<RedisURI> uriFieldReference = new AtomicReference();
|
||||
|
||||
@Override
|
||||
public RedisURI get() {
|
||||
|
||||
RedisURI uri = uriFieldReference.get();
|
||||
if (uri != null) {
|
||||
return uri;
|
||||
}
|
||||
|
||||
uri = RedisURI.class.cast(new DirectFieldAccessor(client).getPropertyValue("redisURI"));
|
||||
|
||||
return uriFieldReference.compareAndSet(null, uri) ? uri : uriFieldReference.get();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -61,16 +109,8 @@ class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetA
|
||||
|
||||
if (StatefulConnection.class.isAssignableFrom(connectionType)) {
|
||||
|
||||
return readFrom.map(it -> {
|
||||
|
||||
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(client);
|
||||
RedisURI redisURI = RedisURI.class.cast(fieldAccessor.getPropertyValue("redisURI"));
|
||||
|
||||
StatefulRedisMasterSlaveConnection<?, ?> connection = MasterSlave.connect(client, codec, redisURI);
|
||||
connection.setReadFrom(it);
|
||||
|
||||
return connectionType.cast(connection);
|
||||
}).orElseGet(() -> connectionType.cast(client.connect(codec)));
|
||||
return connectionType.cast(readFrom.map(it -> this.masterSlaveConnection(redisURISupplier.get(), it))
|
||||
.orElseGet(() -> client.connect(codec)));
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
|
||||
@@ -93,9 +133,20 @@ class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetA
|
||||
}
|
||||
|
||||
if (StatefulConnection.class.isAssignableFrom(connectionType)) {
|
||||
return connectionType.cast(client.connect(codec, redisURI));
|
||||
|
||||
return connectionType
|
||||
.cast(readFrom.map(it -> this.masterSlaveConnection(redisURI, it)).orElseGet(() -> client.connect(codec)));
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
|
||||
}
|
||||
|
||||
private StatefulRedisConnection masterSlaveConnection(RedisURI redisUri, ReadFrom readFrom) {
|
||||
|
||||
StatefulRedisMasterSlaveConnection<?, ?> connection = MasterSlave.connect(client, codec, redisUri);
|
||||
connection.setReadFrom(readFrom);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -389,6 +389,9 @@ public class LettuceConnectionFactoryTests {
|
||||
@Test // DATAREDIS-580
|
||||
public void factoryUsesMasterSlaveConnections() {
|
||||
|
||||
assumeThat(String.format("No slaves connected to %s:%s.", SettingsUtils.getHost(), SettingsUtils.getPort()),
|
||||
connection.info("replication").getProperty("connected_slaves", "0").compareTo("0") > 0, is(true));
|
||||
|
||||
LettuceClientConfiguration configuration = LettuceTestClientConfiguration.builder().readFrom(ReadFrom.SLAVE)
|
||||
.build();
|
||||
|
||||
@@ -402,7 +405,7 @@ public class LettuceConnectionFactoryTests {
|
||||
assertThat(connection.ping(), is(equalTo("PONG")));
|
||||
assertThat(connection.info().getProperty("role"), is(equalTo("slave")));
|
||||
} finally {
|
||||
this.connection.close();
|
||||
connection.close();
|
||||
}
|
||||
|
||||
factory.destroy();
|
||||
|
||||
@@ -22,8 +22,6 @@ import io.lettuce.core.api.sync.RedisCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
|
||||
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
@@ -45,8 +43,7 @@ public abstract class LettuceReactiveClusterCommandsTestsBase {
|
||||
assumeThat(clientProvider.test(), is(true));
|
||||
nativeCommands = clientProvider.getClient().connect().sync();
|
||||
connection = new LettuceReactiveRedisClusterConnection(
|
||||
new ClusterConnectionProvider(clientProvider.getClient(), LettuceReactiveRedisConnection.CODEC,
|
||||
Optional.empty()),
|
||||
new ClusterConnectionProvider(clientProvider.getClient(), LettuceReactiveRedisConnection.CODEC),
|
||||
clientProvider.getClient());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -90,9 +89,9 @@ public abstract class LettuceReactiveCommandsTestsBase {
|
||||
List<Object[]> parameters = new ArrayList<>();
|
||||
|
||||
StandaloneConnectionProvider standaloneProvider = new StandaloneConnectionProvider(standalone.getClient(),
|
||||
LettuceReactiveRedisConnection.CODEC, Optional.empty());
|
||||
LettuceReactiveRedisConnection.CODEC);
|
||||
StandaloneConnectionProvider nativeConnectionProvider = new StandaloneConnectionProvider(standalone.getClient(),
|
||||
StringCodec.UTF8, Optional.empty());
|
||||
StringCodec.UTF8);
|
||||
|
||||
parameters.add(new Object[] { standaloneProvider, nativeConnectionProvider, "Standalone" });
|
||||
parameters.add(new Object[] {
|
||||
@@ -102,9 +101,9 @@ public abstract class LettuceReactiveCommandsTestsBase {
|
||||
if (cluster.test()) {
|
||||
|
||||
ClusterConnectionProvider clusterProvider = new ClusterConnectionProvider(cluster.getClient(),
|
||||
LettuceReactiveRedisConnection.CODEC, Optional.empty());
|
||||
LettuceReactiveRedisConnection.CODEC);
|
||||
ClusterConnectionProvider nativeClusterConnectionProvider = new ClusterConnectionProvider(cluster.getClient(),
|
||||
StringCodec.UTF8, Optional.empty());
|
||||
StringCodec.UTF8);
|
||||
|
||||
parameters.add(new Object[] { clusterProvider, nativeClusterConnectionProvider, "Cluster" });
|
||||
}
|
||||
|
||||
@@ -219,7 +219,7 @@ public class LettuceSentinelIntegrationTests extends AbstractConnectionIntegrati
|
||||
LettuceClientConfiguration configuration = LettuceTestClientConfiguration.builder().readFrom(ReadFrom.SLAVE)
|
||||
.build();
|
||||
|
||||
LettuceConnectionFactory factory = new LettuceConnectionFactory(SettingsUtils.standaloneConfiguration(),
|
||||
LettuceConnectionFactory factory = new LettuceConnectionFactory(SENTINEL_CONFIG,
|
||||
configuration);
|
||||
factory.afterPropertiesSet();
|
||||
|
||||
@@ -229,7 +229,7 @@ public class LettuceSentinelIntegrationTests extends AbstractConnectionIntegrati
|
||||
assertThat(connection.ping(), is(equalTo("PONG")));
|
||||
assertThat(connection.info().getProperty("role"), is(equalTo("slave")));
|
||||
} finally {
|
||||
this.connection.close();
|
||||
connection.close();
|
||||
}
|
||||
|
||||
factory.destroy();
|
||||
|
||||
Reference in New Issue
Block a user