当单个主库已经无法同时承受全部键空间、写吞吐和网络出口时,只增加副本并不能继续扩展写能力。Redis Cluster 的核心价值,是把键空间拆成多个分片主库,每个分片再各自拥有副本与故障转移能力。

系列导航:

16384 个哈希槽是 Cluster 的路由基础

Redis Cluster 不是直接把“键永久绑定到某台机器”,而是先把全部键映射到 016383 的 16384 个哈希槽:

slot = CRC16(key) mod 16384

然后再由不同主节点分别负责其中一部分槽位。这样设计的关键好处是:

  • 键到槽位的计算规则固定。
  • 槽位到节点的归属可以变化。
  • 扩容时只需要迁移部分槽位,而不是重算全量键路由。

所以 Cluster 真正分配的不是“某台机器负责某个前缀”,而是“某台主节点当前负责一组槽位”。

MOVEDASK 反映的是两种不同的路由状态

客户端第一次访问某个键时,未必知道它对应的槽位当前在哪个主节点。Cluster 就通过重定向响应把路由信息逐步纠正回来。

MOVED:槽位的稳定归属已经改变

当目标槽位已经明确归属于另一个主节点时,Redis 会返回类似:

MOVED 12182 192.168.10.12:7000

这表示客户端应该:

  • 把这次请求发到新地址。
  • 更新本地维护的槽位缓存。

MOVED 说明“稳定状态已经变了”,因此是应该长期记住的新路由。

ASK:槽位还在迁移中

迁槽过程中,一部分键已经到了新节点,另一部分还在旧节点。此时旧节点可能返回:

ASK 12182 192.168.10.12:7000

这不是让客户端永久修改缓存,而是告诉它:

  1. 这一次先去新节点。
  2. 到新节点前先发送 ASKING
  3. 迁移完成前,不要把整张槽位表立刻改成永久归属。

所以 MOVED 表示“归属已经稳定改变”,ASK 表示“当前处于迁移窗口中的临时通路”。

客户端路由能力是 Cluster 能不能落地的前提

Redis Cluster 没有像某些数据库那样的透明代理层。客户端要么自己理解 Cluster,要么就会不断收到重定向错误。

一个可用的 Cluster 客户端至少需要具备:

  • 能缓存槽位到节点的映射。
  • 能识别 MOVEDASK 并自动重试。
  • 在拓扑变化后刷新路由缓存。
  • 理解多键操作的槽位限制。

这也是为什么 redis-cli -c 和普通 redis-cli 的体验完全不同。前者会自动跟随重定向,后者只是把错误原样打印给你。

另外,多个键如果需要事务、Lua 或多键命令共同执行,通常还要借助哈希标签把它们放进同一个槽位,例如:

order:{1001}:header
order:{1001}:items

否则请求会因为跨槽而失败。Cluster 的路由能力不是纯粹的网络问题,它会反过来约束你的键设计和客户端选型。

扩容真正做的是“加节点 + 迁槽 + 让客户端完成路由收敛”

Redis Cluster 不是新增一个空节点就自动均衡完成。扩容通常至少包含三步:

  1. 把新节点加入集群。
  2. 为新主节点分配副本关系。
  3. 从现有主节点迁移一部分槽位到新主节点。

迁槽期间,客户端会看到 ASK;迁槽稳定后,新的槽位归属通过 MOVED 逐步收敛。真正需要关注的不是“容器已经多起来了”,而是:

  • 16384 个槽位是否仍然完整覆盖。
  • 新旧主节点的负载是否真的更均衡。
  • 客户端是否能在迁槽窗口内正常自动跟随。

缩容的逻辑也是反过来:先迁走目标节点负责的全部槽位,再把节点安全移出集群。开源 Redis Cluster 并不会因为你加了机器就自动完成理想重平衡。

Cluster 的故障转移仍然建立在复制之上

每个 Cluster 主节点与自己的副本之间,底层仍然使用 Redis 复制。区别在于:这里的故障转移是“按分片独立进行”,而不是整个系统只有一个逻辑主库。

典型过程可以概括为:

  1. 某主节点失联后,其他节点先从本地视角把它标成疑似故障。
  2. 疑似故障通过集群总线传播。
  3. 当多数主节点对该主节点形成故障共识后,它被标记为失败。
  4. 该分片的某个副本争取晋升授权。
  5. 获得支持的副本接管原主节点负责的槽位。

这里要特别记住两点:

  • 投票域看的是主节点多数派,不是副本数量多数派。
  • Cluster 里的主从复制依旧是异步的,因此故障切换时同样存在少量已确认写入尚未复制完成的丢失窗口。

也就是说,Cluster 解决的是“分片 + 分片级高可用”,不是“跨分片强一致”。

什么时候该选 Cluster

如果你的问题只是“单主 Redis 需要自动故障转移”,通常先到 Redis 主从复制与 Sentinel:从复制链路到自动故障切换(九) 就够了。真正该引入 Cluster 的场景通常是:

  • 数据量已经稳定超过单机可治理范围。
  • 写吞吐或网络出口需要多个主节点共同承担。
  • 客户端和数据模型能接受槽位限制与重定向逻辑。
  • 团队能驾驭迁槽、扩缩容和跨故障域副本分布。

Cluster 的复杂度不是多几个容器那么简单,而是你开始真的管理一个“带路由、带分片、带分片级故障转移”的系统。

本篇解决了什么:

  • 解释了 16384 个哈希槽为什么是 Cluster 路由与扩容的基础
  • 区分了 MOVEDASK、客户端缓存刷新和迁槽中的临时访问路径
  • 说明了 Cluster 适合解决分片扩展问题,而不是替代单主自动切换的所有场景

下一篇:Redis + .NET 实战:StackExchange.Redis 连接、读写与序列化(十一)