MIGRATE 是集群迁移槽位的底层动作。它看起来只是「把 key 挪到另一个实例」,但有几个细节直接影响线上迁移:它是同步的(大 key 会阻塞源)、它有批量形式(快很多)、它超时失败之后 key 到底在哪(不一定只在一边)。这篇把每一点都在真实实例上量出来。
> MIGRATE 127.0.0.1 7531 k1 0 5000 "OK" 源 EXISTS k1 = 0 目标 GET k1 = v1 > MIGRATE 127.0.0.1 7531 k2 0 5000 "OK" 目标 TTL k2 = 100 (源上设的是 100 秒) > MIGRATE 127.0.0.1 7531 nosuch 0 5000 "NOKEY" > MIGRATE 127.0.0.1 7531 l 0 5000 COPY "OK" COPY 之后:源 LLEN l = 3 目标 LLEN l = 3 > MIGRATE 127.0.0.1 7531 l 0 5000 COPY (error) ERR Target instance replied with error: BUSYKEY Target key name already exists. > MIGRATE 127.0.0.1 7531 l 0 5000 COPY REPLACE "OK" > MIGRATE 127.0.0.1 7531 "" 0 5000 KEYS h z "OK" 批量 KEYS:目标 HGETALL h = ['f', 'x'] ZRANGE z = ['m']
MIGRATE host port key db timeout:把 key 序列化,发给目标执行 RESTORE,目标回 OK 之后源再删掉。结果:成功 OK,源上没有这个 key NOKEY。TTL 一并保留(源 100 秒 → 目标 TTL 100);list/hash/zset 都能搬。COPY 不删源;目标已有同名 key 时报 BUSYKEY,加 REPLACE 才覆盖;KEYS k1 k2 … 是批量形式(此时 key 参数写空字符串 "")。
value 大小 MIGRATE 耗时 源上 PING 最大停顿 目标上 PING 最大停顿
10 MB 43 ms 41 ms 29 ms (返回 OK,目标 STRLEN = 10 MB)
100 MB 232 ms 231 ms 153 ms (返回 OK,目标 STRLEN = 100 MB)
300 MB 634 ms 634 ms 462 ms (返回 OK,目标 STRLEN = 300 MB)MIGRATE 在源上是同步执行的:100 MB 的字符串用了 232 ms,同时源上另一个连接的 PING 最大停顿 231 ms——整个 MIGRATE 期间源节点几乎不响应任何命令。目标也被阻塞(153 ms,处理 RESTORE 的那一段)。耗时与大小近似线性:10 MB 43 ms、100 MB 232 ms、300 MB 634 ms。这是本机回环上的数字;跨机器的网络传输会让源上的阻塞更长。所以含大 key 的槽,迁移时会让这个节点的所有槽一起卡住(第 82 篇)。
逐个 MIGRATE(1000 次调用):60 ms;一次 MIGRATE ... KEYS(1000 个 key):3 ms;快 21.4 倍;目标 DBSIZE = 1000
1000 个 100 字节的小 key:逐个调用 MIGRATE 1000 次花了 60 ms,一次 MIGRATE … KEYS 只要 3 ms,快 21 倍。这是本机回环上的比值;跨机器时每次往返更贵,批量的优势会更大(我没有测)。
MIGRATE(timeout 800 ms)→ IOERR error or timeout reading to target instance,耗时 803 ms;此刻源上 EXISTS k = 1 目标解冻后:EXISTS k = 1,GET = important;源上 EXISTS k = 1
把目标进程冻结,MIGRATE(超时 800 ms)等到超时返回 IOERR error or timeout reading to target instance,源上 key 还在(没收到目标的 OK,不会删)。但是:解冻目标之后,它读到了早先发出的 RESTORE 并执行了——目标上也出现了这个 key。两边都有这个 key。所以MIGRATE 报 IOERR 时,不能假设 key 没有到达目标;重试要用 REPLACE 或先检查目标,否则会遇到 BUSYKEY;(集群迁移期间这个「两边都有」的状态会不会带来问题,我没有验证。)