Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
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;
Expand All @@ -37,7 +39,9 @@
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.Cluster.Refresh;
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Lettuce.StaticMasterReplica;
import org.springframework.boot.data.redis.autoconfigure.RedisProperties.Pool;
import org.springframework.boot.ssl.SslBundle;
import org.springframework.boot.ssl.SslOptions;
Expand All @@ -49,11 +53,13 @@
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.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
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;

/**
Expand All @@ -64,6 +70,7 @@
* @author Moritz Halbritter
* @author Phillip Webb
* @author Scott Frederick
* @author Yong-Hyun Kim
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisClient.class)
Expand Down Expand Up @@ -120,6 +127,12 @@ private LettuceConnectionFactory createConnectionFactory(
LettuceClientConfiguration clientConfiguration = getLettuceClientConfiguration(
clientConfigurationBuilderCustomizers, clientOptionsBuilderCustomizers, clientResources,
getProperties().getLettuce().getPool());

RedisStaticMasterReplicaConfiguration staticMasterReplicaConfiguration = getStaticMasterReplicaConfiguration();
if (staticMasterReplicaConfiguration != null) {
return new LettuceConnectionFactory(staticMasterReplicaConfiguration, clientConfiguration);
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the code move to a new Mode value. It feels odd that there is a if check right before we switch over all the supported modes.

return switch (this.mode) {
case STANDALONE -> new LettuceConnectionFactory(getStandaloneConfig(), clientConfiguration);
case CLUSTER -> {
Expand All @@ -135,6 +148,26 @@ private LettuceConnectionFactory createConnectionFactory(
};
}

private @Nullable RedisStaticMasterReplicaConfiguration getStaticMasterReplicaConfiguration() {
StaticMasterReplica staticMasterReplica = getProperties().getLettuce().getStaticMasterReplica();

if (!CollectionUtils.isEmpty(staticMasterReplica.getNodes())) {
List<Node> nodes = asNodes(staticMasterReplica.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,
Expand Down Expand Up @@ -250,6 +283,20 @@ private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceCli
}
}

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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
* @author Stephane Nicoll
* @author Scott Frederick
* @author Yanming Zhou
* @author Yong-Hyun Kim
* @since 4.0.0
*/
@ConfigurationProperties("spring.data.redis")
Expand Down Expand Up @@ -482,6 +483,8 @@ public static class Lettuce {

private final Cluster cluster = new Cluster();

private final StaticMasterReplica staticMasterReplica = new StaticMasterReplica();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we could name this Masterreplica. Groups should not use camel case as we don't want hyphens in them.


public Duration getShutdownTimeout() {
return this.shutdownTimeout;
}
Expand All @@ -506,6 +509,10 @@ public Cluster getCluster() {
return this.cluster;
}

public StaticMasterReplica getStaticMasterReplica() {
return this.staticMasterReplica;
}

public static class Cluster {

private final Refresh refresh = new Refresh();
Expand Down Expand Up @@ -562,6 +569,27 @@ public void setAdaptive(boolean adaptive) {

}

/**
* Lettuce static master-replica properties.
*/
public static class StaticMasterReplica {

/**
* List of "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;
}

}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
* @author Moritz Halbritter
* @author Andy Wilkinson
* @author Phillip Webb
* @author Yong-Hyun Kim
*/
class RedisAutoConfigurationTests {

Expand Down Expand Up @@ -501,6 +502,38 @@ void testRedisConfigurationWithClusterAndAuthentication() {
);
}

@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");
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))
.run((context) -> {
LettuceConnectionFactory connectionFactory = context.getBean(LettuceConnectionFactory.class);
assertThat(getUserName(connectionFactory)).isEqualTo("user");
assertThat(connectionFactory.getPassword()).isEqualTo("password");
assertThat(connectionFactory.getDatabase()).isOne();
});
}

@Test
void testRedisConfigurationCreateClientOptionsByDefault() {
this.contextRunner.run(assertClientOptions(ClientOptions.class, (options) -> {
Expand Down Expand Up @@ -704,6 +737,10 @@ private RedisClusterNode createRedisNode(String host) {
return node;
}

private boolean isStaticMasterReplicaAware(LettuceConnectionFactory factory) {
return ReflectionTestUtils.invokeMethod(factory, "isStaticMasterReplicaAware");
}

private static final class RedisNodes implements Nodes {

private final List<RedisNodeDescription> descriptions;
Expand Down
Loading