Skip to content

Commit fbcc1fd

Browse files
committed
Polish "Add support for static master-replica with Lettuce"
See gh-46957
1 parent c280fde commit fbcc1fd

File tree

10 files changed

+214
-102
lines changed

10 files changed

+214
-102
lines changed

documentation/spring-boot-docs/src/docs/antora/modules/reference/pages/data/nosql.adoc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ spring:
7575
TIP: You can also register an arbitrary number of beans that implement javadoc:org.springframework.boot.data.redis.autoconfigure.LettuceClientConfigurationBuilderCustomizer[] for more advanced customizations.
7676
javadoc:io.lettuce.core.resource.ClientResources[] can also be customized using javadoc:org.springframework.boot.data.redis.autoconfigure.ClientResourcesBuilderCustomizer[].
7777
If you use Jedis, javadoc:org.springframework.boot.data.redis.autoconfigure.JedisClientConfigurationBuilderCustomizer[] is also available.
78-
Alternatively, you can register a bean of type javadoc:org.springframework.data.redis.connection.RedisStandaloneConfiguration[], javadoc:org.springframework.data.redis.connection.RedisSentinelConfiguration[], or javadoc:org.springframework.data.redis.connection.RedisClusterConfiguration[] to take full control over the configuration.
78+
79+
Alternatively, you can register a bean of type javadoc:org.springframework.data.redis.connection.RedisStandaloneConfiguration[], javadoc:org.springframework.data.redis.connection.RedisSentinelConfiguration[], javadoc:org.springframework.data.redis.connection.RedisClusterConfiguration[], or javadoc:org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration[]] to take full control over the configuration.
80+
81+
NOTE: master/replica is not supported by Jedis.
7982

8083
If you add your own javadoc:org.springframework.context.annotation.Bean[format=annotation] of any of the auto-configured types, it replaces the default (except in the case of javadoc:org.springframework.data.redis.core.RedisTemplate[], when the exclusion is based on the bean name, `redisTemplate`, not its type).
8184

module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisConnectionConfiguration.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@
3333
import org.springframework.data.redis.connection.RedisPassword;
3434
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
3535
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
36+
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
3637
import org.springframework.util.Assert;
3738
import org.springframework.util.ClassUtils;
38-
import org.springframework.util.CollectionUtils;
3939

