From c8a48914417bc84a88bde2f0f0c9d11c61bc5ff4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 5 Aug 2014 09:42:44 +0200 Subject: [PATCH] Add support for Redis Sentinel configuration Spring Data Redis 1.4.0 introduced Redis Sentinel support. When specified, RedisConnectionFactory uses the Sentinel configuration to determine the current master. Sentinel configuration can be specified using two new properties: spring.redis.sentinel.master and spring.redis.sentinel.nodes. For example: spring.redis.sentinel.master=mymaster # name of redis server spring.redis.sentinel.nodes=127.0.0.1:26379,127.0.0.1:26380 Alternatively, a bean of type RedisSentinelConfiguration can be declared and it will be used to configure the connection factory. Note: At this time, Sentinel support is only available for Jedis Closes gh-1337 --- .../redis/RedisAutoConfiguration.java | 72 ++++++++++++++++--- .../autoconfigure/redis/RedisProperties.java | 38 +++++++++- .../redis/RedisAutoConfigurationTests.java | 64 +++++++++++++++++ .../appendix-application-properties.adoc | 2 + .../main/resources/META-INF/spring.provides | 2 +- 5 files changed, 165 insertions(+), 13 deletions(-) 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