直接用migrate命令迁移大key会失败,因其仅适用于单实例间原子迁移,集群中key的slot归属由哈希计算决定,目标节点若不负责该slot则拒绝接收并报错moved;强行直连还可能导致ask/moved重定向失效、数据不可达。

直接用 MIGRATE 命令迁移大Key会失败
集群环境下 MIGRATE 无法跨节点原子迁移大Key,因为该命令只在单实例间工作,而集群中 Key 的槽位(slot)归属由哈希计算决定,不是靠客户端指定目标节点。你执行 MIGRATE target_host 6379 big_hash_key 0 5000 时,Redis 会报错 ERR Target instance replied with error: MOVED —— 它发现目标节点不负责这个 Key 的 slot,拒绝接收。
更关键的是,即使绕过集群直连某个节点强行调用 MIGRATE,一旦该 Key 被拆分或重哈希,后续请求就会因 ASK/MOVED 重定向失败,导致数据不可达。
- 集群模式下禁止对任意 Key 手动指定迁移目标节点
-
MIGRATE在集群中仅适用于已知 slot 归属、且目标节点就是该 slot master 的极少数调试场景 - 大Key迁移必须依赖集群原生命令链:
CLUSTER SETSLOT ... MIGRATING+CLUSTER GETKEYSINSLOT+MIGRATE(批量 keys)
必须用 CLUSTER GETKEYSINSLOT 分批导出字段
Hash 类型的大Key(比如 user:profile:1001 含 8 万字段)不能用 HGETALL 一次性拉取——它会触发输出缓冲区溢出、阻塞主线程,并可能被 client buffer 阈值(client-output-buffer-limit)强制断连。
正确做法是先定位 Key 所在 slot:CLUSTER KEYSLOT user:profile:1001 返回 12345,再用游标式分批获取该 slot 下的 key 列表,从中筛选出目标 Key 并逐批迁移其字段:
127.0.0.1:7001> CLUSTER GETKEYSINSLOT 12345 100 1) "user:profile:1001" 2) "order:log:9999"
拿到 key 名后,用 HSCAN 分片读取字段,每次最多 500 字段:
127.0.0.1:7001> HSCAN user:profile:1001 0 COUNT 500 1) "142" 2) 1) "field_a" 2) "val_a" 3) "field_b" 4) "val_b" ... 127.0.0.1:7001> HSCAN user:profile:1001 142 COUNT 500
-
CLUSTER GETKEYSINSLOT每次最多返回 1000 个 key,不能保证包含全部目标 Key,需循环调用直到游标返回 0 - 若目标 Key 是 List/ZSet/Hash,必须用对应 SCAN 命令(
LTRIM+LRANGE、ZSCAN、HSCAN),不能用全量命令 - 每次 SCAN 后立即写入新 Key(如
user:profile:1001:shard0),再用HDEL删除已迁字段,避免重复迁移
UNLINK 替代 DEL 是唯一安全的清理方式
迁移完成后,源 Key 还剩大量字段未删尽?千万别用 DEL。对含数万字段的 Hash 执行 DEL,主线程会同步遍历并释放每个字段内存,卡顿长达数秒,引发集群心跳超时、主从切换。
必须改用 UNLINK:
127.0.0.1:7001> UNLINK user:profile:1001 (integer) 1
它把释放操作交给后台线程(lazyfree),主线程瞬间返回。但要注意两个前提:
- 确保
lazyfree-lazy-user-del yes已在 redis.conf 中开启(Redis 4.0+ 默认关闭) -
UNLINK不支持通配符,只能对明确 key 名操作;若需批量清理残留,得配合SCAN+UNLINK脚本 - 迁移中途若崩溃,残留部分字段的旧 Key 仍可读,但业务逻辑必须兼容“半迁移”状态(例如读逻辑优先查新 Key,未命中再查旧 Key)
集群槽位迁移期间必须禁用写入或做双写兜底
当你要把整个 slot(比如 12345)从 node A 迁到 node B,执行 CLUSTER SETSLOT 12345 MIGRATING nodeB 后,node A 对该 slot 的写入会返回 ASK 重定向。但很多老客户端不处理 ASK,直接报错或丢数据。
真实生产中必须主动控制写入路径:
- 停写窗口期:短暂停止对该 slot 所有 Key 的写入(适合低峰期、可接受秒级中断的场景)
- 双写策略:应用层同时写旧 Key 和新 Key(如
HSET user:profile:1001:shard0 f v+HSET user:profile:1001:shard1 f v),迁移完成后再切读流量 - 代理层拦截:用 Twemproxy 或 Redis Proxy 在路由层识别大Key,自动转发到新分片,避免业务改代码
最易被忽略的一点:迁移脚本里没校验 CLUSTER NODES 输出中目标节点是否已真正接管 slot —— 仅靠 CLUSTER SETSLOT ... NODE 命令成功不代表传播完成,必须等所有 master 节点的 CLUSTER NODES 输出里都显示该 slot 归属新节点,才算落地。











