Skip to content

Commit c5047f4

Browse files
christophstroblmp911de
authored andcommitted
DATAREDIS-315 - Add initial support for redis cluster (jedis/lettuce).
Cluster support is based on the very same building blocks as non clustered communication. RedisClusterConnection and extension to RedisConnection handles the communication with the Redis Cluster and translates errors into the Spring DAO exception hierarchy. Redirects for to a specific keys to the corresponding slot serving node are handled by the driver libraries, higher level functions like collecting information accross nodes, or sending commands to all nodes in the cluster that are covered by RedisClusterConnection utilizing a ClusterCommandExecutor distributing commands accross the cluster and collecting results. RedisTemplate provides access to cluster specific operations via the ClusterOperations interface that can be obtained via RedisTemplate.opsForCluster(). This allows to execute commands explicitly on a single node within the cluster while retaining de-/serialization features configured for the template. Original pull request: spring-projects#158.
1 parent a6ab1b5 commit c5047f4

File tree

69 files changed

+17506
-105
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+17506
-105
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
<beanutils>1.9.2</beanutils>
2525
<xstream>1.4.8</xstream>
2626
<pool>2.2</pool>
27-
<lettuce>3.3.1.Final</lettuce>
27+
<lettuce>3.4.Final</lettuce>
2828
<jedis>2.8.0</jedis>
2929
<srp>0.7</srp>
3030
<jredis>06052013</jredis>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
[[cluster]]
2+
= Redis Cluster
3+
4+
Working with http://redis.io/topics/cluster-spec[Redis Cluster] requires a Redis Server version 3.0+ and provides a very own set of features and capabilities. Please refer to the http://redis.io/topics/cluster-tutorial[Cluster Tutorial] for more information.
5+
6+
TIP: Redis Cluster is only supported by <<redis:connectors:jedis,jedis>> and <<redis:connectors:lettuce,lettuce>>.
7+
8+
== Enabling Redis Cluster
9+
10+
Cluster support is based on the very same building blocks as non clustered communication. `RedisClusterConnection` and extension to `RedisConnection` handles the communication with the Redis Cluster and translates errors into the Spring DAO exception hierarchy.
11+
`RedisClusterConnection` 's are created via the `RedisConnectionFactory` which has to be set up with the according `RedisClusterConfiguration`.
12+
13+
.Sample RedisConnectionFactory Configuration for Redis Cluster
14+
====
15+
[source,java]
16+
----
17+
@Configuration
18+
public class AppConfig {
19+
20+
/*
21+
* spring.redis.cluster.nodes[0] = 127.0.0.1:7379
22+
* spring.redis.cluster.nodes[1] = 127.0.0.1:7380
23+
* ...
24+
*/
25+
@Value("${spring.redis.cluster.nodes}")
26+
private Collection<String> initialClusterNodes;
27+
28+
public @Bean RedisConnectionFactory connectionFactory() {
29+
30+
return new JedisConnectionFactory(
31+
new RedisClusterConfiguration(initialClusterNodes));
32+
}
33+
}
34+
----
35+
====
36+
37+
NOTE: The initial configuration points driver libraries to an initial set of cluster nodes. Changes resulting from live cluster reconfiguration will only be kept in the native driver and not be written back to the configuration.
38+
39+
== Working With Redis Cluster Connection
40+
41+
As mentioned above Redis Cluster behaves different from single node Redis or even a Sentinel monitored master slave environment. This is reasoned by the automatic sharding that maps a key to one of 16384 slots which are distributed across the nodes. Therefore commands that involve more than one key must assert that all keys map to the exact same slot in order to avoid cross slot execution errors.
42+
Further on, hence a single cluster node, only servers a dedicated set of keys, commands issued against one particular server only return results for those keys served by the server. As a very simple example take the `KEYS` command. When issued to a server in cluster environment it only returns the keys served by the node the request is sent to and not necessarily all keys within the cluster. So to get all keys in cluster environment it is necessary to read the keys from at least all known master nodes.
43+
44+
While redirects for to a specific keys to the corresponding slot serving node are handled by the driver libraries, higher level functions like collecting information across nodes, or sending commands to all nodes in the cluster that are covered by `RedisClusterConnection`. Picking up the keys example from just before, this means, that the `keys(pattern)` method picks up every master node in cluster and simultaneously executes the `KEYS` command on every single one, while picking up the results and returning the cumulated set of keys. To just request the keys of a single node `RedisClusterConnection` provides overloads for those (like `keys(node, pattern)` ).
45+
46+
.Sample of Running Commands Across the Cluster
47+
====
48+
[source,text]
49+
----
50+
redis-cli@127.0.0.1:7379 > cluster nodes
51+
52+
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 <1>
53+
7bb78c... 127.0.0.1:7380 master - 0 1449730618304 2 connected 5461-10922 <2>
54+
164888... 127.0.0.1:7381 master - 0 1449730618304 3 connected 10923-16383 <3>
55+
b8b5ee... 127.0.0.1:7382 slave 6b38bb... 0 1449730618304 25 connected <4>
56+
----
57+
58+
[source,java]
59+
----
60+
RedisClusterConnection connection = connectionFactory.getClusterConnnection();
61+
62+
connection.set("foo", value); <5>
63+
connection.set("bar", value); <6>
64+
65+
connection.keys("*"); <7>
66+
67+
connection.keys(NODE_7379, "*"); <8>
68+
connection.keys(NODE_7380, "*"); <9>
69+
connection.keys(NODE_7381, "*"); <10>
70+
connection.keys(NODE_7382, "*"); <11>
71+
----
72+
<1> Master node serving slots 0 to 5460 replicated to slave at 7382
73+
<2> Master node serving slots 5461 to 10922
74+
<3> Master node serving slots 10923 to 16383
75+
<4> Slave node holding replicates of master at 7379
76+
<5> Request routed to node at 7381 serving slot 12182
77+
<6> Request routed to node at 7379 serving slot 5061
78+
<7> Request routed to nodes at 7379, 7380, 7381 -> [foo, bar]
79+
<8> Request routed to node at 7379 -> [bar]
80+
<9> Request routed to node at 7380 -> []
81+
<10> Request routed to node at 7381 -> [foo]
82+
<11> Request routed to node at 7382 -> [bar]
83+
====
84+
85+
Cross slot requests such as `MGET` are automatically served by the native driver library when all keys map to the same slot. However once this is not the case `RedisClusterConnection` executes multiple parallel `GET` commands against the slot serving nodes and again returns a cumulated result. Obviously this is less performing than the single slot execution and therefore should be used with care. In doubt please consider pinning keys to the same slot by providing a prefix in curly brackets like `{my-prefix}.foo` and `{my-prefix}.bar` which will both map to the same slot number.
86+
87+
.Sample of Cross Slot Request Handling
88+
====
89+
[source,text]
90+
----
91+
redis-cli@127.0.0.1:7379 > cluster nodes
92+
93+
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 <1>
94+
7bb...
95+
----
96+
97+
[source,java]
98+
----
99+
RedisClusterConnection connection = connectionFactory.getClusterConnnection();
100+
101+
connection.set("foo", value); // slot: 12182
102+
connection.set("{foo}.bar", value); // slot: 12182
103+
connection.set("bar", value); // slot: 5461
104+
105+
connection.mGet("foo", "{foo}.bar"); <2>
106+
107+
connection.mGet("foo", "bar"); <3>
108+
----
109+
<1> Same Configuration as in the sample before.
110+
<2> Keys map to same slot -> 127.0.0.1:7381 MGET foo {foo}.bar
111+
<3> Keys map to different slots and get split up into single slot ones routed to the according nodes +
112+
-> 127.0.0.1:7379 GET bar +
113+
-> 127.0.0.1:7381 GET foo
114+
====
115+
116+
TIP: The above provides simple examples to demonstrate the general strategy followed by Spring Data Redis. Be aware that some operations might require loading huge amounts of data into memory in order to compute the desired command. Additionally not all cross slot requests can safely be ported to multiple single slot requests and will error if misused (eg. `PFCOUNT` ).
117+
118+
== Working With RedisTemplate and ClusterOperations
119+
120+
Please refer to the section <<redis:template>> to read about general purpose, configuration and usage of `RedisTemplate`.
121+
122+
WARNING: Please be careful when setting up `RedisTemplate#keySerializer` using any of the Json `RedisSerializers` as changing json structure has immediate influence on hash slot calculation.
123+
124+
`RedisTemplate` provides access to cluster specific operations via the `ClusterOperations` interface that can be obtained via `RedisTemplate.opsForCluster()`. This allows to execute commands explicitly on a single node within the cluster while retaining de-/serialization features configured for the template and provides administrative commands such as `CLUSTER MEET` or more high level operations for eg. resharding.
125+
126+
127+
.Accessing RedisClusterConnection via RedisTemplate
128+
====
129+
[source,text]
130+
----
131+
ClusterOperations clusterOps = redisTemplate.opsForCluster();
132+
clusterOps.shutdown(NODE_7379); <1>
133+
----
134+
<1> Shut down node at 7379 and cross fingers there is a slave in place that can take over.
135+
====
136+

