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
This commit is contained in:
Christoph Strobl 2014-08-05 09:42:44 +02:00 committed by Andy Wilkinson
parent fbeb8c966c
commit c8a4891441
5 changed files with 165 additions and 13 deletions

View File

@ -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<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
@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() {

View File

@ -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;
}
}
}

View File

@ -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<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
}
}
}
}
}

View File

@ -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

View File

@ -1 +1 @@
provides: spring-data-redis,lettuce
provides: spring-data-redis,jedis