diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 2d0949221..be60e018f 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -7,6 +7,7 @@ New and noteworthy in the latest releases. == New in Spring Data Redis 2.1 * Unix domain socket connections using <>. +* <> support using Lettuce. [[new-in-2.0.0]] == New in Spring Data Redis 2.0 diff --git a/src/main/asciidoc/reference/redis.adoc b/src/main/asciidoc/reference/redis.adoc index c8b6fdcff..cd00917a6 100644 --- a/src/main/asciidoc/reference/redis.adoc +++ b/src/main/asciidoc/reference/redis.adoc @@ -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: <>), not only allows data to be savely stored at more nodes. It also allows, using <>, 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 diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/ClusterConnectionProvider.java b/src/main/java/org/springframework/data/redis/connection/lettuce/ClusterConnectionProvider.java index dd5f1146f..51e33d61a 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/ClusterConnectionProvider.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/ClusterConnectionProvider.java @@ -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; + /** + * 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) diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java b/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java index cee1555b7..982f73ae5 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java @@ -95,7 +95,8 @@ class DefaultLettuceClientConfiguration implements LettuceClientConfiguration { return clientResources; } - /* (non-Javadoc) + /* + * (non-Javadoc) * @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions() */ @Override diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java index a377a0965..9e5bbaf35 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java @@ -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); } /** diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java index 830ff592e..a67412e01 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java @@ -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; diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java index ff5ae91cf..f169dbade 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java @@ -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 = 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 diff --git a/src/main/java/org/springframework/data/redis/connection/lettuce/StandaloneConnectionProvider.java b/src/main/java/org/springframework/data/redis/connection/lettuce/StandaloneConnectionProvider.java index a544a5b4c..4aacd1489 100644 --- a/src/main/java/org/springframework/data/redis/connection/lettuce/StandaloneConnectionProvider.java +++ b/src/main/java/org/springframework/data/redis/connection/lettuce/StandaloneConnectionProvider.java @@ -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; + private final Supplier 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() { + + AtomicReference 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; + } + } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java index 22e9a6c33..a527c79d2 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java @@ -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(); diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterCommandsTestsBase.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterCommandsTestsBase.java index c3289b937..9b121f38b 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterCommandsTestsBase.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterCommandsTestsBase.java @@ -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()); } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveCommandsTestsBase.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveCommandsTestsBase.java index 902286bfb..69dd6379a 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveCommandsTestsBase.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveCommandsTestsBase.java @@ -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 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" }); } diff --git a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceSentinelIntegrationTests.java b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceSentinelIntegrationTests.java index 639a914ab..17ce3c46f 100644 --- a/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceSentinelIntegrationTests.java +++ b/src/test/java/org/springframework/data/redis/connection/lettuce/LettuceSentinelIntegrationTests.java @@ -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();