From fc227314203bc62bccc480be5fe7228ebf4f4b93 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 11 Feb 2016 12:38:13 +0100 Subject: [PATCH 1/2] Add Redis Cluster support Introduce configuration options for "spring.redis.cluster.nodes" and "spring.redis.cluster.max-redirects". Properties such as "timeout" and others remain available via "spring.redis.timeout" and do not have to be configured on the cluster itself. See gh-5128 --- .../data/redis/RedisAutoConfiguration.java | 63 +++++++++++++++++-- .../data/redis/RedisProperties.java | 50 ++++++++++++++- .../redis/RedisAutoConfigurationTests.java | 25 ++++++-- .../appendix-application-properties.adoc | 2 + 4 files changed, 128 insertions(+), 12 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index bb5e38439a1..5f125b6fddc 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -29,10 +29,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Cluster; import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel; import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -76,6 +78,9 @@ public class RedisAutoConfiguration { @Autowired(required = false) private RedisSentinelConfiguration sentinelConfiguration; + @Autowired(required = false) + private RedisClusterConfiguration clusterConfiguration; + protected final JedisConnectionFactory applyProperties( JedisConnectionFactory factory) { factory.setHostName(this.properties.getHost()); @@ -104,6 +109,34 @@ public class RedisAutoConfiguration { return null; } + /** + * Create {@link RedisClusterConfiguration} from {@link RedisProperties.Cluster}. + * + * + * @return {@literal null} if no {@link RedisProperties.Cluster} set. + * + */ + protected final RedisClusterConfiguration getClusterConfiguration() { + + if (this.clusterConfiguration != null) { + return this.clusterConfiguration; + } + + if (this.properties.getCluster() == null) { + return null; + } + + Cluster clusterProperties = this.properties.getCluster(); + RedisClusterConfiguration config = new RedisClusterConfiguration( + clusterProperties.getNodes()); + + if (clusterProperties.getMaxRedirects() != null) { + config.setMaxRedirects(config.getMaxRedirects().intValue()); + } + + return config; + } + private List createSentinels(Sentinel sentinel) { List sentinels = new ArrayList(); String nodes = sentinel.getNodes(); @@ -135,9 +168,21 @@ public class RedisAutoConfiguration { @ConditionalOnMissingBean(RedisConnectionFactory.class) public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException { - return applyProperties(new JedisConnectionFactory(getSentinelConfig())); + return applyProperties(createJedisConnectionFactory()); } + private JedisConnectionFactory createJedisConnectionFactory() { + + if (getSentinelConfig() != null) { + return new JedisConnectionFactory(getSentinelConfig()); + } + + if (getClusterConfiguration() != null) { + return new JedisConnectionFactory(getClusterConfiguration()); + } + + return new JedisConnectionFactory(); + } } /** @@ -156,10 +201,18 @@ public class RedisAutoConfiguration { } private JedisConnectionFactory createJedisConnectionFactory() { - if (this.properties.getPool() != null) { - return new JedisConnectionFactory(getSentinelConfig(), jedisPoolConfig()); + + JedisPoolConfig poolConfig = this.properties.getPool() != null ? jedisPoolConfig() + : new JedisPoolConfig(); + + if (getSentinelConfig() != null) { + return new JedisConnectionFactory(getSentinelConfig(), poolConfig); + } + if (getClusterConfiguration() != null) { + return new JedisConnectionFactory(getClusterConfiguration(), poolConfig); } - return new JedisConnectionFactory(getSentinelConfig()); + + return new JedisConnectionFactory(poolConfig); } private JedisPoolConfig jedisPoolConfig() { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index 08f778f48fe..0f0ebf0ac57 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.data.redis; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -57,6 +59,8 @@ public class RedisProperties { private Sentinel sentinel; + private Cluster cluster; + public int getDatabase() { return this.database; } @@ -113,6 +117,14 @@ public class RedisProperties { this.pool = pool; } + public Cluster getCluster() { + return cluster; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + /** * Pool properties. */ @@ -176,6 +188,42 @@ public class RedisProperties { } } + /** + * Cluster properties. + * + */ + public static class Cluster { + + /** + * List of host:port pairs. This setting points to an "initial" list of cluster + * nodes and is required to have at least one entry. + */ + private List nodes; + + /** + * Maximum number of "redirects". Limits the number of redirects to follow when + * executing commands across the cluster. Leave empty to use driver specific + * settings. + */ + private Integer maxRedirects; + + public List getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public Integer getMaxRedirects() { + return maxRedirects; + } + + public void setMaxRedirects(Integer maxRedirects) { + this.maxRedirects = maxRedirects; + } + } + /** * Redis sentinel properties. */ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index 8249745256b..225c8b8b876 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -96,7 +96,7 @@ public class RedisAutoConfigurationTests { @Test public void testRedisConfigurationWithSentinel() throws Exception { List sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); - if (isAtLeastOneSentinelAvailable(sentinels)) { + if (isAtLeastOneNodeAvailable(sentinels)) { load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:" + StringUtils.collectionToCommaDelimitedString(sentinels)); assertThat(this.context.getBean(JedisConnectionFactory.class) @@ -104,9 +104,22 @@ public class RedisAutoConfigurationTests { } } - private boolean isAtLeastOneSentinelAvailable(List sentinels) { - for (String sentinel : sentinels) { - if (isSentinelAvailable(sentinel)) { + @Test + public void testRedisConfigurationWithCluster() throws Exception { + + List clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); + if (isAtLeastOneNodeAvailable(clusterNodes)) { + load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), + "spring.redis.cluster.nodes[1]:" + clusterNodes.get(1)); + assertThat( + this.context.getBean(JedisConnectionFactory.class) + .getClusterConnection()).isNotNull(); + } + } + + private boolean isAtLeastOneNodeAvailable(List nodes) { + for (String node : nodes) { + if (isAvailable(node)) { return true; } } @@ -114,7 +127,7 @@ public class RedisAutoConfigurationTests { return false; } - private boolean isSentinelAvailable(String node) { + private boolean isAvailable(String node) { Jedis jedis = null; try { String[] hostAndPort = node.split(":"); diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index fdd2689a6bb..3501a3b5d30 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -685,6 +685,8 @@ content into your application; rather pick only the properties that you need. spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use. # REDIS ({sc-spring-boot-autoconfigure}/redis/RedisProperties.{sc-ext}[RedisProperties]) + spring.redis.cluster.nodes= # List of host:port pairs pointing to an intial collection of cluster nodes. Requires at least one node to connect to the cluster. + spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster. Leave empty to use the driver specific value. spring.redis.database=0 # Database index used by the connection factory. spring.redis.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server. From a95568d3e8ef96c3a37e1ca675facded0cb16b9c Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 18 Feb 2016 15:08:57 +0100 Subject: [PATCH 2/2] Polish contribution Closes gh-5128 --- .../data/redis/RedisAutoConfiguration.java | 27 +++++-------------- .../data/redis/RedisProperties.java | 19 ++++++------- .../redis/RedisAutoConfigurationTests.java | 1 - .../appendix-application-properties.adoc | 4 +-- 4 files changed, 19 insertions(+), 32 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index 5f125b6fddc..f553e891994 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java @@ -110,48 +110,40 @@ public class RedisAutoConfiguration { } /** - * Create {@link RedisClusterConfiguration} from {@link RedisProperties.Cluster}. - * - * - * @return {@literal null} if no {@link RedisProperties.Cluster} set. - * + * Create a {@link RedisClusterConfiguration} if necessary. + * @return {@literal null} if no cluster settings are set. */ protected final RedisClusterConfiguration getClusterConfiguration() { - if (this.clusterConfiguration != null) { return this.clusterConfiguration; } - if (this.properties.getCluster() == null) { return null; } - Cluster clusterProperties = this.properties.getCluster(); RedisClusterConfiguration config = new RedisClusterConfiguration( clusterProperties.getNodes()); if (clusterProperties.getMaxRedirects() != null) { - config.setMaxRedirects(config.getMaxRedirects().intValue()); + config.setMaxRedirects(config.getMaxRedirects()); } - return config; } private List createSentinels(Sentinel sentinel) { - List sentinels = new ArrayList(); - String nodes = sentinel.getNodes(); - for (String node : StringUtils.commaDelimitedListToStringArray(nodes)) { + List nodes = new ArrayList(); + for (String node : StringUtils.commaDelimitedListToStringArray(sentinel.getNodes())) { try { String[] parts = StringUtils.split(node, ":"); Assert.state(parts.length == 2, "Must be defined as 'host:port'"); - sentinels.add(new RedisNode(parts[0], Integer.valueOf(parts[1]))); + nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1]))); } catch (RuntimeException ex) { throw new IllegalStateException( "Invalid redis sentinel " + "property '" + node + "'", ex); } } - return sentinels; + return nodes; } } @@ -172,15 +164,12 @@ public class RedisAutoConfiguration { } private JedisConnectionFactory createJedisConnectionFactory() { - if (getSentinelConfig() != null) { return new JedisConnectionFactory(getSentinelConfig()); } - if (getClusterConfiguration() != null) { return new JedisConnectionFactory(getClusterConfiguration()); } - return new JedisConnectionFactory(); } } @@ -201,7 +190,6 @@ public class RedisAutoConfiguration { } private JedisConnectionFactory createJedisConnectionFactory() { - JedisPoolConfig poolConfig = this.properties.getPool() != null ? jedisPoolConfig() : new JedisPoolConfig(); @@ -211,7 +199,6 @@ public class RedisAutoConfiguration { if (getClusterConfiguration() != null) { return new JedisConnectionFactory(getClusterConfiguration(), poolConfig); } - return new JedisConnectionFactory(poolConfig); } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index 0f0ebf0ac57..162e2ebdf51 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -118,7 +118,7 @@ public class RedisProperties { } public Cluster getCluster() { - return cluster; + return this.cluster; } public void setCluster(Cluster cluster) { @@ -186,29 +186,28 @@ public class RedisProperties { public void setMaxWait(int maxWait) { this.maxWait = maxWait; } + } /** * Cluster properties. - * */ public static class Cluster { /** - * List of host:port pairs. This setting points to an "initial" list of cluster - * nodes and is required to have at least one entry. + * Comma-separated list of "host:port" pairs to bootstrap from. This represents + * an "initial" list of cluster nodes and is required to have at least one entry. */ private List nodes; /** - * Maximum number of "redirects". Limits the number of redirects to follow when - * executing commands across the cluster. Leave empty to use driver specific - * settings. + * Maximum number of redirects to follow when executing commands across the + * cluster. */ private Integer maxRedirects; public List getNodes() { - return nodes; + return this.nodes; } public void setNodes(List nodes) { @@ -216,12 +215,13 @@ public class RedisProperties { } public Integer getMaxRedirects() { - return maxRedirects; + return this.maxRedirects; } public void setMaxRedirects(Integer maxRedirects) { this.maxRedirects = maxRedirects; } + } /** @@ -254,5 +254,6 @@ public class RedisProperties { public void setNodes(String nodes) { this.nodes = nodes; } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index 225c8b8b876..e91f1b057f0 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -106,7 +106,6 @@ public class RedisAutoConfigurationTests { @Test public void testRedisConfigurationWithCluster() throws Exception { - List clusterNodes = Arrays.asList("127.0.0.1:27379", "127.0.0.1:27380"); if (isAtLeastOneNodeAvailable(clusterNodes)) { load("spring.redis.cluster.nodes[0]:" + clusterNodes.get(0), diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index 3501a3b5d30..68dbb3d59d4 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -685,8 +685,8 @@ content into your application; rather pick only the properties that you need. spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use. # REDIS ({sc-spring-boot-autoconfigure}/redis/RedisProperties.{sc-ext}[RedisProperties]) - spring.redis.cluster.nodes= # List of host:port pairs pointing to an intial collection of cluster nodes. Requires at least one node to connect to the cluster. - spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster. Leave empty to use the driver specific value. + spring.redis.cluster.max-redirects= # Maximum number of redirects to follow when executing commands across the cluster. + spring.redis.cluster.nodes= # Comma-separated list of "host:port" pairs to bootstrap from. spring.redis.database=0 # Database index used by the connection factory. spring.redis.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server.