diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfiguration.java index 01e12f071e2..d40670e0e61 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfiguration.java @@ -17,6 +17,8 @@ package org.springframework.boot.autoconfigure.redis; import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; import org.apache.commons.pool2.impl.GenericObjectPool; import org.springframework.beans.factory.annotation.Autowired; @@ -24,15 +26,19 @@ 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.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.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisNode; +import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnection; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.util.StringUtils; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPoolConfig; @@ -43,6 +49,7 @@ import redis.clients.jedis.JedisPoolConfig; * @author Dave Syer * @author Andy Wilkinson * @author Christian Dupuis + * @author Christoph Strobl */ @Configuration @ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }) @@ -55,17 +62,58 @@ public class RedisAutoConfiguration { return new RedisProperties(); } - @Configuration - @ConditionalOnMissingClass(name = "org.apache.commons.pool2.impl.GenericObjectPool") - protected static class RedisConnectionConfiguration { + protected abstract static class RedisHAConnectionConfiguration { @Autowired - private RedisProperties properties; + protected RedisProperties properties; + + @Autowired(required = false) + private RedisSentinelConfiguration sentinelConfiguration; + + protected RedisSentinelConfiguration potentiallyGetSentinelConfig() { + + if (this.sentinelConfiguration == null + && this.properties.getSentinel() == null) { + return null; + } + + RedisSentinelConfiguration sentinelConfig = this.sentinelConfiguration; + if (sentinelConfig == null && this.properties.getSentinel() != null) { + sentinelConfig = new RedisSentinelConfiguration().master(this.properties + .getSentinel().getMaster()); + sentinelConfig.setSentinels(createRedisNodesForSentinel(this.properties + .getSentinel())); + } + return sentinelConfig; + } + + private List createRedisNodesForSentinel(Sentinel sentinel) { + + String[] nodeStrings = StringUtils.commaDelimitedListToStringArray(sentinel + .getNodes()); + + List nodes = new ArrayList(nodeStrings.length); + + for (String hostAndPort : nodeStrings) { + String[] args = StringUtils.split(hostAndPort, ":"); + nodes.add(new RedisNode(args[0], Integer.valueOf(args[1]))); + } + + return nodes; + } + + } + + @Configuration + @ConditionalOnMissingClass(name = "org.apache.commons.pool2.impl.GenericObjectPool") + protected static class RedisConnectionConfiguration extends + RedisHAConnectionConfiguration { @Bean @ConditionalOnMissingBean RedisConnectionFactory redisConnectionFactory() throws UnknownHostException { - JedisConnectionFactory factory = new JedisConnectionFactory(); + JedisConnectionFactory factory = new JedisConnectionFactory( + potentiallyGetSentinelConfig()); applyConnectionFactoryProperties(factory, this.properties); return factory; } @@ -74,10 +122,8 @@ public class RedisAutoConfiguration { @Configuration @ConditionalOnClass(GenericObjectPool.class) - protected static class RedisPooledConnectionConfiguration { - - @Autowired - private RedisProperties properties; + protected static class RedisPooledConnectionConfiguration extends + RedisHAConnectionConfiguration { @Bean @ConditionalOnMissingBean @@ -88,10 +134,14 @@ public class RedisAutoConfiguration { } private JedisConnectionFactory createJedisConnectionFactory() { + if (this.properties.getPool() != null) { - return new JedisConnectionFactory(jedisPoolConfig()); + return new JedisConnectionFactory(potentiallyGetSentinelConfig(), + jedisPoolConfig()); + } + else { + return new JedisConnectionFactory(potentiallyGetSentinelConfig()); } - return new JedisConnectionFactory(); } private JedisPoolConfig jedisPoolConfig() { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisProperties.java index 5135e792a0a..236b5a7bc98 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/redis/RedisProperties.java @@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties; * Configuration properties for Redis. * * @author Dave Syer + * @author Christoph Strobl */ @ConfigurationProperties(prefix = "spring.redis") public class RedisProperties { @@ -36,6 +37,8 @@ public class RedisProperties { private RedisProperties.Pool pool; + private RedisProperties.Sentinel sentinel; + public String getHost() { return this.host; } @@ -76,7 +79,16 @@ public class RedisProperties { this.database = database; } - /** + + public void setSentinel(Sentinel sentinel) { + this.sentinel = sentinel; + } + + public Sentinel getSentinel() { + return sentinel; + } + + /** * Pool properties. */ public static class Pool { @@ -122,4 +134,28 @@ public class RedisProperties { } } + /** + * Properties for configuring redis sentinels. + */ + public static class Sentinel { + + private String master; + private String nodes; + + public String getMaster() { + return master; + } + + public void setMaster(String master) { + this.master = master; + } + + public String getNodes() { + return nodes; + } + + public void setNodes(String nodes) { + this.nodes = nodes; + } + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfigurationTests.java index 7805e8249a7..80f942c48df 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/redis/RedisAutoConfigurationTests.java @@ -16,6 +16,9 @@ package org.springframework.boot.autoconfigure.redis; +import java.util.Arrays; +import java.util.List; + import org.junit.Test; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.test.EnvironmentTestUtils; @@ -23,15 +26,20 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.util.StringUtils; + +import redis.clients.jedis.Jedis; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Tests for {@link RedisAutoConfiguration}. * * @author Dave Syer * @author Christian Dupuis + * @author Christoph Strobl */ public class RedisAutoConfigurationTests { @@ -74,4 +82,60 @@ public class RedisAutoConfigurationTests { .getPoolConfig().getMaxIdle()); } + @Test + public void testRedisConfigurationWithSentinel() throws Exception { + List sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); + + if (isAtLeastOneSentinelAvailable(sentinels)) { + this.context = new AnnotationConfigApplicationContext(); + EnvironmentTestUtils.addEnvironment(this.context, + "spring.redis.sentinel.master:mymaster"); + EnvironmentTestUtils.addEnvironment( + this.context, + "spring.redis.sentinel.nodes:" + + StringUtils.collectionToCommaDelimitedString(sentinels)); + this.context.register(RedisAutoConfiguration.class, + PropertyPlaceholderAutoConfiguration.class); + this.context.refresh(); + + assertTrue(this.context.getBean(JedisConnectionFactory.class) + .isRedisSentinelAware()); + } + } + + private boolean isAtLeastOneSentinelAvailable(List sentinels) { + for (String sentinel : sentinels) { + if (isSentinelAvailable(sentinel)) { + return true; + } + } + + return false; + } + + private boolean isSentinelAvailable(String node) { + Jedis jedis = null; + try { + String[] hostAndPort = node.split(":"); + jedis = new Jedis(hostAndPort[0], Integer.valueOf(hostAndPort[1])); + jedis.connect(); + jedis.ping(); + return true; + } + catch (Exception ex) { + return false; + } + finally { + if (jedis != null) { + try { + jedis.disconnect(); + jedis.close(); + } + catch (Exception ex) { + // Continue + } + } + } + } + } 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 8ccf60ec4f6..e9b0bba440f 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -261,6 +261,8 @@ content into your application; rather pick only the properties that you need. spring.redis.pool.min-idle=0 spring.redis.pool.max-active=8 spring.redis.pool.max-wait=-1 + spring.redis.sentinel.master= # name of Redis server + spring.redis.sentinel.nodes= # comma-separated list of host:port pairs # ACTIVEMQ ({sc-spring-boot-autoconfigure}/jms/activemq/ActiveMQProperties.{sc-ext}[ActiveMQProperties]) spring.activemq.broker-url=tcp://localhost:61616 # connection URL diff --git a/spring-boot-starters/spring-boot-starter-redis/src/main/resources/META-INF/spring.provides b/spring-boot-starters/spring-boot-starter-redis/src/main/resources/META-INF/spring.provides index b803b513397..ef52e2ea087 100644 --- a/spring-boot-starters/spring-boot-starter-redis/src/main/resources/META-INF/spring.provides +++ b/spring-boot-starters/spring-boot-starter-redis/src/main/resources/META-INF/spring.provides @@ -1 +1 @@ -provides: spring-data-redis,lettuce \ No newline at end of file +provides: spring-data-redis,jedis \ No newline at end of file