From fc227314203bc62bccc480be5fe7228ebf4f4b93 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 11 Feb 2016 12:38:13 +0100 Subject: [PATCH] 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 --- .../data/redis/RedisAutoConfiguration.java | 63 +++++++++++++++++-- .../data/redis/RedisProperties.java | 50 ++++++++++++++- .../redis/RedisAutoConfigurationTests.java | 25 ++++++-- .../appendix-application-properties.adoc | 2 + 4 files changed, 128 insertions(+), 12 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java index bb5e38439a1..5f125b6fddc 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfiguration.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -29,10 +29,12 @@ 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.data.redis.RedisProperties.Cluster; import org.springframework.boot.autoconfigure.data.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.RedisClusterConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisNode; import org.springframework.data.redis.connection.RedisSentinelConfiguration; @@ -76,6 +78,9 @@ public class RedisAutoConfiguration { @Autowired(required = false) private RedisSentinelConfiguration sentinelConfiguration; + @Autowired(required = false) + private RedisClusterConfiguration clusterConfiguration; + protected final JedisConnectionFactory applyProperties( JedisConnectionFactory factory) { factory.setHostName(this.properties.getHost()); @@ -104,6 +109,34 @@ public class RedisAutoConfiguration { 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 createSentinels(Sentinel sentinel) { List sentinels = new ArrayList(); String nodes = sentinel.getNodes(); @@ -135,9 +168,21 @@ public class RedisAutoConfiguration { @ConditionalOnMissingBean(RedisConnectionFactory.class) public JedisConnectionFactory redisConnectionFactory() 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 { } 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); + } + if (getClusterConfiguration() != null) { + return new JedisConnectionFactory(getClusterConfiguration(), poolConfig); } - return new JedisConnectionFactory(getSentinelConfig()); + + return new JedisConnectionFactory(poolConfig); } private JedisPoolConfig jedisPoolConfig() { diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java index 08f778f48fe..0f0ebf0ac57 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/data/redis/RedisProperties.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.data.redis; +import java.util.List; + import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -57,6 +59,8 @@ public class RedisProperties { private Sentinel sentinel; + private Cluster cluster; + public int getDatabase() { return this.database; } @@ -113,6 +117,14 @@ public class RedisProperties { this.pool = pool; } + public Cluster getCluster() { + return cluster; + } + + public void setCluster(Cluster cluster) { + this.cluster = cluster; + } + /** * Pool properties. */ @@ -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 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 getNodes() { + return nodes; + } + + public void setNodes(List nodes) { + this.nodes = nodes; + } + + public Integer getMaxRedirects() { + return maxRedirects; + } + + public void setMaxRedirects(Integer maxRedirects) { + this.maxRedirects = maxRedirects; + } + } + /** * Redis sentinel properties. */ diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java index 8249745256b..225c8b8b876 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationTests.java @@ -1,5 +1,5 @@ /* - * 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"); * you may not use this file except in compliance with the License. @@ -96,7 +96,7 @@ public class RedisAutoConfigurationTests { @Test public void testRedisConfigurationWithSentinel() throws Exception { List 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:" + StringUtils.collectionToCommaDelimitedString(sentinels)); assertThat(this.context.getBean(JedisConnectionFactory.class) @@ -104,9 +104,22 @@ public class RedisAutoConfigurationTests { } } - private boolean isAtLeastOneSentinelAvailable(List sentinels) { - for (String sentinel : sentinels) { - if (isSentinelAvailable(sentinel)) { + @Test + public void testRedisConfigurationWithCluster() throws Exception { + + List 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 nodes) { + for (String node : nodes) { + if (isAvailable(node)) { return true; } } @@ -114,7 +127,7 @@ public class RedisAutoConfigurationTests { return false; } - private boolean isSentinelAvailable(String node) { + private boolean isAvailable(String node) { Jedis jedis = null; try { String[] hostAndPort = node.split(":"); 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 fdd2689a6bb..3501a3b5d30 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -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. # 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.host=localhost # Redis server host. spring.redis.password= # Login password of the redis server.