bigywater.tech
Redis · 81

Redis MIGRATE

MIGRATE 是集群迁移槽位的底层动作。它看起来只是「把 key 挪到另一个实例」,但有几个细节直接影响线上迁移:它是同步的(大 key 会阻塞源)、它有批量形式(快很多)、它超时失败之后 key 到底在哪(不一定只在一边)。这篇把每一点都在真实实例上量出来。

231 ms
MIGRATE 一个 100 MB 字符串时,源节点上另一个连接 PING 的最大停顿(MIGRATE 共 232 ms)
21 倍
一次 MIGRATE … KEYS(1000 个)比逐个 MIGRATE 1000 次快多少
两边都有
目标冻结导致 IOERR 后,解冻的目标上出现了这个 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:同步执行,阻塞两端

另一个连接每 1 ms 发一次 PING,记录 MIGRATE 期间的最大停顿
实测 · 本机执行输出
  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 篇)。

批量 KEYS

实测 · 本机执行输出
逐个 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 倍。这是本机回环上的比值;跨机器时每次往返更贵,批量的优势会更大(我没有测)。

目标卡住:失败之后 key 在哪

用 SIGSTOP 冻结目标进程
实测 · 本机执行输出
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;(集群迁移期间这个「两边都有」的状态会不会带来问题,我没有验证。)

分步演示:MIGRATE 的语义与代价

Redis MIGRATE
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Redis 8.10.2 独立实例(本机回环端口,save 关闭),redis-cli 风格输出由自写的 RESP 客户端打印;计时用 time.perf_counter,数字每次运行会有小幅抖动。两个独立实例(非集群);value 是 x 重复的字符串;停顿由另一个连接每 1 ms 一次的 PING 测得;失败实验用 SIGSTOP
    • 没有验证:跨机器网络下的耗时与阻塞、list/hash 等大集合的 MIGRATE 耗时、AUTH/AUTH2 选项、集群里对 MIGRATE 与 RESTORE-ASKING 的关系、IOERR 后目标上 key 是否总会出现(这里只观察了一次冻结)
    京ICP备2026067502号-1京公网安备11010502063487号