Skip to content

Commit dfc1bf0

Browse files
christophstroblodrotbohm
authored andcommitted
#146 - Add example to demonstrate Redis Cluster support.
Example demonstrating the basic usage of Spring Data Redis in a clustered environment using Jedis.
1 parent 9cb44b3 commit dfc1bf0

File tree

8 files changed

+400
-2
lines changed

8 files changed

+400
-2
lines changed

redis/cluster/pom.xml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<artifactId>spring-data-redis-cluster-example</artifactId>
6+
<name>Spring Data Redis - Cluster Example</name>
7+
8+
<parent>
9+
<groupId>org.springframework.data.examples</groupId>
10+
<artifactId>spring-data-redis-examples</artifactId>
11+
<version>1.0.0.BUILD-SNAPSHOT</version>
12+
<relativePath>../pom.xml</relativePath>
13+
</parent>
14+
15+
<dependencies>
16+
17+
<dependency>
18+
<groupId>org.springframework.boot</groupId>
19+
<artifactId>spring-boot-starter-redis</artifactId>
20+
</dependency>
21+
<dependency>
22+
<groupId>${project.groupId}</groupId>
23+
<artifactId>spring-data-redis-example-utils</artifactId>
24+
<version>${project.version}</version>
25+
<scope>test</scope>
26+
</dependency>
27+
28+
<!-- remove this one when DATAREDIS-315 has been merged -->
29+
<dependency>
30+
<groupId>org.springframework.data</groupId>
31+
<artifactId>spring-data-redis</artifactId>
32+
<version>1.7.0.DATAREDIS-315-SNAPSHOT</version>
33+
</dependency>
34+
</dependencies>
35+
36+
</project>

