集群客户端最先遇到的两个错误是 MOVED 和 ASK,看起来都是「去别的节点问」,含义却完全不同。一个说「这个槽归它了,永远」,一个说「这一次去它那里问,别改你的地图」。这篇不靠概念,直接用 redis-cli 一样的原始命令,在一个正在迁移槽位的真实集群上,把两者出现的每一种情形都跑出来。
> GET foo "bar" > GET foo (error) MOVED 12182 127.0.0.1:7342 > SET foo x (error) MOVED 12182 127.0.0.1:7342
槽 12182 属于 7342。发给另一个主库 7340 的 GET foo、SET foo x 都得到 MOVED 12182 127.0.0.1:7342:节点不会代办转发,只指路。
> GET foo (error) MOVED 12182 127.0.0.1:7342 > READONLY "OK" > GET foo "bar"
从库默认也回 MOVED。在连接上发一次 READONLY,之后这条连接上的读命令可以被从库自己处理(我只测了读命令)。
> CLUSTER SETSLOT 12182 IMPORTING 75a351996149e140b8b7fb4cc22c69e642f91842 "OK" > CLUSTER SETSLOT 12182 MIGRATING 33d348498d505ba72affc45ae4791ba0b034af81 "OK" > CLUSTER COUNTKEYSINSLOT 12182 (integer) 2
迁移一个槽分两步设置:目标节点 CLUSTER SETSLOT 槽 IMPORTING 源节点ID,源节点 CLUSTER SETSLOT 槽 MIGRATING 目标节点ID。此时槽仍然属于源节点,槽里有 2 个 key(foo 和 {foo}exists,后者用 hash tag 落在同一个槽,第 72 篇)。
> GET {foo}exists
"1"
> GET {foo}notthere
(error) ASK 12182 127.0.0.1:7340
> SET {foo}newkey 1
(error) ASK 12182 127.0.0.1:7340
> MGET {foo}exists {foo}notthere
(error) TRYAGAIN Multiple keys request during rehashing of slot源节点对「自己还有的 key」照常服务(GET {foo}exists 得到 "1");对「自己没有的 key」——无论是读一个不存在的 key,还是新写入一个 key——回 ASK 12182 127.0.0.1:7340:这个 key 如果存在,就一定已经在目标那里了;新 key 也只能写到目标。MGET 同时带一个存在、一个不存在的 key,两边各有一部分,回 TRYAGAIN。
> GET {foo}notthere
(error) MOVED 12182 127.0.0.1:7342
> ASKING
"OK"
> GET {foo}notthere
(nil)
> ASKING
"OK"
> SET {foo}newkey written-on-target
"OK"
ASKING 只对紧接着的一条命令有效:
> GET {foo}newkey
(error) MOVED 12182 127.0.0.1:7342目标节点此时还不「拥有」这个槽,所以直接发命令会被打回 MOVED 指向源。必须先发 ASKING,紧接着的下一条命令才会被处理(GET 返回 nil、SET 成功)。ASKING 只管一条命令——第二次直接读同一个 key 又是 MOVED。所以 ASK 是「这一次」的指路,不应该更新客户端缓存的槽位表;MOVED 才是。
> MIGRATE 127.0.0.1 7340 {foo}exists 0 5000
"OK"
> GET {foo}exists
(error) ASK 12182 127.0.0.1:7340
> ASKING
"OK"
> GET {foo}exists
"1"> MIGRATE 127.0.0.1 7340 foo 0 5000 "OK" > CLUSTER SETSLOT 12182 NODE 33d348498d505ba72affc45ae4791ba0b034af81 "OK" > CLUSTER SETSLOT 12182 NODE 33d348498d505ba72affc45ae4791ba0b034af81 "OK" > GET foo (error) MOVED 12182 127.0.0.1:7340 > GET foo "bar"
MIGRATE 把 key 从源搬到目标(第 81 篇)。搬完一个 key,源上再读它也变成 ASK。把槽里的 key 都搬完之后,在两端各执行 CLUSTER SETSLOT 槽 NODE 目标ID 完成迁移:源节点从此回的是 MOVED,目标直接服务。(生产里用 redis-cli --cluster reshard 自动完成这些步骤,第 79 篇。)