Add configuration to enable Redis Cluster topology refresh
This commit adds two options to enable a refresh of the cluster topology using Lettuce. Closes gh-15630
This commit is contained in:
parent
d8cead5457
commit
dfac3a282b
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -18,7 +18,12 @@ package org.springframework.boot.autoconfigure.data.redis;
|
|||
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
import io.lettuce.core.ClientOptions;
|
||||
import io.lettuce.core.RedisClient;
|
||||
import io.lettuce.core.TimeoutOptions;
|
||||
import io.lettuce.core.cluster.ClusterClientOptions;
|
||||
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
|
||||
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.Builder;
|
||||
import io.lettuce.core.resource.ClientResources;
|
||||
import io.lettuce.core.resource.DefaultClientResources;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
|
|
@ -26,6 +31,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
|||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh;
|
||||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
@ -88,6 +94,7 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
|
|||
if (StringUtils.hasText(getProperties().getUrl())) {
|
||||
customizeConfigurationFromUrl(builder);
|
||||
}
|
||||
builder.clientOptions(initializeClientOptionsBuilder().timeoutOptions(TimeoutOptions.enabled()).build());
|
||||
builder.clientResources(clientResources);
|
||||
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||
return builder.build();
|
||||
|
|
@ -120,6 +127,22 @@ class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
|
|||
return builder;
|
||||
}
|
||||
|
||||
private ClientOptions.Builder initializeClientOptionsBuilder() {
|
||||
if (getProperties().getCluster() != null) {
|
||||
ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
|
||||
Refresh refreshProperties = getProperties().getLettuce().getCluster().getRefresh();
|
||||
Builder refreshBuilder = ClusterTopologyRefreshOptions.builder();
|
||||
if (refreshProperties.getPeriod() != null) {
|
||||
refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
|
||||
}
|
||||
if (refreshProperties.isAdaptive()) {
|
||||
refreshBuilder.enableAllAdaptiveRefreshTriggers();
|
||||
}
|
||||
return builder.topologyRefreshOptions(refreshBuilder.build());
|
||||
}
|
||||
return ClientOptions.builder();
|
||||
}
|
||||
|
||||
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
|
||||
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
|
||||
if (connectionInfo.isUseSsl()) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -354,6 +354,8 @@ public class RedisProperties {
|
|||
*/
|
||||
private Pool pool;
|
||||
|
||||
private final Cluster cluster = new Cluster();
|
||||
|
||||
public Duration getShutdownTimeout() {
|
||||
return this.shutdownTimeout;
|
||||
}
|
||||
|
|
@ -370,6 +372,51 @@ public class RedisProperties {
|
|||
this.pool = pool;
|
||||
}
|
||||
|
||||
public Cluster getCluster() {
|
||||
return this.cluster;
|
||||
}
|
||||
|
||||
public static class Cluster {
|
||||
|
||||
private final Refresh refresh = new Refresh();
|
||||
|
||||
public Refresh getRefresh() {
|
||||
return this.refresh;
|
||||
}
|
||||
|
||||
public static class Refresh {
|
||||
|
||||
/**
|
||||
* Cluster topology refresh period.
|
||||
*/
|
||||
private Duration period;
|
||||
|
||||
/**
|
||||
* Whether adaptive topology refreshing using all available refresh
|
||||
* triggers should be used.
|
||||
*/
|
||||
private boolean adaptive;
|
||||
|
||||
public Duration getPeriod() {
|
||||
return this.period;
|
||||
}
|
||||
|
||||
public void setPeriod(Duration period) {
|
||||
this.period = period;
|
||||
}
|
||||
|
||||
public boolean isAdaptive() {
|
||||
return this.adaptive;
|
||||
}
|
||||
|
||||
public void setAdaptive(boolean adaptive) {
|
||||
this.adaptive = adaptive;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
|
@ -17,19 +17,27 @@
|
|||
package org.springframework.boot.autoconfigure.data.redis;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.lettuce.core.ClientOptions;
|
||||
import io.lettuce.core.cluster.ClusterClientOptions;
|
||||
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.RefreshTrigger;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
import org.springframework.boot.test.context.runner.ContextConsumer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisClusterConfiguration;
|
||||
import org.springframework.data.redis.connection.RedisNode;
|
||||
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;
|
||||
|
|
@ -54,7 +62,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||
*/
|
||||
class RedisAutoConfigurationTests {
|
||||
|
||||
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
|
||||
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class));
|
||||
|
||||
@Test
|
||||
|
|
@ -230,6 +238,61 @@ class RedisAutoConfigurationTests {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedisConfigurationCreateClientOptionsByDefault() {
|
||||
this.contextRunner.run(assertClientOptions(ClientOptions.class, (options) -> {
|
||||
assertThat(options.getTimeoutOptions().isApplyConnectionTimeout()).isTrue();
|
||||
assertThat(options.getTimeoutOptions().isTimeoutCommands()).isTrue();
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedisConfigurationWithClusterCreateClusterClientOptions() {
|
||||
this.contextRunner.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380")
|
||||
.run(assertClientOptions(ClusterClientOptions.class, (options) -> {
|
||||
assertThat(options.getTimeoutOptions().isApplyConnectionTimeout()).isTrue();
|
||||
assertThat(options.getTimeoutOptions().isTimeoutCommands()).isTrue();
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedisConfigurationWithClusterRefreshPeriod() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380",
|
||||
"spring.redis.lettuce.cluster.refresh.period=30s")
|
||||
.run(assertClientOptions(ClusterClientOptions.class,
|
||||
(options) -> assertThat(options.getTopologyRefreshOptions().getRefreshPeriod())
|
||||
.hasSeconds(30)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedisConfigurationWithClusterAdaptiveRefresh() {
|
||||
this.contextRunner
|
||||
.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380",
|
||||
"spring.redis.lettuce.cluster.refresh.adaptive=true")
|
||||
.run(assertClientOptions(ClusterClientOptions.class,
|
||||
(options) -> assertThat(options.getTopologyRefreshOptions().getAdaptiveRefreshTriggers())
|
||||
.isEqualTo(EnumSet.allOf(RefreshTrigger.class))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRedisConfigurationWithClusterRefreshPeriodHasNoEffectWithNonClusteredConfiguration() {
|
||||
this.contextRunner.withPropertyValues("spring.redis.cluster.refresh.period=30s").run(assertClientOptions(
|
||||
ClientOptions.class, (options) -> assertThat(options.getClass()).isEqualTo(ClientOptions.class)));
|
||||
}
|
||||
|
||||
private <T extends ClientOptions> ContextConsumer<AssertableApplicationContext> assertClientOptions(
|
||||
Class<T> expectedType, Consumer<T> options) {
|
||||
return (context) -> {
|
||||
LettuceClientConfiguration clientConfiguration = context.getBean(LettuceConnectionFactory.class)
|
||||
.getClientConfiguration();
|
||||
assertThat(clientConfiguration.getClientOptions()).isPresent();
|
||||
ClientOptions clientOptions = clientConfiguration.getClientOptions().get();
|
||||
assertThat(clientOptions.getClass()).isEqualTo(expectedType);
|
||||
options.accept(expectedType.cast(clientOptions));
|
||||
};
|
||||
}
|
||||
|
||||
private LettucePoolingClientConfiguration getPoolingClientConfiguration(LettuceConnectionFactory factory) {
|
||||
return (LettucePoolingClientConfiguration) ReflectionTestUtils.getField(factory, "clientConfiguration");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue