#2075·ioredis

自动流水线: 由于管道级别的 MOVED 处理,在集群拓扑变更期间命令失败

作者: ivoronin创建于 2026年2月9日更新于 2026年9月16日

背景 自动管道将命令按节点分组: 所有命令的节点都由同一组节点提供服务,因此它们最终会被放入一个管道中。一个单独的自动管道通常包含针对许多不同节点的命令。对于单独的(未管道化)命令,集群客户端会透明地处理 MOVED 错误 - 它会捕获重定向,更新路由,并重试命令到正确的节点。应用程序永远不会看到 MOVED 错误。 # 问题 考虑一个拓扑变化,其中节点 200 从节点 A 迁移到节点 C,而节点 100 则保持在节点 A 上: 1. 自动管道将节点 100 和节点 200 的命令组合在一起(在批量处理时均由节点 A 服务) 2. 管道将所有内容发送到节点 A 3. 节点 100 的命令成功,节点 200 的命令返回 MOVED 200 NodeC:port 之后的情况取决于命令组合: ## 失败模式 1: 具有写操作的管道 如果任何成功的命令都是写操作(SET、INCR 等),管道会认为自己不可重试,并返回原始结果。成功的命令正常解析,但 MOVED 命令会以原始错误的形式传递给应用程序。 这是不可预期的 - 应用程序不希望看到 MOVED 错误,因为集群客户端会对单个命令透明地进行处理。 在此路径中,整个 MOVED 处理块被忽略:不进行节点映射更新,也不调用 refreshSlotsCache()。过时的节点映射持续存在,因此后续的自动管道批次将继续被路由到错误的节点,并继续出现相同的 MOVED 错误。由于默认情况下未配置 slotsRefreshInterval,映射将保持过时,直到外部触发刷新(例如,非自动管道命令遇到 MOVED)。 ## 失败模式 2: 只读管道(更糟) 如果所有成功的命令都是读取,管道会认为自己可重试。它会解析 commonError 消息,更新该一个节点的映射(节点 200 现在指向节点 C),并重新运行 exec()。但是,节点映射现在只部分修正 - 移动处理器调用了 refreshSlotsCache(),但它是异步的(将 CLUSTER SLOTS 发送到一个节点),而重试 exec() 则在全面拓扑到位之前同步运行。 在重试中, generateMultiWithNodes 会看到节点 100 和节点 200 现在属于不同的分配组,并拒绝管道,并返回 `"All keys in the pipeline should belong to the same slots