4040
/**
4141
* Base Redis connection configuration.
@@ -49,7 +49,6 @@
4949
* @author Andy Wilkinson
5050
* @author Phillip Webb
5151
* @author Yanming Zhou
52-
* @author Yong-Hyun Kim
5352
*/
5453
abstract class DataRedisConnectionConfiguration {
5554

@@ -64,6 +63,8 @@ abstract class DataRedisConnectionConfiguration {
6463

6564
private final @Nullable RedisClusterConfiguration clusterConfiguration;
6665

66+
private final @Nullable RedisStaticMasterReplicaConfiguration masterReplicaConfiguration;
67+
6768
private final DataRedisConnectionDetails connectionDetails;
6869

6970
protected final Mode mode;
@@ -72,11 +73,13 @@ protected DataRedisConnectionConfiguration(DataRedisProperties properties,
7273
DataRedisConnectionDetails connectionDetails,
7374
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
7475
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
75-
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
76+
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
77+
ObjectProvider<RedisStaticMasterReplicaConfiguration> masterReplicaConfiguration) {
7678
this.properties = properties;
7779
this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable();
7880
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
7981
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
82+
this.masterReplicaConfiguration = masterReplicaConfiguration.getIfAvailable();
8083
this.connectionDetails = connectionDetails;
8184
this.mode = determineMode();
8285
}
@@ -145,6 +148,25 @@ protected final RedisStandaloneConfiguration getStandaloneConfig() {
145148
return null;
146149
}
147150

151+
protected final @Nullable RedisStaticMasterReplicaConfiguration getMasterReplicaConfiguration() {
152+
if (this.masterReplicaConfiguration != null) {
153+
return this.masterReplicaConfiguration;
154+
}
155+
if (this.connectionDetails.getMasterReplica() != null) {
156+
List<Node> nodes = this.connectionDetails.getMasterReplica().getNodes();
157+
RedisStaticMasterReplicaConfiguration config = new RedisStaticMasterReplicaConfiguration(
158+
nodes.get(0).host(), nodes.get(0).port());
159+
nodes.stream().skip(1).forEach((node) -> config.addNode(node.host(), node.port()));
160+
config.setUsername(this.connectionDetails.getUsername());
161+
String password = this.connectionDetails.getPassword();
162+
if (password != null) {
163+
config.setPassword(RedisPassword.of(password));
164+
}
165+
return config;
166+
}
167+
return null;
168+
}
169+
148170
private List<RedisNode> getNodes(Cluster cluster) {
149171
return cluster.getNodes().stream().map(this::asRedisNode).toList();
150172
}
@@ -193,15 +215,15 @@ private Mode determineMode() {
193215
if (getClusterConfiguration() != null) {
194216
return Mode.CLUSTER;
195217
}
196-
if (!CollectionUtils.isEmpty(this.properties.getLettuce().getNodes())) {
197-
return Mode.STATIC_MASTER_REPLICA;
218+
if (getMasterReplicaConfiguration() != null) {
219+
return Mode.MASTER_REPLICA;
198220
}
199221
return Mode.STANDALONE;
200222
}
201223

202224
enum Mode {
203225

204-
STANDALONE, CLUSTER, SENTINEL, STATIC_MASTER_REPLICA
226+
STANDALONE, CLUSTER, MASTER_REPLICA, SENTINEL
205227

206228
}
207229

module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisConnectionDetails.java

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,32 +58,41 @@ public interface DataRedisConnectionDetails extends ConnectionDetails {
5858
}
5959

6060
/**
61-
* Redis standalone configuration. Mutually exclusive with {@link #getSentinel()} and
62-
* {@link #getCluster()}.
61+
* Redis standalone configuration. Mutually exclusive with {@link #getSentinel()},
62+
* {@link #getCluster()} and {@link #getMasterReplica()}.
6363
* @return the Redis standalone configuration
6464
*/
6565
default @Nullable Standalone getStandalone() {
6666
return null;
6767
}
6868

6969
/**
70-
* Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()} and
71-
* {@link #getCluster()}.
70+
* Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()},
71+
* {@link #getCluster()} and {@link #getMasterReplica()}.
7272
* @return the Redis sentinel configuration
7373
*/
7474
default @Nullable Sentinel getSentinel() {
7575
return null;
7676
}
7777

7878
/**
79-
* Redis cluster configuration. Mutually exclusive with {@link #getStandalone()} and
80-
* {@link #getSentinel()}.
79+
* Redis cluster configuration. Mutually exclusive with {@link #getStandalone()},
80+
* {@link #getSentinel()} and {@link #getMasterReplica()}.
8181
* @return the Redis cluster configuration
8282
*/
8383
default @Nullable Cluster getCluster() {
8484
return null;
8585
}
8686

87+
/**
88+
* Redis master replica configuration. Mutually exclusive with
89+
* {@link #getStandalone()}, {@link #getSentinel()} and {@link #getCluster()}.
90+
* @return the Redis master replica configuration
91+
*/
92+
default @Nullable MasterReplica getMasterReplica() {
93+
return null;
94+
}
95+
8796
/**
8897
* Redis standalone configuration.
8998
*/
@@ -201,6 +210,20 @@ interface Cluster {
201210

202211
}
203212

213+
/**
214+
* Redis master replica configuration.
215+
*/
216+
interface MasterReplica {
217+
218+
/**
219+
* Static nodes to use. This represents the full list of cluster nodes and is
220+
* required to have at least one entry.
221+
* @return the nodes to use
222+
*/
223+
List<Node> getNodes();
224+
225+
}
226+
204227
/**
205228
* A node in a sentinel or cluster configuration.
206229
*

module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/DataRedisProperties.java

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
* @author Stephane Nicoll
3535
* @author Scott Frederick
3636
* @author Yanming Zhou
37-
* @author Yong-Hyun Kim
3837
* @since 4.0.0
3938
*/
4039
@ConfigurationProperties("spring.data.redis")
@@ -95,6 +94,8 @@ public class DataRedisProperties {
9594

9695
private @Nullable Cluster cluster;
9796

97+
private @Nullable Masterreplica masterreplica;
98+
9899
private final Ssl ssl = new Ssl();
99100

100101
private final Jedis jedis = new Jedis();
@@ -201,6 +202,14 @@ public void setCluster(@Nullable Cluster cluster) {
201202
this.cluster = cluster;
202203
}
203204

205+
public @Nullable Masterreplica getMasterreplica() {
206+
return this.masterreplica;
207+
}
208+
209+
public void setMasterreplica(@Nullable Masterreplica masterreplica) {
210+
this.masterreplica = masterreplica;
211+
}
212+
204213
public Jedis getJedis() {
205214
return this.jedis;
206215
}
@@ -355,6 +364,26 @@ public void setMaxRedirects(@Nullable Integer maxRedirects) {
355364

356365
}
357366

367+
/**
368+
* Master Replica properties.
369+
*/
370+
public static class Masterreplica {
371+
372+
/**
373+
* Static list of "host:port" pairs to use, at least one entry is required.
374+
*/
375+
private @Nullable List<String> nodes;
376+
377+
public @Nullable List<String> getNodes() {
378+
return this.nodes;
379+
}
380+
381+
public void setNodes(@Nullable List<String> nodes) {
382+
this.nodes = nodes;
383+
}
384+
385+
}
386+
358387
/**
359388
* Redis sentinel properties.
360389
*/
@@ -483,20 +512,6 @@ public static class Lettuce {
483512

484513
private final Cluster cluster = new Cluster();
485514

486-
/**
487-
* List of static master-replica "host:port" pairs regardless of role
488-
* as the actual roles are determined by querying each node's ROLE command.
489-
*/
490-
private @Nullable List<String> nodes;
491-
492-
public @Nullable List<String> getNodes() {
493-
return this.nodes;
494-
}
495-
496-
public void setNodes(@Nullable List<String> nodes) {
497-
this.nodes = nodes;
498-
}
499-
500515
public Duration getShutdownTimeout() {
501516
return this.shutdownTimeout;
502517
}

module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/JedisConnectionConfiguration.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.data.redis.connection.RedisConnectionFactory;
3939
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
4040
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
41+
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
4142
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
4243
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
4344
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisSslClientConfigurationBuilder;
@@ -66,9 +67,10 @@ class JedisConnectionConfiguration extends DataRedisConnectionConfiguration {
6667
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
6768
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
6869
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
70+
ObjectProvider<RedisStaticMasterReplicaConfiguration> masterReplicaConfiguration,
6971
DataRedisConnectionDetails connectionDetails) {
7072
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfiguration,
71-
clusterConfiguration);
73+
clusterConfiguration, masterReplicaConfiguration);
7274
}
7375

7476
@Bean
@@ -104,7 +106,7 @@ private JedisConnectionFactory createJedisConnectionFactory(
104106
Assert.state(sentinelConfig != null, "'sentinelConfig' must not be null");
105107
yield new JedisConnectionFactory(sentinelConfig, clientConfiguration);
106108
}
107-
case STATIC_MASTER_REPLICA -> throw new IllegalStateException("Static master replica is not supported for Jedis");
109+
case MASTER_REPLICA -> throw new IllegalStateException("'masterReplicaConfig' is not supported by Jedis");
108110
};
109111
}
110112

module/spring-boot-data-redis/src/main/java/org/springframework/boot/data/redis/autoconfigure/LettuceConnectionConfiguration.java

Lines changed: 6 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.boot.data.redis.autoconfigure;
1818

1919
import java.time.Duration;
20-
import java.util.Collections;
21-
import java.util.List;
2220

2321
import io.lettuce.core.ClientOptions;
2422
import io.lettuce.core.ReadFrom;
@@ -39,8 +37,6 @@
3937
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4038
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
4139
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
42-
import org.springframework.boot.data.redis.autoconfigure.RedisConnectionDetails.Node;
43-
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Lettuce;
4440
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties.Lettuce.Cluster.Refresh;
4541
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties.Pool;
4642
import org.springframework.boot.ssl.SslBundle;
@@ -59,7 +55,6 @@
5955
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
6056
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
6157
import org.springframework.util.Assert;
62-
import org.springframework.util.CollectionUtils;
6358
import org.springframework.util.StringUtils;
6459

6560
/**
@@ -70,7 +65,6 @@
7065
* @author Moritz Halbritter
7166
* @author Phillip Webb
7267
* @author Scott Frederick
73-
* @author Yong-Hyun Kim
7468
*/
7569
@Configuration(proxyBeanMethods = false)
7670
@ConditionalOnClass(RedisClient.class)
@@ -81,9 +75,10 @@ class LettuceConnectionConfiguration extends DataRedisConnectionConfiguration {
8175
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
8276
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
8377
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
78+
ObjectProvider<RedisStaticMasterReplicaConfiguration> masterReplicaConfiguration,
8479
DataRedisConnectionDetails connectionDetails) {
8580
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfigurationProvider,
86-
clusterConfigurationProvider);
81+
clusterConfigurationProvider, masterReplicaConfiguration);
8782
}
8883

