redis cluster和proxy会根据key来计算value对应的slot,相同slot的key落在相同的机器上。
slot计算公式
slot = CRC16(key) % 16384
使用注意
1. 防止单台机器压力过大
由于相同slot的数据都会落在同一台机器,所以对hash、list、set、zset数据结构需要合理设计key及field,防止单个key中数据过多,造成机器压力较大。
2. 使相同key落在相同server中
redis命令或者lua脚本只能同时访问操作落在相同server上的多个key,如果需要在lua脚本中操作多个key该怎么办?
redis其实只会对key的{}中内容进行计算slot,不同key使用相同的{}内容计算出来slot相同。示例如下:
3. redisson lua槽位踩坑
redisson使用RScript来实现lua脚本,使用KEYS数组接收要操作的key列表,使用ARGV数组来接收field、value等值。
实际案例描述
需要使用lua脚本来执行hmset,所以打算将field与value对都存放在ARGV中,并且在执行hmset时对ARGV使用unpack,表示接收的参数是一个数组。但同时还需要为key设置过期时间,这使得还需要传入一个过期时间参数,出于简单考虑将过期时间放入KEYS数组中传入并使用,然后实际却会报错:
Caused by: org.redisson.client.RedisException: ERR slot id unmatch.. channel:
但是lua脚本实际上只操作了一个key,KEYS[2]只是作为expire操作的参数。
修改expire操作参数为一个常量,经过测试仍然会报错,但后来不再把expireTime放在KEYS数组中便不再报错。可以得出结论:
- redisson(可能是redis)会对KEYS数组中所有值判断是否在一个slot中,不管这些key有没有被实际操作
所以在使用redis lua脚本时要严格将要操作的key放在KEYS数组中传入,将各种field、value、time放入ARGV数组中传入。
代码示例
将expireTime也放入ARGV数组中传入的代码:
"local unpack = unpack or table.unpack; " +
"local hashArray = {}; " +
"local hashStartIndex = 2; " +
"for i = hashStartIndex , #ARGV do " +
" hashArray[i - hashStartIndex + 1] = ARGV[i]; " +
"end " +
"local exists = redis.call('EXISTS', KEYS[1]);" +
"if (exists == 0) then " +
" redis.call('HMSET', KEYS[1], unpack(hashArray)); " +
"end " +
"redis.call('EXPIRE', KEYS[1], ARGV[1]); " +
"return redis.call('HGETALL', KEYS[1]); ";