DATAREDIS-467 - Fix cluster multi key commands.

We moved away from returning raw map types and now return dedicated objects for command executions in cluster environment. This allows to maintain node information and collecting results from each and every callback. MGET now returns values according to the key position.

Additionally we now prefix info commands in the cluster with the nodes host:port.

Original pull request: #174.
This commit is contained in:
Christoph Strobl
2016-02-24 14:55:11 +01:00
committed by Mark Paluch
parent 6f26a5a073
commit 02742e3b49
7 changed files with 606 additions and 346 deletions

View File

@@ -25,7 +25,6 @@ import static org.springframework.data.redis.test.util.MockitoUtils.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
@@ -43,6 +42,7 @@ import org.springframework.data.redis.ClusterRedirectException;
import org.springframework.data.redis.PassThroughExceptionTranslationStrategy;
import org.springframework.data.redis.TooManyClusterRedirectionsException;
import org.springframework.data.redis.connection.ClusterCommandExecutor.ClusterCommandCallback;
import org.springframework.data.redis.connection.ClusterCommandExecutor.MulitNodeResult;
import org.springframework.data.redis.connection.ClusterCommandExecutor.MultiKeyClusterCommandCallback;
import org.springframework.data.redis.connection.RedisClusterNode.LinkState;
import org.springframework.data.redis.connection.RedisClusterNode.SlotRange;
@@ -77,8 +77,7 @@ public class ClusterCommandExecutorUnitTests {
.withId("3b9b8192a874fa8f1f09dbc0ee20afab5738eee7").promotedAs(NodeType.MASTER).linkState(LinkState.CONNECTED)
.build();
static final RedisClusterNode CLUSTER_NODE_2_LOOKUP = RedisClusterNode.newRedisClusterNode()
.withId("0f2ee5df45d18c50aca07228cc18b1da96fd5e84")
.build();
.withId("0f2ee5df45d18c50aca07228cc18b1da96fd5e84").build();
static final RedisClusterNode UNKNOWN_CLUSTER_NODE = new RedisClusterNode("8.8.8.8", 7379, null);
@@ -149,7 +148,8 @@ public class ClusterCommandExecutorUnitTests {
@Test
public void executeCommandOnSingleNodeByHostAndPortShouldBeExecutedCorrectly() {
executor.executeCommandOnSingleNode(COMMAND_CALLBACK, new RedisClusterNode(CLUSTER_NODE_2_HOST, CLUSTER_NODE_2_PORT));
executor.executeCommandOnSingleNode(COMMAND_CALLBACK,
new RedisClusterNode(CLUSTER_NODE_2_HOST, CLUSTER_NODE_2_PORT));
verify(con2, times(1)).theWheelWeavesAsTheWheelWills();
}
@@ -216,8 +216,9 @@ public class ClusterCommandExecutorUnitTests {
new MockClusterResourceProvider(), new PassThroughExceptionTranslationStrategy(exceptionConverter),
new ConcurrentTaskExecutor(new SyncTaskExecutor()));
executor.executeCommandAsyncOnNodes(COMMAND_CALLBACK, Arrays.asList(new RedisClusterNode(CLUSTER_NODE_1_HOST, CLUSTER_NODE_1_PORT),
new RedisClusterNode(CLUSTER_NODE_2_HOST, CLUSTER_NODE_2_PORT)));
executor.executeCommandAsyncOnNodes(COMMAND_CALLBACK,
Arrays.asList(new RedisClusterNode(CLUSTER_NODE_1_HOST, CLUSTER_NODE_1_PORT),
new RedisClusterNode(CLUSTER_NODE_2_HOST, CLUSTER_NODE_2_PORT)));
verify(con1, times(1)).theWheelWeavesAsTheWheelWills();
verify(con2, times(1)).theWheelWeavesAsTheWheelWills();
@@ -234,8 +235,8 @@ public class ClusterCommandExecutorUnitTests {
new MockClusterResourceProvider(), new PassThroughExceptionTranslationStrategy(exceptionConverter),
new ConcurrentTaskExecutor(new SyncTaskExecutor()));
executor.executeCommandAsyncOnNodes(COMMAND_CALLBACK, Arrays.asList(new RedisClusterNode(CLUSTER_NODE_1.id),
CLUSTER_NODE_2_LOOKUP));
executor.executeCommandAsyncOnNodes(COMMAND_CALLBACK,
Arrays.asList(new RedisClusterNode(CLUSTER_NODE_1.id), CLUSTER_NODE_2_LOOKUP));
verify(con1, times(1)).theWheelWeavesAsTheWheelWills();
verify(con2, times(1)).theWheelWeavesAsTheWheelWills();
@@ -252,8 +253,8 @@ public class ClusterCommandExecutorUnitTests {
new MockClusterResourceProvider(), new PassThroughExceptionTranslationStrategy(exceptionConverter),
new ConcurrentTaskExecutor(new SyncTaskExecutor()));
executor.executeCommandAsyncOnNodes(COMMAND_CALLBACK, Arrays.asList(
new RedisClusterNode("unknown"), CLUSTER_NODE_2_LOOKUP));
executor.executeCommandAsyncOnNodes(COMMAND_CALLBACK,
Arrays.asList(new RedisClusterNode("unknown"), CLUSTER_NODE_2_LOOKUP));
}
/**
@@ -306,32 +307,29 @@ public class ClusterCommandExecutorUnitTests {
when(con2.theWheelWeavesAsTheWheelWills()).thenReturn("mat");
when(con3.theWheelWeavesAsTheWheelWills()).thenReturn("perrin");
Map<RedisClusterNode, String> result = executor.executeCommandOnAllNodes(COMMAND_CALLBACK);
MulitNodeResult<String> result = executor.executeCommandOnAllNodes(COMMAND_CALLBACK);
assertThat(result.keySet(), hasItems(CLUSTER_NODE_1, CLUSTER_NODE_2, CLUSTER_NODE_3));
assertThat(result.values(), hasItems("rand", "mat", "perrin"));
assertThat(result.resultsAsList(), hasItems("rand", "mat", "perrin"));
}
/**
* @see DATAREDIS-315
* @see DATAREDIS-467
*/
@Test
public void executeMultikeyCommandShouldRunCommandAcrossCluster() {
// key-1 and key-9 map both to node1
ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
when(con1.bloodAndAshes(captor.capture())).thenReturn("rand");
when(con1.bloodAndAshes(captor.capture())).thenReturn("rand").thenReturn("egwene");
when(con2.bloodAndAshes(any(byte[].class))).thenReturn("mat");
when(con3.bloodAndAshes(any(byte[].class))).thenReturn("perrin");
Map<RedisClusterNode, String> result = executor.executeMuliKeyCommand(
MULTIKEY_CALLBACK,
new HashSet<byte[]>(Arrays.asList("key-1".getBytes(), "key-2".getBytes(), "key-3".getBytes(),
"key-9".getBytes())));
MulitNodeResult<String> result = executor.executeMuliKeyCommand(MULTIKEY_CALLBACK, new HashSet<byte[]>(
Arrays.asList("key-1".getBytes(), "key-2".getBytes(), "key-3".getBytes(), "key-9".getBytes())));
assertThat(result.keySet(), hasItems(CLUSTER_NODE_1, CLUSTER_NODE_2, CLUSTER_NODE_3));
assertThat(result.values(), hasItems("rand", "mat", "perrin"));
assertThat(result.resultsAsList(), hasItems("rand", "mat", "perrin", "egwene"));
// check that 2 keys have been routed to node1
assertThat(captor.getAllValues().size(), is(2));
@@ -389,8 +387,8 @@ public class ClusterCommandExecutorUnitTests {
@Override
public ClusterTopology getTopology() {
return new ClusterTopology(new LinkedHashSet<RedisClusterNode>(Arrays.asList(CLUSTER_NODE_1, CLUSTER_NODE_2,
CLUSTER_NODE_3)));
return new ClusterTopology(
new LinkedHashSet<RedisClusterNode>(Arrays.asList(CLUSTER_NODE_1, CLUSTER_NODE_2, CLUSTER_NODE_3)));
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.redis.connection.jedis;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
import static org.hamcrest.number.IsCloseTo.*;
import static org.junit.Assert.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
@@ -536,7 +537,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
nativeConnection.set(SAME_SLOT_KEY_2_BYTES, VALUE_2_BYTES);
assertThat(clusterConnection.mGet(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES),
hasItems(VALUE_1_BYTES, VALUE_2_BYTES));
contains(VALUE_1_BYTES, VALUE_2_BYTES));
}
/**
@@ -548,7 +549,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
nativeConnection.set(KEY_1_BYTES, VALUE_1_BYTES);
nativeConnection.set(KEY_2_BYTES, VALUE_2_BYTES);
assertThat(clusterConnection.mGet(KEY_1_BYTES, KEY_2_BYTES), hasItems(VALUE_1_BYTES, VALUE_2_BYTES));
assertThat(clusterConnection.mGet(KEY_1_BYTES, KEY_2_BYTES), contains(VALUE_1_BYTES, VALUE_2_BYTES));
}
/**
@@ -2088,29 +2089,23 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
assertThat(values,
hasItems(JedisConverters.toBytes("a"), JedisConverters.toBytes("b"), JedisConverters.toBytes("c")));
assertThat(
values,
not(hasItems(JedisConverters.toBytes("d"), JedisConverters.toBytes("e"), JedisConverters.toBytes("f"),
JedisConverters.toBytes("g"))));
assertThat(values, not(hasItems(JedisConverters.toBytes("d"), JedisConverters.toBytes("e"),
JedisConverters.toBytes("f"), JedisConverters.toBytes("g"))));
values = clusterConnection.zRangeByLex(KEY_1_BYTES, Range.range().lt("c"));
assertThat(values, hasItems(JedisConverters.toBytes("a"), JedisConverters.toBytes("b")));
assertThat(values, not(hasItem(JedisConverters.toBytes("c"))));
values = clusterConnection.zRangeByLex(KEY_1_BYTES, Range.range().gte("aaa").lt("g"));
assertThat(
values,
hasItems(JedisConverters.toBytes("b"), JedisConverters.toBytes("c"), JedisConverters.toBytes("d"),
JedisConverters.toBytes("e"), JedisConverters.toBytes("f")));
assertThat(values, hasItems(JedisConverters.toBytes("b"), JedisConverters.toBytes("c"),
JedisConverters.toBytes("d"), JedisConverters.toBytes("e"), JedisConverters.toBytes("f")));
assertThat(values, not(hasItems(JedisConverters.toBytes("a"), JedisConverters.toBytes("g"))));
values = clusterConnection.zRangeByLex(KEY_1_BYTES, Range.range().gte("e"));
assertThat(values,
hasItems(JedisConverters.toBytes("e"), JedisConverters.toBytes("f"), JedisConverters.toBytes("g")));
assertThat(
values,
not(hasItems(JedisConverters.toBytes("a"), JedisConverters.toBytes("b"), JedisConverters.toBytes("c"),
JedisConverters.toBytes("d"))));
assertThat(values, not(hasItems(JedisConverters.toBytes("a"), JedisConverters.toBytes("b"),
JedisConverters.toBytes("c"), JedisConverters.toBytes("d"))));
}
/**
@@ -2118,7 +2113,7 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
*/
@Test
public void infoShouldCollectionInfoFromAllClusterNodes() {
assertThat(clusterConnection.info().size(), is(3));
assertThat(Double.valueOf(clusterConnection.info().size()), closeTo(245d, 35d));
}
/**
@@ -2288,8 +2283,8 @@ public class JedisClusterConnectionTests implements ClusterConnectionTests {
@Test
public void clusterGetSlavesShouldReturnSlaveCorrectly() {
Set<RedisClusterNode> slaves = clusterConnection.clusterGetSlaves(new RedisClusterNode(CLUSTER_HOST,
MASTER_NODE_1_PORT));
Set<RedisClusterNode> slaves = clusterConnection
.clusterGetSlaves(new RedisClusterNode(CLUSTER_HOST, MASTER_NODE_1_PORT));
assertThat(slaves.size(), is(1));
assertThat(slaves, hasItem(new RedisClusterNode(CLUSTER_HOST, SLAVEOF_NODE_1_PORT)));

View File

@@ -16,6 +16,7 @@
package org.springframework.data.redis.connection.lettuce;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.collection.IsIterableContainingInOrder.*;
import static org.hamcrest.number.IsCloseTo.*;
import static org.junit.Assert.*;
import static org.springframework.data.redis.connection.ClusterTestVariables.*;
@@ -529,7 +530,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
nativeConnection.set(SAME_SLOT_KEY_2, VALUE_2);
assertThat(clusterConnection.mGet(SAME_SLOT_KEY_1_BYTES, SAME_SLOT_KEY_2_BYTES),
hasItems(VALUE_1_BYTES, VALUE_2_BYTES));
contains(VALUE_1_BYTES, VALUE_2_BYTES));
}
/**
@@ -541,7 +542,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
nativeConnection.set(KEY_1, VALUE_1);
nativeConnection.set(KEY_2, VALUE_2);
assertThat(clusterConnection.mGet(KEY_1_BYTES, KEY_2_BYTES), hasItems(VALUE_1_BYTES, VALUE_2_BYTES));
assertThat(clusterConnection.mGet(KEY_1_BYTES, KEY_2_BYTES), contains(VALUE_1_BYTES, VALUE_2_BYTES));
}
/**
@@ -2103,7 +2104,7 @@ public class LettuceClusterConnectionTests implements ClusterConnectionTests {
*/
@Test
public void infoShouldCollectionInfoFromAllClusterNodes() {
assertThat(clusterConnection.info().size(), is(3));
assertThat(Double.valueOf(clusterConnection.info().size()), closeTo(245d, 35d));
}
/**