8984
@Bean(destroyMethod = "shutdown")
@@ -127,12 +122,6 @@ private LettuceConnectionFactory createConnectionFactory(
127122
LettuceClientConfiguration clientConfiguration = getLettuceClientConfiguration(
128123
clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, clientResources,
129124
getProperties().getLettuce().getPool());
130-
131-
RedisStaticMasterReplicaConfiguration staticMasterReplicaConfiguration = getStaticMasterReplicaConfiguration();
132-
if (staticMasterReplicaConfiguration != null) {
133-
return new LettuceConnectionFactory(staticMasterReplicaConfiguration, clientConfiguration);
134-
}
135-
136125
return switch (this.mode) {
137126
case STANDALONE -> new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
138127
case CLUSTER -> {
@@ -145,34 +134,14 @@ private LettuceConnectionFactory createConnectionFactory(
145134
Assert.state(sentinelConfig != null, "'sentinelConfig' must not be null");
146135
yield new LettuceConnectionFactory(sentinelConfig, clientConfiguration);
147136
}
148-
case STATIC_MASTER_REPLICA -> {
149-
RedisStaticMasterReplicaConfiguration configuration = getStaticMasterReplicaConfiguration();
150-
Assert.state(configuration != null, "'staticMasterReplicaConfiguration' must not be null");
151-
yield new LettuceConnectionFactory(configuration, clientConfiguration);
137+
case MASTER_REPLICA -> {
138+
RedisStaticMasterReplicaConfiguration masterReplicaConfiguration = getMasterReplicaConfiguration();
139+
Assert.state(masterReplicaConfiguration != null, "'masterReplicaConfig' must not be null");
140+
yield new LettuceConnectionFactory(masterReplicaConfiguration, clientConfiguration);
152141
}
153142
};
154143
}
155144

156-
private @Nullable RedisStaticMasterReplicaConfiguration getStaticMasterReplicaConfiguration() {
157-
RedisProperties.Lettuce lettuce = getProperties().getLettuce();
158-
159-
if (!CollectionUtils.isEmpty(lettuce.getNodes())) {
160-
List<Node> nodes = asNodes(lettuce.getNodes());
161-
RedisStaticMasterReplicaConfiguration configuration = new RedisStaticMasterReplicaConfiguration(
162-
nodes.get(0).host(), nodes.get(0).port());
163-
configuration.setUsername(getProperties().getUsername());
164-
if (StringUtils.hasText(getProperties().getPassword())) {
165-
configuration.setPassword(getProperties().getPassword());
166-
}
167-
configuration.setDatabase(getProperties().getDatabase());
168-
nodes.stream().skip(1).forEach((node) -> configuration.addNode(node.host(), node.port()));
169-
170-
return configuration;
171-
}
172-
173-
return null;
174-
}
175-
176145
private LettuceClientConfiguration getLettuceClientConfiguration(
177146
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
178147
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
@@ -288,20 +257,6 @@ private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceCli
288257
}
289258
}
290259

291-
private List<Node> asNodes(@Nullable List<String> nodes) {
292-
if (nodes == null) {
293-
return Collections.emptyList();
294-
}
295-
return nodes.stream().map(this::asNode).toList();
296-
}
297-
298-
private Node asNode(String node) {
299-
int portSeparatorIndex = node.lastIndexOf(':');
300-
String host = node.substring(0, portSeparatorIndex);
301-
int port = Integer.parseInt(node.substring(portSeparatorIndex + 1));
302-
return new Node(host, port);
303-
}
304-
305260
/**
306261
* Inner class to allow optional commons-pool2 dependency.
307262
*/

0 commit comments

Comments
 (0)