Commit fc227314 authored by Christoph Strobl's avatar Christoph Strobl Committed by Stephane Nicoll

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
parent d66bc7b1
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -29,10 +29,12 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; ...@@ -29,10 +29,12 @@ 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.data.redis.RedisProperties.Cluster;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Sentinel; import org.springframework.boot.autoconfigure.data.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.RedisClusterConfiguration;
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.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration; import org.springframework.data.redis.connection.RedisSentinelConfiguration;
...@@ -76,6 +78,9 @@ public class RedisAutoConfiguration { ...@@ -76,6 +78,9 @@ public class RedisAutoConfiguration {
@Autowired(required = false) @Autowired(required = false)
private RedisSentinelConfiguration sentinelConfiguration; private RedisSentinelConfiguration sentinelConfiguration;
@Autowired(required = false)
private RedisClusterConfiguration clusterConfiguration;
protected final JedisConnectionFactory applyProperties( protected final JedisConnectionFactory applyProperties(
JedisConnectionFactory factory) { JedisConnectionFactory factory) {
factory.setHostName(this.properties.getHost()); factory.setHostName(this.properties.getHost());
...@@ -104,6 +109,34 @@ public class RedisAutoConfiguration { ...@@ -104,6 +109,34 @@ public class RedisAutoConfiguration {
return null; 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<RedisNode> createSentinels(Sentinel sentinel) { private List<RedisNode> createSentinels(Sentinel sentinel) {
List<RedisNode> sentinels = new ArrayList<RedisNode>(); List<RedisNode> sentinels = new ArrayList<RedisNode>();
String nodes = sentinel.getNodes(); String nodes = sentinel.getNodes();
...@@ -135,9 +168,21 @@ public class RedisAutoConfiguration { ...@@ -135,9 +168,21 @@ public class RedisAutoConfiguration {
@ConditionalOnMissingBean(RedisConnectionFactory.class) @ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() public JedisConnectionFactory redisConnectionFactory()
throws UnknownHostException { 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 { ...@@ -156,10 +201,18 @@ public class RedisAutoConfiguration {
} }
private JedisConnectionFactory createJedisConnectionFactory() { 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);
} }
return new JedisConnectionFactory(getSentinelConfig()); if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
}
return new JedisConnectionFactory(poolConfig);
} }
private JedisPoolConfig jedisPoolConfig() { private JedisPoolConfig jedisPoolConfig() {
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,6 +16,8 @@ ...@@ -16,6 +16,8 @@
package org.springframework.boot.autoconfigure.data.redis; package org.springframework.boot.autoconfigure.data.redis;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
/** /**
...@@ -57,6 +59,8 @@ public class RedisProperties { ...@@ -57,6 +59,8 @@ public class RedisProperties {
private Sentinel sentinel; private Sentinel sentinel;
private Cluster cluster;
public int getDatabase() { public int getDatabase() {
return this.database; return this.database;
} }
...@@ -113,6 +117,14 @@ public class RedisProperties { ...@@ -113,6 +117,14 @@ public class RedisProperties {
this.pool = pool; this.pool = pool;
} }
public Cluster getCluster() {
return cluster;
}
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
/** /**
* Pool properties. * Pool properties.
*/ */
...@@ -176,6 +188,42 @@ public class RedisProperties { ...@@ -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<String> 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<String> getNodes() {
return nodes;
}
public void setNodes(List<String> nodes) {
this.nodes = nodes;
}
public Integer getMaxRedirects() {
return maxRedirects;
}
public void setMaxRedirects(Integer maxRedirects) {
this.maxRedirects = maxRedirects;
}
}
/** /**
* Redis sentinel properties. * Redis sentinel properties.
*/ */
......
/* /*
* 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -96,7 +96,7 @@ public class RedisAutoConfigurationTests { ...@@ -96,7 +96,7 @@ public class RedisAutoConfigurationTests {
@Test @Test
public void testRedisConfigurationWithSentinel() throws Exception { public void testRedisConfigurationWithSentinel() throws Exception {
List<String> sentinels = Arrays.asList("127.0.0.1:26379", "127.0.0.1:26380"); List<String> 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:" load("spring.redis.sentinel.master:mymaster", "spring.redis.sentinel.nodes:"
+ StringUtils.collectionToCommaDelimitedString(sentinels)); + StringUtils.collectionToCommaDelimitedString(sentinels));
assertThat(this.context.getBean(JedisConnectionFactory.class) assertThat(this.context.getBean(JedisConnectionFactory.class)
...@@ -104,9 +104,22 @@ public class RedisAutoConfigurationTests { ...@@ -104,9 +104,22 @@ public class RedisAutoConfigurationTests {
} }
} }
private boolean isAtLeastOneSentinelAvailable(List<String> sentinels) { @Test
for (String sentinel : sentinels) { public void testRedisConfigurationWithCluster() throws Exception {
if (isSentinelAvailable(sentinel)) {
List<String> 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<String> nodes) {
for (String node : nodes) {
if (isAvailable(node)) {
return true; return true;
} }
} }
...@@ -114,7 +127,7 @@ public class RedisAutoConfigurationTests { ...@@ -114,7 +127,7 @@ public class RedisAutoConfigurationTests {
return false; return false;
} }
private boolean isSentinelAvailable(String node) { private boolean isAvailable(String node) {
Jedis jedis = null; Jedis jedis = null;
try { try {
String[] hostAndPort = node.split(":"); String[] hostAndPort = node.split(":");
......
...@@ -685,6 +685,8 @@ content into your application; rather pick only the properties that you need. ...@@ -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. spring.mongodb.embedded.version=2.6.10 # Version of Mongo to use.
# REDIS ({sc-spring-boot-autoconfigure}/redis/RedisProperties.{sc-ext}[RedisProperties]) # 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.database=0 # Database index used by the connection factory.
spring.redis.host=localhost # Redis server host. spring.redis.host=localhost # Redis server host.
spring.redis.password= # Login password of the redis server. spring.redis.password= # Login password of the redis server.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment