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
pull/1519/merge
Christoph Strobl 10 years ago committed by Andy Wilkinson
parent fbeb8c966c
commit c8a4891441

@ -17,6 +17,8 @@
package org.springframework.boot.autoconfigure.redis; package org.springframework.boot.autoconfigure.redis;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.pool2.impl.GenericObjectPool; import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.beans.factory.annotation.Autowired; 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.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; 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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory; 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.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;
import redis.clients.jedis.Jedis; import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig; import redis.clients.jedis.JedisPoolConfig;
@ -43,6 +49,7 @@ import redis.clients.jedis.JedisPoolConfig;
* @author Dave Syer * @author Dave Syer
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Christian Dupuis * @author Christian Dupuis
* @author Christoph Strobl
*/ */
@Configuration @Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class }) @ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@ -55,17 +62,58 @@ public class RedisAutoConfiguration {
return new RedisProperties(); return new RedisProperties();
} }
@Configuration protected abstract static class RedisHAConnectionConfiguration {
@ConditionalOnMissingClass(name = "org.apache.commons.pool2.impl.GenericObjectPool")
protected static class RedisConnectionConfiguration {
@Autowired @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<RedisNode> createRedisNodesForSentinel(Sentinel sentinel) {
String[] nodeStrings = StringUtils.commaDelimitedListToStringArray(sentinel
.getNodes());
List<RedisNode> nodes = new ArrayList<RedisNode>(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 @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
RedisConnectionFactory redisConnectionFactory() throws UnknownHostException { RedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
JedisConnectionFactory factory = new JedisConnectionFactory(); JedisConnectionFactory factory = new JedisConnectionFactory(
potentiallyGetSentinelConfig());
applyConnectionFactoryProperties(factory, this.properties); applyConnectionFactoryProperties(factory, this.properties);
return factory; return factory;
} }
@ -74,10 +122,8 @@ public class RedisAutoConfiguration {
@Configuration @Configuration
@ConditionalOnClass(GenericObjectPool.class) @ConditionalOnClass(GenericObjectPool.class)
protected static class RedisPooledConnectionConfiguration { protected static class RedisPooledConnectionConfiguration extends
RedisHAConnectionConfiguration {
@Autowired
private RedisProperties properties;
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ -88,10 +134,14 @@ public class RedisAutoConfiguration {
} }
private JedisConnectionFactory createJedisConnectionFactory() { private JedisConnectionFactory createJedisConnectionFactory() {
if (this.properties.getPool() != null) { 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() { private JedisPoolConfig jedisPoolConfig() {

@ -22,6 +22,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
* Configuration properties for Redis. * Configuration properties for Redis.
* *
* @author Dave Syer * @author Dave Syer
* @author Christoph Strobl
*/ */
@ConfigurationProperties(prefix = "spring.redis") @ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties { public class RedisProperties {
@ -36,6 +37,8 @@ public class RedisProperties {
private RedisProperties.Pool pool; private RedisProperties.Pool pool;
private RedisProperties.Sentinel sentinel;
public String getHost() { public String getHost() {
return this.host; return this.host;
} }
@ -76,7 +79,16 @@ public class RedisProperties {
this.database = database; this.database = database;
} }
/**
public void setSentinel(Sentinel sentinel) {
this.sentinel = sentinel;
}
public Sentinel getSentinel() {
return sentinel;
}
/**
* Pool properties. * Pool properties.
*/ */
public static class Pool { 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;
}
}
} }

@ -16,6 +16,9 @@
package org.springframework.boot.autoconfigure.redis; package org.springframework.boot.autoconfigure.redis;
import java.util.Arrays;
import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.test.EnvironmentTestUtils; 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.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.StringRedisTemplate; 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.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* Tests for {@link RedisAutoConfiguration}. * Tests for {@link RedisAutoConfiguration}.
* *
* @author Dave Syer * @author Dave Syer
* @author Christian Dupuis * @author Christian Dupuis
* @author Christoph Strobl
*/ */
public class RedisAutoConfigurationTests { public class RedisAutoConfigurationTests {
@ -74,4 +82,60 @@ public class RedisAutoConfigurationTests {
.getPoolConfig().getMaxIdle()); .getPoolConfig().getMaxIdle());
} }
@Test
public void testRedisConfigurationWithSentinel() throws Exception {
List<String> 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<String> 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
}
}
}
}
} }

@ -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.min-idle=0
spring.redis.pool.max-active=8 spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1 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]) # ACTIVEMQ ({sc-spring-boot-autoconfigure}/jms/activemq/ActiveMQProperties.{sc-ext}[ActiveMQProperties])
spring.activemq.broker-url=tcp://localhost:61616 # connection URL spring.activemq.broker-url=tcp://localhost:61616 # connection URL

@ -1 +1 @@
provides: spring-data-redis,lettuce provides: spring-data-redis,jedis
Loading…
Cancel
Save