src/main/asciidoc/index.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ include::introduction/getting-started.adoc[]
3333
:leveloffset: +1
3434
include::reference/introduction.adoc[]
3535
include::reference/redis.adoc[]
36+
include::reference/redis-cluster.adoc[]
3637
:leveloffset: -1
3738

3839
[[appendixes]]
@@ -43,4 +44,4 @@ include::reference/redis.adoc[]
4344
include::appendix/introduction.adoc[]
4445
include::appendix/appendix-schema.adoc[]
4546
include::appendix/appendix-command-reference.adoc[]
46-
:leveloffset: -1
47+
:leveloffset: -1

src/main/asciidoc/reference/redis.adoc

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,8 +427,4 @@ NOTE: By default `RedisCacheManager` will not participate in any ongoing transac
427427

428428
NOTE: By default `RedisCacheManager` does not prefix keys for cache regions, which can lead to an unexpected growth of a `ZSET` used to maintain known keys. It's highly recommended to enable the usage of prefixes in order to avoid this unexpected growth and potential key clashes using more than one cache region.
429429

430-
[[redis:future]]
431-
== Roadmap ahead
432-
433-
Spring Data Redis project is in its early stages. We are interested in feedback, knowing what your use cases are, what are the common patters you encounter so that the Redis module better serves your needs. Do contact us using the channels <<null,mentioned>> above, we are interested in hearing from you!
434430

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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 org.springframework.data.redis;
17+
18+
import org.springframework.dao.DataRetrievalFailureException;
19+
20+
/**
21+
* {@link ClusterRedirectException} indicates that a requested slot is not served by the targeted server but can be
22+
* obtained on another one.
23+
*
24+
* @author Christoph Strobl
25+
* @since 1.7
26+
*/
27+
public class ClusterRedirectException extends DataRetrievalFailureException {
28+
29+
private static final long serialVersionUID = -857075813794333965L;
30+
31+
private final int slot;
32+
private final String host;
33+
private final int port;
34+
35+
/**
36+
* Creates new {@link ClusterRedirectException}.
37+
*
38+
* @param slot the slot to redirect to.
39+
* @param targetHost the host to redirect to.
40+
* @param targetPort the port on the host.
41+
* @param e the root cause from the data access API in use
42+
*/
43+
public ClusterRedirectException(int slot, String targetHost, int targetPort, Throwable e) {
44+
45+
super(String.format("Redirect: slot %s to %s:%s.", slot, targetHost, targetPort), e);
46+
47+
this.slot = slot;
48+
this.host = targetHost;
49+
this.port = targetPort;
50+
}
51+
52+
/**
53+
* Get slot to go for.
54+
*
55+
* @return
56+
*/
57+
public int getSlot() {
58+
return slot;
59+
}
60+
61+
/**
62+
* Get host serving the slot.
63+
*
64+
* @return
65+
*/
66+
public String getTargetHost() {
67+
return host;
68+
}
69+
70+
/**
71+
* Get port on host serving the slot.
72+
*
73+
* @return
74+
*/
75+
public int getTargetPort() {
76+
return port;
77+
}
78+
79+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 org.springframework.data.redis;
17+
18+
import org.springframework.dao.DataAccessResourceFailureException;
19+
20+
/**
21+
* {@link DataAccessResourceFailureException} indicating the current local snapshot of cluster state does no longer
22+
* represent the actual remote state. This can happen nodes are removed from cluster, slots get migrated to other nodes
23+
* and so on.
24+
*
25+
* @author Christoph Strobl
26+
* @since 1.7
27+
*/
28+
public class ClusterStateFailureExeption extends DataAccessResourceFailureException {
29+
30+
private static final long serialVersionUID = 333399051713240852L;
31+
32+
/**
33+
* Creates new {@link ClusterStateFailureExeption}.
34+
*
35+
* @param msg
36+
*/
37+
public ClusterStateFailureExeption(String msg) {
38+
super(msg);
39+
}
40+
41+
/**
42+
* Creates new {@link ClusterStateFailureExeption}.
43+
*
44+
* @param msg
45+
* @param cause
46+
*/
47+
public ClusterStateFailureExeption(String msg, Throwable cause) {
48+
super(msg, cause);
49+
}
50+
51+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 org.springframework.data.redis;
17+
18+
import org.springframework.dao.DataRetrievalFailureException;
19+
20+
/**
21+
* {@link DataRetrievalFailureException} thrown when following cluster redirects exceeds the max number of edges.
22+
*
23+
* @author Christoph Strobl
24+
* @since 1.7
25+
*/
26+
public class TooManyClusterRedirectionsException extends DataRetrievalFailureException {
27+
28+
private static final long serialVersionUID = -2818933672669154328L;
29+
30+
/**
31+
* Creates new {@link TooManyClusterRedirectionsException}.
32+
*
33+
* @param msg
34+
*/
35+
public TooManyClusterRedirectionsException(String msg) {
36+
super(msg);
37+
}
38+
39+
/**
40+
* Creates new {@link TooManyClusterRedirectionsException}.
41+
*
42+
* @param msg
43+
* @param cause
44+
*/
45+
public TooManyClusterRedirectionsException(String msg, Throwable cause) {
46+
super(msg, cause);
47+
}
48+
49+
}

0 commit comments

Comments
 (0)