Skip to content

Commit dfac3a2

Browse files
committed
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
1 parent d8cead5 commit dfac3a2

File tree

3 files changed

+137
-4
lines changed

3 files changed

+137
-4
lines changed

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,14 +18,20 @@
1818

1919
import java.net.UnknownHostException;
2020

21+
import io.lettuce.core.ClientOptions;
2122
import io.lettuce.core.RedisClient;
23+
import io.lettuce.core.TimeoutOptions;
24+
import io.lettuce.core.cluster.ClusterClientOptions;
25+
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions;
26+
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.Builder;
2227
import io.lettuce.core.resource.ClientResources;
2328
import io.lettuce.core.resource.DefaultClientResources;
2429
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
2530

2631
import org.springframework.beans.factory.ObjectProvider;
2732
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2833
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
34+
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh;
2935
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
3036
import org.springframework.context.annotation.Bean;
3137
import org.springframework.context.annotation.Configuration;
@@ -88,6 +94,7 @@ private LettuceClientConfiguration getLettuceClientConfiguration(
8894
if (StringUtils.hasText(getProperties().getUrl())) {
8995
customizeConfigurationFromUrl(builder);
9096
}
97+
builder.clientOptions(initializeClientOptionsBuilder().timeoutOptions(TimeoutOptions.enabled()).build());
9198
builder.clientResources(clientResources);
9299
builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
93100
return builder.build();
@@ -120,6 +127,22 @@ private LettuceClientConfigurationBuilder applyProperties(
120127
return builder;
121128
}
122129

130+
private ClientOptions.Builder initializeClientOptionsBuilder() {
131+
if (getProperties().getCluster() != null) {
132+
ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
133+
Refresh refreshProperties = getProperties().getLettuce().getCluster().getRefresh();
134+
Builder refreshBuilder = ClusterTopologyRefreshOptions.builder();
135+
if (refreshProperties.getPeriod() != null) {
136+
refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
137+
}
138+
if (refreshProperties.isAdaptive()) {
139+
refreshBuilder.enableAllAdaptiveRefreshTriggers();
140+
}
141+
return builder.topologyRefreshOptions(refreshBuilder.build());
142+
}
143+
return ClientOptions.builder();
144+
}
145+
123146
private void customizeConfigurationFromUrl(LettuceClientConfiguration.LettuceClientConfigurationBuilder builder) {
124147
ConnectionInfo connectionInfo = parseUrl(getProperties().getUrl());
125148
if (connectionInfo.isUseSsl()) {

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -354,6 +354,8 @@ public static class Lettuce {
354354
*/
355355
private Pool pool;
356356

357+
private final Cluster cluster = new Cluster();
358+
357359
public Duration getShutdownTimeout() {
358360
return this.shutdownTimeout;
359361
}
@@ -370,6 +372,51 @@ public void setPool(Pool pool) {
370372
this.pool = pool;
371373
}
372374

375+
public Cluster getCluster() {
376+
return this.cluster;
377+
}
378+
379+
public static class Cluster {
380+
381+
private final Refresh refresh = new Refresh();
382+
383+
public Refresh getRefresh() {
384+
return this.refresh;
385+
}
386+
387+
public static class Refresh {
388+
389+
/**
390+
* Cluster topology refresh period.
391+
*/
392+
private Duration period;
393+
394+
/**
395+
* Whether adaptive topology refreshing using all available refresh
396+
* triggers should be used.
397+
*/
398+
private boolean adaptive;
399+
400+
public Duration getPeriod() {
401+
return this.period;
402+
}
403+
404+
public void setPeriod(Duration period) {
405+
this.period = period;
406+
}
407+
408+
public boolean isAdaptive() {
409+
return this.adaptive;
410+
}
411+
412+
public void setAdaptive(boolean adaptive) {
413+
this.adaptive = adaptive;
414+
}
415+
416+
}
417+
418+
}
419+
373420
}
374421

375422
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,19 +17,27 @@
1717
package org.springframework.boot.autoconfigure.data.redis;
1818

1919
import java.util.Arrays;
20+
import java.util.EnumSet;
2021
import java.util.List;
2122
import java.util.Set;
23+
import java.util.function.Consumer;
2224
import java.util.stream.Collectors;
2325

26+
import io.lettuce.core.ClientOptions;
27+
import io.lettuce.core.cluster.ClusterClientOptions;
28+
import io.lettuce.core.cluster.ClusterTopologyRefreshOptions.RefreshTrigger;
2429
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
2530
import org.junit.jupiter.api.Test;
2631

2732
import org.springframework.boot.autoconfigure.AutoConfigurations;
33+
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
2834
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
35+
import org.springframework.boot.test.context.runner.ContextConsumer;
2936
import org.springframework.context.annotation.Bean;
3037
import org.springframework.context.annotation.Configuration;
3138
import org.springframework.data.redis.connection.RedisClusterConfiguration;
3239
import org.springframework.data.redis.connection.RedisNode;
40+
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
3341
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration.LettuceClientConfigurationBuilder;
3442
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
3543
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
@@ -54,7 +62,7 @@
5462
*/
5563
class RedisAutoConfigurationTests {
5664

57-
private ApplicationContextRunner contextRunner = new ApplicationContextRunner()
65+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner()
5866
.withConfiguration(AutoConfigurations.of(RedisAutoConfiguration.class));
5967

6068
@Test
@@ -230,6 +238,61 @@ void testRedisConfigurationWithClusterAndPassword() {
230238
);
231239
}
232240

241+
@Test
242+
void testRedisConfigurationCreateClientOptionsByDefault() {
243+
this.contextRunner.run(assertClientOptions(ClientOptions.class, (options) -> {
244+
assertThat(options.getTimeoutOptions().isApplyConnectionTimeout()).isTrue();
245+
assertThat(options.getTimeoutOptions().isTimeoutCommands()).isTrue();
246+
}));
247+
}
248+
249+
@Test
250+
void testRedisConfigurationWithClusterCreateClusterClientOptions() {
251+
this.contextRunner.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380")
252+
.run(assertClientOptions(ClusterClientOptions.class, (options) -> {
253+
assertThat(options.getTimeoutOptions().isApplyConnectionTimeout()).isTrue();
254+
assertThat(options.getTimeoutOptions().isTimeoutCommands()).isTrue();
255+
}));
256+
}
257+
258+
@Test
259+
void testRedisConfigurationWithClusterRefreshPeriod() {
260+
this.contextRunner
261+
.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380",
262+
"spring.redis.lettuce.cluster.refresh.period=30s")
263+
.run(assertClientOptions(ClusterClientOptions.class,
264+
(options) -> assertThat(options.getTopologyRefreshOptions().getRefreshPeriod())
265+
.hasSeconds(30)));
266+
}
267+
268+
@Test
269+
void testRedisConfigurationWithClusterAdaptiveRefresh() {
270+
this.contextRunner
271+
.withPropertyValues("spring.redis.cluster.nodes=127.0.0.1:27379,127.0.0.1:27380",
272+
"spring.redis.lettuce.cluster.refresh.adaptive=true")
273+
.run(assertClientOptions(ClusterClientOptions.class,
274+
(options) -> assertThat(options.getTopologyRefreshOptions().getAdaptiveRefreshTriggers())
275+
.isEqualTo(EnumSet.allOf(RefreshTrigger.class))));
276+
}
277+
278+
@Test
279+
void testRedisConfigurationWithClusterRefreshPeriodHasNoEffectWithNonClusteredConfiguration() {
280+
this.contextRunner.withPropertyValues("spring.redis.cluster.refresh.period=30s").run(assertClientOptions(
281+
ClientOptions.class, (options) -> assertThat(options.getClass()).isEqualTo(ClientOptions.class)));
282+
}
283+
284+
private <T extends ClientOptions> ContextConsumer<AssertableApplicationContext> assertClientOptions(
285+
Class<T> expectedType, Consumer<T> options) {
286+
return (context) -> {
287+
LettuceClientConfiguration clientConfiguration = context.getBean(LettuceConnectionFactory.class)
288+
.getClientConfiguration();
289+
assertThat(clientConfiguration.getClientOptions()).isPresent();
290+
ClientOptions clientOptions = clientConfiguration.getClientOptions().get();
291+
assertThat(clientOptions.getClass()).isEqualTo(expectedType);
292+
options.accept(expectedType.cast(clientOptions));
293+
};
294+
}
295+
233296
private LettucePoolingClientConfiguration getPoolingClientConfiguration(LettuceConnectionFactory factory) {
234297
return (LettucePoolingClientConfiguration) ReflectionTestUtils.getField(factory, "clientConfiguration");
235298
}

0 commit comments

Comments
 (0)