Skip to content

Commit c3f7a59

Browse files
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.
1 parent dccbdfe commit c3f7a59

12 files changed

+143
-38
lines changed

src/main/asciidoc/new-features.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ New and noteworthy in the latest releases.
77
== New in Spring Data Redis 2.1
88

99
* Unix domain socket connections using <<redis:connectors:lettuce,Lettuce>>.
10+
* <<redis:write-to-master-read-from-slave, Write to Master read from Slave>> support using Lettuce.
1011

1112
[[new-in-2.0.0]]
1213
== New in Spring Data Redis 2.0

src/main/asciidoc/reference/redis.adoc

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,30 @@ class RedisConfiguration {
111111
}
112112
----
113113

114+
[[redis:write-to-master-read-from-slave]]
115+
=== Write to Master read from Slave
116+
117+
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.
118+
Set the read/write strategy to be used via `LettuceClientConfiguration`.
119+
120+
[source,java]
121+
----
122+
@Configuration
123+
class WriteToMasterReadFromSlaveConfiguration {
124+
125+
@Bean
126+
public LettuceConnectionFactory redisConnectionFactory() {
127+
128+
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
129+
.readFrom(SLAVE_PREFERRED)
130+
.build();
131+
132+
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379), clientConfig);
133+
}
134+
}
135+
----
136+
137+
114138
[[redis:sentinel]]
115139
== Redis Sentinel Support
116140

src/main/java/org/springframework/data/redis/connection/lettuce/ClusterConnectionProvider.java

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,24 +21,53 @@
2121
import io.lettuce.core.cluster.api.StatefulRedisClusterConnection;
2222
import io.lettuce.core.codec.RedisCodec;
2323
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
24-
import lombok.RequiredArgsConstructor;
2524

2625
import java.util.Optional;
2726

