CROSSSLOT 是从单机迁到 Redis Cluster 时最常见的报错之一。很多人以为它的意思是「这两个 key 不在同一个节点」,其实不是。这篇用一个 3 主集群,找一对「同节点、不同槽」的 key 来证明这一点,再看哪些命令会触发它、事务和 Lua 怎么处理,以及一个容易被忽略的 Lua 漏洞。
> MSET key0 1 key4 2
(error) CROSSSLOT Keys in request don't hash to the same slot
> MSET {t}1 1 {t}2 2
"OK"
> MGET {t}1 {t}2
1) "1"
2) "2"选了 key0(槽 13252)和 key4(槽 13120):它们都在节点 7372 上,槽不同。直接发给 7372 的 MSET key0 1 key4 2 仍然报 CROSSSLOT Keys in request don't hash to the same slot。改成 hash tag {t}1、{t}2 就成功。原因不深:槽是集群迁移和路由的最小单位,服务器只保证「同一个槽的 key 会一起搬」。
> DEL key0 key4
(error) CROSSSLOT Keys in request don't hash to the same slot
> MGET key0 key4
(error) CROSSSLOT Keys in request don't hash to the same slot
> RENAME key0 key4
(error) CROSSSLOT Keys in request don't hash to the same slot
> SUNION key0 key4
(error) CROSSSLOT Keys in request don't hash to the same slot
> SADD key0 1
(integer) 1
> SINTERSTORE key4 key0 {q}1
(error) CROSSSLOT Keys in request don't hash to the same slot所有「一条命令里涉及多个 key」的命令都要求它们同槽:DEL、MGET、RENAME、SUNION、SINTERSTORE……(SADD key0 1 只有一个 key,当然没问题。)
> SUNION key0 key0 1) "1" > SCARD key0 (integer) 1
同一个 key 在一条命令里出现多次不算跨槽;单个 key 的命令(SCARD key0)当然也不会。
> MULTI
"OK"
> SET key0 1
"QUEUED"
> SET key4 2
"QUEUED"
> EXEC
(error) CROSSSLOT Keys in request don't hash to the same slot
(改成同一个槽:)
> MULTI
"OK"
> SET {t}a 1
"QUEUED"
> SET {t}b 2
"QUEUED"
> EXEC
1) "OK"
2) "OK"MULTI 之后的两条 SET 都返回 QUEUED,EXEC 时因为两个 key 不同槽整体被拒:CROSSSLOT,一条都没有执行。改成同槽的 {t}a、{t}b 之后正常提交。
> EVAL "return redis.call('SET', KEYS[1], 1) and redis.call('SET', KEYS[2], 2)" 2 key0 key4
(error) CROSSSLOT Keys in request don't hash to the same slot
> EVAL "return redis.call('SET', KEYS[1], 1) and redis.call('SET', 'undeclared', 2)" 1 key0
(error) ERR Script attempted to access a non local key in a cluster node script: 2f8a88effc3094ba5d57fc8e5724b35aa11e34d5, on @user_script:1.
(下面这条:没有在 KEYS 里声明的 key4,它和 key0 在同一个节点、不同槽)
> EVAL "redis.call('DEL', KEYS[1]); return redis.call('SET', ARGV[1], 2)" 1 key0 key4
"OK"
> EVAL "redis.call('SET', KEYS[1], 1); redis.call('SET', KEYS[2], 2); return 'ok'" 2 {t}x {t}y
"ok"KEYS 里声明两个不同槽的 key → CROSSSLOT;声明同槽的 → 成功。更需要小心的是没有声明的 key:在脚本里直接写字符串或通过 ARGV 传入的 key,如果它属于别的节点,服务器报 Script attempted to access a non local key in a cluster node;但如果它和声明的 key 在同一个节点、只是槽不同(如 key4),脚本竟然执行成功。服务器只检查「是否在本节点」,不检查「是否同槽」。这样写的脚本在单个节点上能跑,但如果这个槽被迁移走,脚本会突然失败——所以脚本要访问的所有 key 都应该老老实实声明在 KEYS 里、并且同槽。
['OK', '1', 2]
流水线里的多条单 key 命令没有限制——每条命令只涉及一个槽。
> MSET key0 1 key1 2 (error) CROSSSLOT Keys in request don't hash to the same slot
分属不同节点的两个 key 发 MSET 同样是 CROSSSLOT(而不是 MOVED)。