一、问题描述
Redis是我们在项目开发中常用的缓存中间件,Redis Cluster是Redis的分布式实现,可以帮我们提高架构是稳定性,可用性。在一次项目开发过程中,使用Redis进行两个Key进行比较,发生下面异常:
org.springframework.dao.InvalidDataAccessApiUsageException: CROSSSLOT Keys in request don't hash to the same slot. channel: nested exception is org.redisson.client.RedisException: CROSSSLOT Keys in request don't hash to the same slot. channel: [id: 0xd8e0d004, L:/10.186.72.183:49438 - R:10.150.25.24/10.150.25.24:6001] command: (SDIFF), params: [[111, 101, 115, 45, 112, 111, 111, 108, 45, 100, ...], [111, 101, 115, 45, 112, 111, 111, 108, 45, 100, ...]]
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:48)
at org.redisson.spring.data.connection.RedissonExceptionConverter.convert(RedissonExceptionConverter.java:35)
at org.springframework.data.redis.PassThroughExceptionTranslationStrategy.translate(PassThroughExceptionTranslationStrategy.java:44)
at org.redisson.spring.data.connection.RedissonConnection.transform(RedissonConnection.java:234)
at org.redisson.spring.data.connection.RedissonConnection.syncFuture(RedissonConnection.java:229)
二、解决方案
将Redis的Key中固定的值用{}包括起来。
举例:key1=order:1,key2=order:2
我们将order用{}括起来:key1={order}:1,key2={order}:2
这样问题就可以解决了。
三、问题分析(知其然,知其所以然)
报错日志的意思是:请求的Key键没有散列到同一个槽位中。
Redis 集群的键空间被分割为 16384 个槽(slot), 集群的最大节点数量也是 16384 个。
每个Key都会经过CRC16计算散列到固定的槽位中,对于多个键,仅当它们都共享相同的连接插槽时才执行(源码中的描述)。
Redis集群规范中说到:
Jedis源码中的体现:
public static int getSlot(byte[] key) {
if (key == null) {
throw new JedisClusterOperationException("Slot calculation of null is impossible");
}
int s = -1;
int e = -1;
boolean sFound = false;
for (int i = 0; i < key.length; i++) {
if (key[i] == '{' && !sFound) {
s = i;
sFound = true;
}
if (key[i] == '}' && sFound) {
e = i;
break;
}
}
if (s > -1 && e > -1 && e != s + 1) {
return getCRC16(key, s + 1, e) & (16384 - 1);
}
return getCRC16(key) & (16384 - 1);
}
Redisson源码中的体现:
有点小问题:如果只拼前缀{ ,如“{order:1”,则会报错 java.lang.IllegalArgumentException: 1 > -1
public int calcSlot(byte[] key) {
if (key == null) {
return 0;
}
int start = indexOf(key, (byte) '{');
if (start != -1) {
int end = indexOf(key, (byte) '}');
key = Arrays.copyOfRange(key, start+1, end);
}
int result = CRC16.crc16(key) % MAX_SLOT;
return result;
}