27+
import org.springframework.lang.Nullable;
28+
import org.springframework.util.Assert;
29+
2830
/**
2931
* Connection provider for Cluster connections.
3032
*
3133
* @author Mark Paluch
3234
* @author Christoph Strobl
3335
* @since 2.0
3436
*/
35-
@RequiredArgsConstructor
3637
class ClusterConnectionProvider implements LettuceConnectionProvider {
3738

3839
private final RedisClusterClient client;
3940
private final RedisCodec<?, ?> codec;
4041
private final Optional<ReadFrom> readFrom;
4142

43+
/**
44+
* Create new {@link ClusterConnectionProvider}.
45+
*
46+
* @param client must not be {@literal null}.
47+
* @param codec must not be {@literal null}.
48+
*/
49+
ClusterConnectionProvider(RedisClusterClient client, RedisCodec<?, ?> codec) {
50+
this(client, codec, null);
51+
}
52+
53+
/**
54+
* Create new {@link ClusterConnectionProvider}.
55+
*
56+
* @param client must not be {@literal null}.
57+
* @param codec must not be {@literal null}.
58+
* @param readFrom can be {@literal null}.
59+
* @since 2.1
60+
*/
61+
ClusterConnectionProvider(RedisClusterClient client, RedisCodec<?, ?> codec, @Nullable ReadFrom readFrom) {
62+
63+
Assert.notNull(client, "Client must not be null!");
64+
Assert.notNull(codec, "Codec must not be null!");
65+
66+
this.client = client;
67+
this.codec = codec;
68+
this.readFrom = Optional.ofNullable(readFrom);
69+
}
70+
4271
/*
4372
* (non-Javadoc)
4473
* @see org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider#getConnection(java.lang.Class)

src/main/java/org/springframework/data/redis/connection/lettuce/DefaultLettuceClientConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ public Optional<ClientResources> getClientResources() {
9595
return clientResources;
9696
}
9797

98-
/* (non-Javadoc)
98+
/*
99+
* (non-Javadoc)
99100
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getClientOptions()
100101
*/
101102
@Override

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceClusterConnection.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import java.util.LinkedHashMap;
3131
import java.util.List;
3232
import java.util.Map;
33-
import java.util.Optional;
3433
import java.util.Set;
3534

3635
import org.apache.commons.logging.Log;
@@ -75,7 +74,7 @@ public class LettuceClusterConnection extends LettuceConnection implements Defau
7574
* @param clusterClient must not be {@literal null}.
7675
*/
7776
public LettuceClusterConnection(RedisClusterClient clusterClient) {
78-
this(new ClusterConnectionProvider(clusterClient, CODEC, Optional.empty()));
77+
this(new ClusterConnectionProvider(clusterClient, CODEC));
7978
}
8079

8180
/**
@@ -99,7 +98,7 @@ public LettuceClusterConnection(RedisClusterClient clusterClient, ClusterCommand
9998
* @since 2.0
10099
*/
101100
public LettuceClusterConnection(RedisClusterClient clusterClient, ClusterCommandExecutor executor, Duration timeout) {
102-
this(new ClusterConnectionProvider(clusterClient, CODEC, Optional.empty()), executor, timeout);
101+
this(new ClusterConnectionProvider(clusterClient, CODEC), executor, timeout);
103102
}
104103

105104
/**

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnection.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import java.util.LinkedList;
5050
import java.util.List;
5151
import java.util.Map;
52-
import java.util.Optional;
5352
import java.util.Queue;
5453
import java.util.concurrent.ConcurrentHashMap;
5554
import java.util.concurrent.Future;
@@ -225,7 +224,7 @@ public LettuceConnection(@Nullable StatefulRedisConnection<byte[], byte[]> share
225224
if (pool != null) {
226225
this.connectionProvider = new LettucePoolConnectionProvider(pool);
227226
} else {
228-
this.connectionProvider = new StandaloneConnectionProvider((RedisClient) client, CODEC, Optional.empty());
227+
this.connectionProvider = new StandaloneConnectionProvider((RedisClient) client, CODEC);
229228
}
230229

231230
this.asyncSharedConn = sharedConnection;

src/main/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactory.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,7 @@ private LettuceConnectionProvider createConnectionProvider(AbstractRedisClient c
899899
}
900900

901901
/**
902-
* Create a {@link LettuceConnectionProvider } given {@link AbstractRedisClient} and {@link RedisCodec}. Configuration
902+
* Create a {@link LettuceConnectionProvider} given {@link AbstractRedisClient} and {@link RedisCodec}. Configuration
903903
* of this connection factory specifies the type of the created connection provider. This method creates either a
904904
* {@link LettuceConnectionProvider} for either {@link RedisClient} or {@link RedisClusterClient}. Subclasses may
905905
* override this method to decorate the connection provider.
@@ -909,10 +909,11 @@ private LettuceConnectionProvider createConnectionProvider(AbstractRedisClient c
909909
* Reactive connections require a {@link java.nio.ByteBuffer} codec.
910910
* @return the connection provider.
911911
* @since 2.1
912-
* @see io.lettuce.core.codec.ByteArrayCodec
913-
* @see org.springframework.data.redis.connection.lettuce.LettuceReactiveRedisConnection.ByteBufferCodec
914912
*/
915-
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {Optional<ReadFrom> readFrom = getClientConfiguration().getReadFrom();
913+
protected LettuceConnectionProvider doCreateConnectionProvider(AbstractRedisClient client, RedisCodec<?, ?> codec) {
914+
915+
ReadFrom readFrom = getClientConfiguration().getReadFrom().orElse(null);
916+
916917
if (isClusterAware()) {
917918
return new ClusterConnectionProvider((RedisClusterClient) client, codec, readFrom);
918919
}
@@ -1216,7 +1217,8 @@ public Optional<ClientOptions> getClientOptions() {
12161217
return Optional.empty();
12171218
}
12181219

1219-
/* (non-Javadoc)
1220+
/*
1221+
* (non-Javadoc)
12201222
* @see org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration#getReadFrom()
12211223
*/
12221224
@Override

src/main/java/org/springframework/data/redis/connection/lettuce/StandaloneConnectionProvider.java

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,77 @@
1919
import io.lettuce.core.RedisClient;
2020
import io.lettuce.core.RedisURI;
2121
import io.lettuce.core.api.StatefulConnection;
22+
import io.lettuce.core.api.StatefulRedisConnection;
2223
import io.lettuce.core.codec.RedisCodec;
2324
import io.lettuce.core.masterslave.MasterSlave;
2425
import io.lettuce.core.masterslave.StatefulRedisMasterSlaveConnection;
2526
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
2627
import io.lettuce.core.sentinel.api.StatefulRedisSentinelConnection;
27-
import lombok.RequiredArgsConstructor;
2828

2929
import java.util.Optional;
30+
import java.util.concurrent.atomic.AtomicReference;
31+
import java.util.function.Supplier;
3032

3133
import org.springframework.beans.DirectFieldAccessor;
3234
import org.springframework.data.redis.connection.lettuce.LettuceConnectionProvider.TargetAware;
35+
import org.springframework.lang.Nullable;
3336

3437
/**
38+
* {@link LettuceConnectionProvider} implementation for a standalone Redis setup.
39+
*
3540
* @author Mark Paluch
3641
* @author Christoph Strobl
3742
* @since 2.0
3843
*/
39-
@RequiredArgsConstructor
4044
class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetAware {
4145

4246
private final RedisClient client;
4347
private final RedisCodec<?, ?> codec;
4448
private final Optional<ReadFrom> readFrom;
49+
private final Supplier<RedisURI> redisURISupplier;
50+
51+
/**
52+
* Create new {@link StandaloneConnectionProvider}.
53+
*
54+
* @param client must not be {@literal null}.
55+
* @param codec must not be {@literal null}.
56+
*/
57+
StandaloneConnectionProvider(RedisClient client, RedisCodec<?, ?> codec) {
58+
this(client, codec, null);
59+
}
60+
61+
/**
62+
* Create new {@link StandaloneConnectionProvider}.
63+
*
64+
* @param client must not be {@literal null}.
65+
* @param codec must not be {@literal null}.
66+
* @param readFrom can be {@literal null}.
67+
* @since 2.1
68+
*/
69+
StandaloneConnectionProvider(RedisClient client, RedisCodec<?, ?> codec, @Nullable ReadFrom readFrom) {
70+
71+
this.client = client;
72+
this.codec = codec;
73+
this.readFrom = Optional.ofNullable(readFrom);
74+
75+
redisURISupplier = new Supplier<RedisURI>() {
76+
77+
AtomicReference<RedisURI> uriFieldReference = new AtomicReference();
78+
79+
@Override
80+
public RedisURI get() {
81+
82+
RedisURI uri = uriFieldReference.get();
83+
if (uri != null) {
84+
return uri;
85+
}
86+
87+
uri = RedisURI.class.cast(new DirectFieldAccessor(client).getPropertyValue("redisURI"));
88+
89+
return uriFieldReference.compareAndSet(null, uri) ? uri : uriFieldReference.get();
90+
}
91+
};
92+
}
4593

4694
/*
4795
* (non-Javadoc)
@@ -61,16 +109,8 @@ class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetA
61109

62110
if (StatefulConnection.class.isAssignableFrom(connectionType)) {
63111

64-
return readFrom.map(it -> {
65-
66-
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(client);
67-
RedisURI redisURI = RedisURI.class.cast(fieldAccessor.getPropertyValue("redisURI"));
68-
69-
StatefulRedisMasterSlaveConnection<?, ?> connection = MasterSlave.connect(client, codec, redisURI);
70-
connection.setReadFrom(it);
71-
72-
return connectionType.cast(connection);
73-
}).orElseGet(() -> connectionType.cast(client.connect(codec)));
112+
return connectionType.cast(readFrom.map(it -> this.masterSlaveConnection(redisURISupplier.get(), it))
113+
.orElseGet(() -> client.connect(codec)));
74114
}
75115

76116
throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
@@ -93,9 +133,20 @@ class StandaloneConnectionProvider implements LettuceConnectionProvider, TargetA
93133
}
94134

95135
if (StatefulConnection.class.isAssignableFrom(connectionType)) {
96-
return connectionType.cast(client.connect(codec, redisURI));
136+
137+
return connectionType
138+
.cast(readFrom.map(it -> this.masterSlaveConnection(redisURI, it)).orElseGet(() -> client.connect(codec)));
97139
}
98140

99141
throw new UnsupportedOperationException("Connection type " + connectionType + " not supported!");
100142
}
143+
144+
private StatefulRedisConnection masterSlaveConnection(RedisURI redisUri, ReadFrom readFrom) {
145+
146+
StatefulRedisMasterSlaveConnection<?, ?> connection = MasterSlave.connect(client, codec, redisUri);
147+
connection.setReadFrom(readFrom);
148+
149+
return connection;
150+
}
151+
101152
}

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceConnectionFactoryTests.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ public void connectsThroughRedisSocket() {
389389
@Test // DATAREDIS-580
390390
public void factoryUsesMasterSlaveConnections() {
391391

392+
assumeThat(String.format("No slaves connected to %s:%s.", SettingsUtils.getHost(), SettingsUtils.getPort()),
393+
connection.info("replication").getProperty("connected_slaves", "0").compareTo("0") > 0, is(true));
394+
392395
LettuceClientConfiguration configuration = LettuceTestClientConfiguration.builder().readFrom(ReadFrom.SLAVE)
393396
.build();
394397

@@ -402,7 +405,7 @@ public void factoryUsesMasterSlaveConnections() {
402405
assertThat(connection.ping(), is(equalTo("PONG")));
403406
assertThat(connection.info().getProperty("role"), is(equalTo("slave")));
404407
} finally {
405-
this.connection.close();
408+
connection.close();
406409
}
407410

408411
factory.destroy();

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveClusterCommandsTestsBase.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
2323
import io.lettuce.core.cluster.api.sync.RedisClusterCommands;
2424

25-
import java.util.Optional;
26-
2725
import org.junit.After;
2826
import org.junit.Before;
2927
import org.junit.ClassRule;
@@ -45,8 +43,7 @@ public void before() {
4543
assumeThat(clientProvider.test(), is(true));
4644
nativeCommands = clientProvider.getClient().connect().sync();
4745
connection = new LettuceReactiveRedisClusterConnection(
48-
new ClusterConnectionProvider(clientProvider.getClient(), LettuceReactiveRedisConnection.CODEC,
49-
Optional.empty()),
46+
new ClusterConnectionProvider(clientProvider.getClient(), LettuceReactiveRedisConnection.CODEC),
5047
clientProvider.getClient());
5148
}
5249

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceReactiveCommandsTestsBase.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import java.nio.charset.Charset;
2727
import java.util.ArrayList;
2828
import java.util.List;
29-
import java.util.Optional;
3029

3130
import org.junit.After;
3231
import org.junit.Before;
@@ -90,9 +89,9 @@ public static List<Object[]> parameters() {
9089
List<Object[]> parameters = new ArrayList<>();
9190

9291
StandaloneConnectionProvider standaloneProvider = new StandaloneConnectionProvider(standalone.getClient(),
93-
LettuceReactiveRedisConnection.CODEC, Optional.empty());
92+
LettuceReactiveRedisConnection.CODEC);
9493
StandaloneConnectionProvider nativeConnectionProvider = new StandaloneConnectionProvider(standalone.getClient(),
95-
StringCodec.UTF8, Optional.empty());
94+
StringCodec.UTF8);
9695

9796
parameters.add(new Object[] { standaloneProvider, nativeConnectionProvider, "Standalone" });
9897
parameters.add(new Object[] {
@@ -102,9 +101,9 @@ public static List<Object[]> parameters() {
102101
if (cluster.test()) {
103102

104103
ClusterConnectionProvider clusterProvider = new ClusterConnectionProvider(cluster.getClient(),
105-
LettuceReactiveRedisConnection.CODEC, Optional.empty());
104+
LettuceReactiveRedisConnection.CODEC);
106105
ClusterConnectionProvider nativeClusterConnectionProvider = new ClusterConnectionProvider(cluster.getClient(),
107-
StringCodec.UTF8, Optional.empty());
106+
StringCodec.UTF8);
108107

109108
parameters.add(new Object[] { clusterProvider, nativeClusterConnectionProvider, "Cluster" });
110109
}

src/test/java/org/springframework/data/redis/connection/lettuce/LettuceSentinelIntegrationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ public void factoryUsesMasterSlaveConnections() {
219219
LettuceClientConfiguration configuration = LettuceTestClientConfiguration.builder().readFrom(ReadFrom.SLAVE)
220220
.build();
221221

222-
LettuceConnectionFactory factory = new LettuceConnectionFactory(SettingsUtils.standaloneConfiguration(),
222+
LettuceConnectionFactory factory = new LettuceConnectionFactory(SENTINEL_CONFIG,
223223
configuration);
224224
factory.afterPropertiesSet();
225225

@@ -229,7 +229,7 @@ public void factoryUsesMasterSlaveConnections() {
229229
assertThat(connection.ping(), is(equalTo("PONG")));
230230
assertThat(connection.info().getProperty("role"), is(equalTo("slave")));
231231
} finally {
232-
this.connection.close();
232+
connection.close();
233233
}
234234

235235
factory.destroy();

0 commit comments

Comments
 (0)