Polish "Add support for static master-replica with Lettuce"

See gh-46957
This commit is contained in:
Stéphane Nicoll 2025-10-01 11:18:55 +02:00
parent c280fdefe8
commit fbcc1fdec6
10 changed files with 222 additions and 110 deletions

View File

@ -75,7 +75,10 @@ spring:
TIP: You can also register an arbitrary number of beans that implement javadoc:org.springframework.boot.data.redis.autoconfigure.LettuceClientConfigurationBuilderCustomizer[] for more advanced customizations.
javadoc:io.lettuce.core.resource.ClientResources[] can also be customized using javadoc:org.springframework.boot.data.redis.autoconfigure.ClientResourcesBuilderCustomizer[].
If you use Jedis, javadoc:org.springframework.boot.data.redis.autoconfigure.JedisClientConfigurationBuilderCustomizer[] is also available.
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.
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.
NOTE: master/replica is not supported by Jedis.
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).

View File

@ -33,9 +33,9 @@ import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
/**
* Base Redis connection configuration.
@ -49,7 +49,6 @@ import org.springframework.util.CollectionUtils;
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yanming Zhou
* @author Yong-Hyun Kim
*/
abstract class DataRedisConnectionConfiguration {
@ -64,6 +63,8 @@ abstract class DataRedisConnectionConfiguration {
private final @Nullable RedisClusterConfiguration clusterConfiguration;
private final @Nullable RedisStaticMasterReplicaConfiguration masterReplicaConfiguration;
private final DataRedisConnectionDetails connectionDetails;
protected final Mode mode;
@ -72,11 +73,13 @@ abstract class DataRedisConnectionConfiguration {
DataRedisConnectionDetails connectionDetails,
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider) {
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<RedisStaticMasterReplicaConfiguration> masterReplicaConfiguration) {
this.properties = properties;
this.standaloneConfiguration = standaloneConfigurationProvider.getIfAvailable();
this.sentinelConfiguration = sentinelConfigurationProvider.getIfAvailable();
this.clusterConfiguration = clusterConfigurationProvider.getIfAvailable();
this.masterReplicaConfiguration = masterReplicaConfiguration.getIfAvailable();
this.connectionDetails = connectionDetails;
this.mode = determineMode();
}
@ -145,6 +148,25 @@ abstract class DataRedisConnectionConfiguration {
return null;
}
protected final @Nullable RedisStaticMasterReplicaConfiguration getMasterReplicaConfiguration() {
if (this.masterReplicaConfiguration != null) {
return this.masterReplicaConfiguration;
}
if (this.connectionDetails.getMasterReplica() != null) {
List<Node> nodes = this.connectionDetails.getMasterReplica().getNodes();
RedisStaticMasterReplicaConfiguration config = new RedisStaticMasterReplicaConfiguration(
nodes.get(0).host(), nodes.get(0).port());
nodes.stream().skip(1).forEach((node) -> config.addNode(node.host(), node.port()));
config.setUsername(this.connectionDetails.getUsername());
String password = this.connectionDetails.getPassword();
if (password != null) {
config.setPassword(RedisPassword.of(password));
}
return config;
}
return null;
}
private List<RedisNode> getNodes(Cluster cluster) {
return cluster.getNodes().stream().map(this::asRedisNode).toList();
}
@ -193,15 +215,15 @@ abstract class DataRedisConnectionConfiguration {
if (getClusterConfiguration() != null) {
return Mode.CLUSTER;
}
if (!CollectionUtils.isEmpty(this.properties.getLettuce().getNodes())) {
return Mode.STATIC_MASTER_REPLICA;
if (getMasterReplicaConfiguration() != null) {
return Mode.MASTER_REPLICA;
}
return Mode.STANDALONE;
}
enum Mode {
STANDALONE, CLUSTER, SENTINEL, STATIC_MASTER_REPLICA
STANDALONE, CLUSTER, MASTER_REPLICA, SENTINEL
}

View File

@ -58,8 +58,8 @@ public interface DataRedisConnectionDetails extends ConnectionDetails {
}
/**
* Redis standalone configuration. Mutually exclusive with {@link #getSentinel()} and
* {@link #getCluster()}.
* Redis standalone configuration. Mutually exclusive with {@link #getSentinel()},
* {@link #getCluster()} and {@link #getMasterReplica()}.
* @return the Redis standalone configuration
*/
default @Nullable Standalone getStandalone() {
@ -67,8 +67,8 @@ public interface DataRedisConnectionDetails extends ConnectionDetails {
}
/**
* Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()} and
* {@link #getCluster()}.
* Redis sentinel configuration. Mutually exclusive with {@link #getStandalone()},
* {@link #getCluster()} and {@link #getMasterReplica()}.
* @return the Redis sentinel configuration
*/
default @Nullable Sentinel getSentinel() {
@ -76,14 +76,23 @@ public interface DataRedisConnectionDetails extends ConnectionDetails {
}
/**
* Redis cluster configuration. Mutually exclusive with {@link #getStandalone()} and
* {@link #getSentinel()}.
* Redis cluster configuration. Mutually exclusive with {@link #getStandalone()},
* {@link #getSentinel()} and {@link #getMasterReplica()}.
* @return the Redis cluster configuration
*/
default @Nullable Cluster getCluster() {
return null;
}
/**
* Redis master replica configuration. Mutually exclusive with
* {@link #getStandalone()}, {@link #getSentinel()} and {@link #getCluster()}.
* @return the Redis master replica configuration
*/
default @Nullable MasterReplica getMasterReplica() {
return null;
}
/**
* Redis standalone configuration.
*/
@ -201,6 +210,20 @@ public interface DataRedisConnectionDetails extends ConnectionDetails {
}
/**
* Redis master replica configuration.
*/
interface MasterReplica {
/**
* Static nodes to use. This represents the full list of cluster nodes and is
* required to have at least one entry.
* @return the nodes to use
*/
List<Node> getNodes();
}
/**
* A node in a sentinel or cluster configuration.
*

View File

@ -34,7 +34,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* @author Stephane Nicoll
* @author Scott Frederick
* @author Yanming Zhou
* @author Yong-Hyun Kim
* @since 4.0.0
*/
@ConfigurationProperties("spring.data.redis")
@ -95,6 +94,8 @@ public class DataRedisProperties {
private @Nullable Cluster cluster;
private @Nullable Masterreplica masterreplica;
private final Ssl ssl = new Ssl();
private final Jedis jedis = new Jedis();
@ -201,6 +202,14 @@ public class DataRedisProperties {
this.cluster = cluster;
}
public @Nullable Masterreplica getMasterreplica() {
return this.masterreplica;
}
public void setMasterreplica(@Nullable Masterreplica masterreplica) {
this.masterreplica = masterreplica;
}
public Jedis getJedis() {
return this.jedis;
}
@ -355,6 +364,26 @@ public class DataRedisProperties {
}
/**
* Master Replica properties.
*/
public static class Masterreplica {
/**
* Static list of "host:port" pairs to use, at least one entry is required.
*/
private @Nullable List<String> nodes;
public @Nullable List<String> getNodes() {
return this.nodes;
}
public void setNodes(@Nullable List<String> nodes) {
this.nodes = nodes;
}
}
/**
* Redis sentinel properties.
*/
@ -483,20 +512,6 @@ public class DataRedisProperties {
private final Cluster cluster = new Cluster();
/**
* List of static master-replica "host:port" pairs regardless of role
* as the actual roles are determined by querying each node's ROLE command.
*/
private @Nullable List<String> nodes;
public @Nullable List<String> getNodes() {
return this.nodes;
}
public void setNodes(@Nullable List<String> nodes) {
this.nodes = nodes;
}
public Duration getShutdownTimeout() {
return this.shutdownTimeout;
}

View File

@ -38,6 +38,7 @@ import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisClientConfigurationBuilder;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration.JedisSslClientConfigurationBuilder;
@ -66,9 +67,10 @@ class JedisConnectionConfiguration extends DataRedisConnectionConfiguration {
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfiguration,
ObjectProvider<RedisClusterConfiguration> clusterConfiguration,
ObjectProvider<RedisStaticMasterReplicaConfiguration> masterReplicaConfiguration,
DataRedisConnectionDetails connectionDetails) {
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfiguration,
clusterConfiguration);
clusterConfiguration, masterReplicaConfiguration);
}
@Bean
@ -104,7 +106,7 @@ class JedisConnectionConfiguration extends DataRedisConnectionConfiguration {
Assert.state(sentinelConfig != null, "'sentinelConfig' must not be null");
yield new JedisConnectionFactory(sentinelConfig, clientConfiguration);
}
case STATIC_MASTER_REPLICA -> throw new IllegalStateException("Static master replica is not supported for Jedis");
case MASTER_REPLICA -> throw new IllegalStateException("'masterReplicaConfig' is not supported by Jedis");
};
}

View File

@ -17,8 +17,6 @@
package org.springframework.boot.data.redis.autoconfigure;
import java.time.Duration;
import java.util.Collections;
import java.util.List;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.ReadFrom;
@ -39,8 +37,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading;
import org.springframework.boot.data.redis.autoconfigure.RedisConnectionDetails.Node;
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Lettuce;
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties.Lettuce.Cluster.Refresh;
import org.springframework.boot.data.redis.autoconfigure.DataRedisProperties.Pool;
import org.springframework.boot.ssl.SslBundle;
@ -59,7 +55,6 @@ import org.springframework.data.redis.connection.lettuce.LettuceClientConfigurat
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
@ -70,7 +65,6 @@ import org.springframework.util.StringUtils;
* @author Moritz Halbritter
* @author Phillip Webb
* @author Scott Frederick
* @author Yong-Hyun Kim
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
@ -81,9 +75,10 @@ class LettuceConnectionConfiguration extends DataRedisConnectionConfiguration {
ObjectProvider<RedisStandaloneConfiguration> standaloneConfigurationProvider,
ObjectProvider<RedisSentinelConfiguration> sentinelConfigurationProvider,
ObjectProvider<RedisClusterConfiguration> clusterConfigurationProvider,
ObjectProvider<RedisStaticMasterReplicaConfiguration> masterReplicaConfiguration,
DataRedisConnectionDetails connectionDetails) {
super(properties, connectionDetails, standaloneConfigurationProvider, sentinelConfigurationProvider,
clusterConfigurationProvider);
clusterConfigurationProvider, masterReplicaConfiguration);
}
@Bean(destroyMethod = "shutdown")
@ -127,12 +122,6 @@ class LettuceConnectionConfiguration extends DataRedisConnectionConfiguration {
LettuceClientConfiguration clientConfiguration = getLettuceClientConfiguration(
clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, clientResources,
getProperties().getLettuce().getPool());
RedisStaticMasterReplicaConfiguration staticMasterReplicaConfiguration = getStaticMasterReplicaConfiguration();
if (staticMasterReplicaConfiguration != null) {
return new LettuceConnectionFactory(staticMasterReplicaConfiguration, clientConfiguration);
}
return switch (this.mode) {
case STANDALONE -> new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
case CLUSTER -> {
@ -145,34 +134,14 @@ class LettuceConnectionConfiguration extends DataRedisConnectionConfiguration {
Assert.state(sentinelConfig != null, "'sentinelConfig' must not be null");
yield new LettuceConnectionFactory(sentinelConfig, clientConfiguration);
}
case STATIC_MASTER_REPLICA -> {
RedisStaticMasterReplicaConfiguration configuration = getStaticMasterReplicaConfiguration();
Assert.state(configuration != null, "'staticMasterReplicaConfiguration' must not be null");
yield new LettuceConnectionFactory(configuration, clientConfiguration);
case MASTER_REPLICA -> {
RedisStaticMasterReplicaConfiguration masterReplicaConfiguration = getMasterReplicaConfiguration();
Assert.state(masterReplicaConfiguration != null, "'masterReplicaConfig' must not be null");
yield new LettuceConnectionFactory(masterReplicaConfiguration, clientConfiguration);
}
};
}
private @Nullable RedisStaticMasterReplicaConfiguration getStaticMasterReplicaConfiguration() {
RedisProperties.Lettuce lettuce = getProperties().getLettuce();
if (!CollectionUtils.isEmpty(lettuce.getNodes())) {
List<Node> nodes = asNodes(lettuce.getNodes());
RedisStaticMasterReplicaConfiguration configuration = new RedisStaticMasterReplicaConfiguration(
nodes.get(0).host(), nodes.get(0).port());
configuration.setUsername(getProperties().getUsername());
if (StringUtils.hasText(getProperties().getPassword())) {
configuration.setPassword(getProperties().getPassword());
}
configuration.setDatabase(getProperties().getDatabase());
nodes.stream().skip(1).forEach((node) -> configuration.addNode(node.host(), node.port()));
return configuration;
}
return null;
}
private LettuceClientConfiguration getLettuceClientConfiguration(
ObjectProvider<LettuceClientConfigurationBuilderCustomizer> clientConfigurationBuilderCustomizers,
ObjectProvider<LettuceClientOptionsBuilderCustomizer> clientOptionsBuilderCustomizers,
@ -288,20 +257,6 @@ class LettuceConnectionConfiguration extends DataRedisConnectionConfiguration {
}
}
private List<Node> asNodes(@Nullable List<String> nodes) {
if (nodes == null) {
return Collections.emptyList();
}
return nodes.stream().map(this::asNode).toList();
}
private Node asNode(String node) {
int portSeparatorIndex = node.lastIndexOf(':');
String host = node.substring(0, portSeparatorIndex);
int port = Integer.parseInt(node.substring(portSeparatorIndex + 1));
return new Node(host, port);
}
/**
* Inner class to allow optional commons-pool2 dependency.
*/

View File

@ -92,6 +92,12 @@ class PropertiesDataRedisConnectionDetails implements DataRedisConnectionDetails
return (cluster != null) ? new PropertiesCluster(cluster) : null;
}
@Override
public @Nullable MasterReplica getMasterReplica() {
DataRedisProperties.Masterreplica masterreplica = this.properties.getMasterreplica();
return (masterreplica != null) ? new PropertiesMasterReplica(masterreplica) : null;
}
private @Nullable DataRedisUrl getRedisUrl() {
return DataRedisUrl.of(this.properties.getUrl());
}
@ -128,6 +134,24 @@ class PropertiesDataRedisConnectionDetails implements DataRedisConnectionDetails
}
/**
* {@link MasterReplica} implementation backed by properties.
*/
private class PropertiesMasterReplica implements MasterReplica {
private final List<Node> nodes;
PropertiesMasterReplica(DataRedisProperties.Masterreplica properties) {
this.nodes = asNodes(properties.getNodes());
}
@Override
public List<Node> getNodes() {
return this.nodes;
}
}
/**
* {@link Sentinel} implementation backed by properties.
*/

View File

@ -247,6 +247,15 @@ class DataRedisAutoConfigurationJedisTests {
.isTrue());
}
@Test
void testRedisConfigurationWitMasterReplica() {
this.contextRunner.withPropertyValues("spring.data.redis.masterreplica.nodes=127.0.0.1:27379,127.0.0.1:27380")
.run((context) -> assertThat(context).hasFailed()
.getFailure()
.rootCause()
.hasMessageContaining("'masterReplicaConfig' is not supported by Jedis"));
}
@Test
void testRedisConfigurationWithSslEnabled() {
this.contextRunner.withPropertyValues("spring.data.redis.ssl.enabled:true").run((context) -> {

View File

@ -61,6 +61,7 @@ import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.RedisStaticMasterReplicaConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
@ -91,7 +92,6 @@ import static org.mockito.Mockito.mock;
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yong-Hyun Kim
*/
class DataRedisAutoConfigurationTests {
@ -497,40 +497,38 @@ class DataRedisAutoConfigurationTests {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(getUserName(connectionFactory)).isEqualTo("user");
assertThat(connectionFactory.getPassword()).isEqualTo("password");
}
);
}
@Test
void testRedisConfigurationWithStaticMasterReplica() {
List<String> staticMasterReplicaNodes = Arrays.asList("127.0.0.1:28319", "127.0.0.1:28320", "[::1]:28321");
this.contextRunner
.withPropertyValues(
"spring.data.redis.lettuce.static-master-replica.nodes[0]:" + staticMasterReplicaNodes.get(0),
"spring.data.redis.lettuce.static-master-replica.nodes[1]:" + staticMasterReplicaNodes.get(1),
"spring.data.redis.lettuce.static-master-replica.nodes[2]:" + staticMasterReplicaNodes.get(2))
.run((context) -> {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(connectionFactory.getSentinelConfiguration()).isNull();
assertThat(connectionFactory.getClusterConfiguration()).isNull();
assertThat(isStaticMasterReplicaAware(connectionFactory)).isTrue();
});
}
@Test
void testRedisConfigurationWithStaticMasterReplicaAndAuthenticationAndDatabase() {
List<String> staticMasterReplicaNodes = Arrays.asList("127.0.0.1:28319", "127.0.0.1:28320");
void testRedisConfigurationWithMasterReplica() {
this.contextRunner
.withPropertyValues("spring.data.redis.masterreplica.nodes=127.0.0.1:28319,127.0.0.1:28320,[::1]:28321")
.run((context) -> {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(connectionFactory.getSentinelConfiguration()).isNull();
assertThat(connectionFactory.getClusterConfiguration()).isNull();
assertThat(connectionFactory).extracting("configuration")
.isInstanceOfSatisfying(RedisStaticMasterReplicaConfiguration.class,
(masterReplicaConfiguration) -> assertThat(masterReplicaConfiguration.getNodes()
.stream()
.map((config) -> new RedisNode(config.getHostName(), config.getPort())))
.containsExactly(new RedisNode("127.0.0.1", 28319), new RedisNode("127.0.0.1", 28320),
new RedisNode("[::1]", 28321)));
});
}
@Test
void testRedisConfigurationWithMasterAndAuthentication() {
this.contextRunner
.withPropertyValues("spring.data.redis.username=user", "spring.data.redis.password=password",
"spring.data.redis.database=1",
"spring.data.redis.lettuce.static-master-replica.nodes[0]:" + staticMasterReplicaNodes.get(0),
"spring.data.redis.lettuce.static-master-replica.nodes[1]:" + staticMasterReplicaNodes.get(1))
"spring.data.redis.masterreplica.nodes=127.0.0.1:28319,127.0.0.1:28320")
.run((context) -> {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(getUserName(connectionFactory)).isEqualTo("user");
assertThat(connectionFactory.getPassword()).isEqualTo("password");
assertThat(connectionFactory.getDatabase()).isOne();
assertThat(connectionFactory).extracting("configuration")
.isInstanceOf(RedisStaticMasterReplicaConfiguration.class);
});
}
@ -661,6 +659,27 @@ class DataRedisAutoConfigurationTests {
});
}
@Test
void usesMasterReplicaFromCustomConnectionDetails() {
this.contextRunner.withUserConfiguration(ConnectionDetailsMasterReplicaConfiguration.class).run((context) -> {
assertThat(context).hasSingleBean(DataRedisConnectionDetails.class)
.doesNotHaveBean(PropertiesDataRedisConnectionDetails.class);
LettuceConnectionFactory cf = context.getBean(LettuceConnectionFactory.class);
assertThat(cf.isUseSsl()).isFalse();
assertThat(cf).extracting("configuration")
.isInstanceOfSatisfying(RedisStaticMasterReplicaConfiguration.class,
(masterReplicationConfiguration) -> {
assertThat(masterReplicationConfiguration.getUsername()).isEqualTo("user-1");
assertThat(masterReplicationConfiguration.getPassword().get())
.isEqualTo("password-1".toCharArray());
assertThat(masterReplicationConfiguration.getNodes())
.map((nodeConfiguration) -> new RedisNode(nodeConfiguration.getHostName(),
nodeConfiguration.getPort()))
.containsExactly(new RedisNode("node-1", 12345), new RedisNode("node-2", 23456));
});
});
}
@Test
void testRedisConfigurationWithSslEnabled() {
this.contextRunner.withPropertyValues("spring.data.redis.ssl.enabled:true").run((context) -> {
@ -738,10 +757,6 @@ class DataRedisAutoConfigurationTests {
return node;
}
private boolean isStaticMasterReplicaAware(LettuceConnectionFactory factory) {
return ReflectionTestUtils.invokeMethod(factory, "isStaticMasterReplicaAware");
}
private static final class RedisNodes implements Nodes {
private final List<RedisNodeDescription> descriptions;
@ -921,4 +936,38 @@ class DataRedisAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class ConnectionDetailsMasterReplicaConfiguration {
@Bean
DataRedisConnectionDetails redisConnectionDetails() {
return new DataRedisConnectionDetails() {
@Override
public String getUsername() {
return "user-1";
}
@Override
public String getPassword() {
return "password-1";
}
@Override
public MasterReplica getMasterReplica() {
return new MasterReplica() {
@Override
public List<Node> getNodes() {
return List.of(new Node("node-1", 12345), new Node("node-2", 23456));
}
};
}
};
}
}
}

View File

@ -57,6 +57,7 @@ class PropertiesRedisConnectionDetailsTests {
assertThat(standalone.getDatabase()).isEqualTo(0);
assertThat(this.connectionDetails.getSentinel()).isNull();
assertThat(this.connectionDetails.getCluster()).isNull();
assertThat(this.connectionDetails.getMasterReplica()).isNull();
assertThat(this.connectionDetails.getUsername()).isNull();
assertThat(this.connectionDetails.getPassword()).isNull();
}
@ -147,6 +148,15 @@ class PropertiesRedisConnectionDetailsTests {
new Node("127.0.0.1", 2222), new Node("[::1]", 3333));
}
@Test
void masterReplicaIsConfigured() {
DataRedisProperties.Masterreplica masterReplica = new DataRedisProperties.Masterreplica();
masterReplica.setNodes(List.of("localhost:1111", "127.0.0.1:2222", "[::1]:3333"));
this.properties.setMasterreplica(masterReplica);
assertThat(this.connectionDetails.getMasterReplica().getNodes()).containsExactly(new Node("localhost", 1111),
new Node("127.0.0.1", 2222), new Node("[::1]", 3333));
}
@Test
void sentinelIsConfigured() {
DataRedisProperties.Sentinel sentinel = new DataRedisProperties.Sentinel();