redis/cluster/readme.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Spring Data Redis - Cluster Examples #
2+
3+
This project contains Redis 3 Cluster specific features of Spring Data Redis.
4+
5+
To run the code in this sample a running cluster environment is required. Please refer to the [redis cluster-tutorial](http://redis.io/topics/cluster-tutorial) for detailed information or check the Cluster Setup section below.
6+
7+
## Support for Cluster ##
8+
9+
Cluster Support uses the same building blocks as the non clustered counterpart. We use `application.properties` to point to an initial set of known cluster nodes.
10+
11+
```properties
12+
spring.redis.cluster.nodes[0]=127.0.0.1:30001
13+
spring.redis.cluster.nodes[1]=127.0.0.1:30002
14+
spring.redis.cluster.nodes[2]=127.0.0.1:30003
15+
```
16+
17+
Additionally we need to have the `RedisConnectionFactory` set up with the according `RedisClusterConfiguration`.
18+
19+
```java
20+
@Configuration
21+
@EnableConfigurationProperties(ClusterConfigurationProperties.class)
22+
public class AppConfig {
23+
24+
/**
25+
* Type safe representation of application.properties
26+
*/
27+
@Autowired ClusterConfigurationProperties clusterProperties;
28+
29+
/**
30+
* The connection factory used for obtaining RedisConnection
31+
* uses a RedisClusterConfiguration that points
32+
* to the initial set of nodes.
33+
*/
34+
@Bean
35+
RedisConnectionFactory connectionFactory() {
36+
return new JedisConnectionFactory(
37+
new RedisClusterConfiguration(clusterProperties.getNodes()));
38+
}
39+
40+
/**
41+
* RedisTemplate can be configured with RedisSerializer if needed.
42+
* NOTE: be careful using JSON serializers for key serialization.
43+
*/
44+
@Bean
45+
RedisTemplate<String, String> redisTemplate() {
46+
return new StringRedisTemplate(connectionFactory());
47+
}
48+
}
49+
```
50+
51+
**INFORMATION:** The tests flush the db of all known instances during the JUnit _setup_ phase to allow inspecting data directly on the cluster nodes after a test is run.
52+
53+
## Cluster Setup ##
54+
55+
To quickly set up a cluster of 6 nodes (3 master | 3 slave) go to the `redis/utils/create-cluster` directory.
56+
57+
58+
```bash
59+
redis/utils/create-cluster $ ./create-cluster start
60+
Starting 30001
61+
Starting 30002
62+
Starting 30003
63+
Starting 30004
64+
Starting 30005
65+
Starting 30006
66+
```
67+
68+
On first initialization cluster nodes need to form the cluster by joining and assigning slot allocations.
69+
**INFO**: This has to be done only once.
70+
71+
```bash
72+
redis/utils/create-cluster $ ./create-cluster create
73+
>>> Creating cluster
74+
>>> Performing hash slots allocation on 6 nodes...
75+
Using 3 masters:
76+
127.0.0.1:30001
77+
127.0.0.1:30002
78+
127.0.0.1:30003
79+
Adding replica 127.0.0.1:30004 to 127.0.0.1:30001
80+
Adding replica 127.0.0.1:30005 to 127.0.0.1:30002
81+
Adding replica 127.0.0.1:30006 to 127.0.0.1:30003
82+
83+
M: 10696916f57e58c5edce34127b23ca7af1b669a0 127.0.0.1:30001
84+
slots:0-5460 (5461 slots) master
85+
M: 5b0e1b4cc87175326ba79d00ecfc6f5dbdb424a7 127.0.0.1:30002
86+
slots:5461-10922 (5462 slots) master
87+
M: 5f3e978fb40b1d9c910d904ea19a0494b78668aa 127.0.0.1:30003
88+
slots:10923-16383 (5461 slots) master
89+
S: d1717c418d03db93183ce2d791ba6f48be5cf028 127.0.0.1:30004
90+
replicates 10696916f57e58c5edce34127b23ca7af1b669a0
91+
S: c7dfcdb9cd1105e4251de51c4ade54de59bb063c 127.0.0.1:30005
92+
replicates 5b0e1b4cc87175326ba79d00ecfc6f5dbdb424a7
93+
S: 3219785a9145717f30648a27a2dd07359e9dd46f 127.0.0.1:30006
94+
replicates 5f3e978fb40b1d9c910d904ea19a0494b78668aa
95+
96+
Can I set the above configuration? (type 'yes' to accept): yes
97+
98+
[OK] All nodes agree about slots configuration.
99+
>>> Check for open slots...
100+
>>> Check slots coverage...
101+
[OK] All 16384 slots covered.
102+
```
103+
104+
It is now possible to connect to the cluster using the `redis-cli`.
105+
106+
```bash
107+
redis/src $ ./redis-cli -c -p 30001
108+
127.0.0.1:30001> cluster nodes
109+
110+
106969... 127.0.0.1:30001 myself,master - 0 0 1 connected 0-5460
111+
5b0e1b... 127.0.0.1:30002 master - 0 1450765112345 2 connected 5461-10922
112+
5f3e97... 127.0.0.1:30003 master - 0 1450765112345 3 connected 10923-16383
113+
d1717c... 127.0.0.1:30004 slave 106969... 0 1450765112345 4 connected
114+
c7dfcd... 127.0.0.1:30005 slave 5b0e1b... 0 1450765113050 5 connected
115+
321978... 127.0.0.1:30006 slave 5f3e97... 0 1450765113050 6 connected
116+
```
117+
118+
To shutdown the cluster use the `create-cluster stop` command.
119+
120+
```bash
121+
redis/utils/create-cluster $ ./create-cluster stop
122+
```
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis.cluster;
17+
18+
import org.springframework.beans.factory.annotation.Autowired;
19+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
20+
import org.springframework.context.annotation.Bean;
21+
import org.springframework.context.annotation.Configuration;
22+
import org.springframework.data.redis.connection.RedisClusterConfiguration;
23+
import org.springframework.data.redis.connection.RedisConnection;
24+
import org.springframework.data.redis.connection.RedisConnectionFactory;
25+
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
26+
import org.springframework.data.redis.core.RedisTemplate;
27+
import org.springframework.data.redis.core.StringRedisTemplate;
28+
import org.springframework.data.redis.serializer.RedisSerializer;
29+
30+
/**
31+
* Application context configuration setting up {@link RedisConnectionFactory} and {@link RedisTemplate} according to
32+
* {@link ClusterConfigurationProperties}.
33+
*
34+
* @author Christoph Strobl
35+
*/
36+
@Configuration
37+
@EnableConfigurationProperties(ClusterConfigurationProperties.class)
38+
public class AppConfig {
39+
40+
/**
41+
* Type safe representation of application.properties
42+
*/
43+
@Autowired ClusterConfigurationProperties clusterProperties;
44+
45+
/**
46+
* The connection factory used for obtaining {@link RedisConnection} uses a {@link RedisClusterConfiguration} that
47+
* points to the initial set of nodes.
48+
*/
49+
@Bean
50+
RedisConnectionFactory connectionFactory() {
51+
return new JedisConnectionFactory(new RedisClusterConfiguration(clusterProperties.getNodes()));
52+
}
53+
54+
/**
55+
* {@link RedisTemplate} can be configured with {@link RedisSerializer} if needed. <br />
56+
* <b>NOTE:</b> be careful using JSON @link RedisSerializer} for key serialization.
57+
*/
58+
@Bean
59+
RedisTemplate<String, String> redisTemplate() {
60+
return new StringRedisTemplate(connectionFactory());
61+
}
62+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis.cluster;
17+
18+
import java.util.List;
19+
20+
import org.springframework.boot.context.properties.ConfigurationProperties;
21+
import org.springframework.stereotype.Component;
22+
23+
/**
24+
* Type safe representation of {@code spring.redis.cluster.*} properties in {@literal application.properties}.
25+
*
26+
* @author Christoph Strobl
27+
*/
28+
@Component
29+
@ConfigurationProperties(prefix = "spring.redis.cluster")
30+
public class ClusterConfigurationProperties {
31+
32+
List<String> nodes;
33+
34+
/**
35+
* Get initial collection of known cluster nodes in format {@code host:port}.
36+
*
37+
* @return
38+
*/
39+
public List<String> getNodes() {
40+
return nodes;
41+
}
42+
43+
public void setNodes(List<String> nodes) {
44+
this.nodes = nodes;
45+
}
46+
47+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package example.springdata.redis.cluster;
17+
18+
import static org.hamcrest.core.Is.*;
19+
import static org.hamcrest.core.IsCollectionContaining.*;
20+
import static org.junit.Assert.*;
21+
22+
import java.util.Arrays;
23+
24+
import org.junit.Before;
25+
import org.junit.ClassRule;
26+
import org.junit.Test;
27+
import org.junit.runner.RunWith;
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.boot.test.SpringApplicationConfiguration;
30+
import org.springframework.dao.DataAccessException;
31+
import org.springframework.data.redis.connection.RedisConnection;
32+
import org.springframework.data.redis.core.RedisCallback;
33+
import org.springframework.data.redis.core.RedisOperations;
34+
import org.springframework.data.redis.core.RedisTemplate;
35+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
36+
37+
import example.springdata.redis.test.util.RequiresRedisServer;
38+
39+
/**
40+
* {@link BasicUsageTests} shows general usage of {@link RedisTemplate} and {@link RedisOperations} in a clustered
41+
* environment.
42+
*
43+
* @author Christoph Strobl
44+
*/
45+
@RunWith(SpringJUnit4ClassRunner.class)
46+
@SpringApplicationConfiguration(classes = { AppConfig.class })
47+
public class BasicUsageTests {
48+
49+
@Autowired RedisTemplate<String, String> template;
50+
51+
public static @ClassRule RequiresRedisServer redisServerAvailable = RequiresRedisServer.listeningAt("127.0.0.1",
52+
30001);
53+
54+
@Before
55+
public void setUp() {
56+
57+
template.execute(new RedisCallback<String>() {
58+
59+
@Override
60+
public String doInRedis(RedisConnection connection) throws DataAccessException {
61+
connection.flushDb();
62+
return "FLUSHED";
63+
}
64+
});
65+
}
66+
67+
/**
68+
* Operation executed on a single node and slot. <br />
69+
* -&gt; {@code SLOT 5798} served by {@code 127.0.0.1:30002}
70+
*/
71+
@Test
72+
public void singleSlotOperation() {
73+
74+
template.opsForValue().set("name", "rand al'thor"); // slot 5798
75+
assertThat(template.opsForValue().get("name"), is("rand al'thor"));
76+
}
77+
78+
/**
79+
* Operation executed on multiple nodes and slots. <br />
80+
* -&gt; {@code SLOT 5798} served by {@code 127.0.0.1:30002} <br />
81+
* -&gt; {@code SLOT 14594} served by {@code 127.0.0.1:30003}
82+
*/
83+
@Test
84+
public void multiSlotOperation() {
85+
86+
template.opsForValue().set("name", "matrim cauthon"); // slot 5798
87+
template.opsForValue().set("nickname", "prince of the ravens"); // slot 14594
88+
89+
assertThat(template.opsForValue().multiGet(Arrays.asList("name", "nickname")),
90+
hasItems("matrim cauthon", "prince of the ravens"));
91+
}
92+
93+
/**
94+
* Operation executed on a single node and slot because of pinned slot key <br />
95+
* -&gt; {@code SLOT 5798} served by {@code 127.0.0.1:30002}
96+
*/
97+
@Test
98+
public void fixedSlotOperation() {
99+
100+
template.opsForValue().set("{user}.name", "perrin aybara"); // slot 5474
101+
template.opsForValue().set("{user}.nickname", "wolfbrother"); // slot 5474
102+
103+
assertThat(template.opsForValue().multiGet(Arrays.asList("{user}.name", "{user}.nickname")),
104+
hasItems("perrin aybara", "wolfbrother"));
105+
}
106+
107+
/**
108+
* Operation executed across the cluster to retrieve cumulated result. <br />
109+
* -&gt; {@code KEY age} served by {@code 127.0.0.1:30001} <br />
110+
* -&gt; {@code KEY name} served by {@code 127.0.0.1:30002} <br />
111+
* -&gt; {@code KEY nickname} served by {@code 127.0.0.1:30003}
112+
*/
113+
@Test
114+
public void multiNodeOperation() {
115+
116+
template.opsForValue().set("name", "rand al'thor"); // slot 5798
117+
template.opsForValue().set("nickname", "dragon reborn"); // slot 14594
118+
template.opsForValue().set("age", "23"); // slot 741;
119+
120+
assertThat(template.keys("*"), hasItems("name", "nickname", "age"));
121+
}
122+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
spring.redis.cluster.nodes[0]=127.0.0.1:30001
2+
spring.redis.cluster.nodes[1]=127.0.0.1:30002
3+
spring.redis.cluster.nodes[2]=127.0.0.1:30003

0 commit comments

Comments
 (0)