Redis Cluster 是 Redis 官方提供的分布式解决方案,与主从复制加哨兵模式不同,它原生支持数据自动分片与节点故障转移。理解 Redis 集群的数据分布规则和故障恢复机制,有助于在生产环境中正确处理扩容、迁移、节点宕机等场景。本文将从哈希槽模型、客户端路由、故障检测与转移几个层面展开解析。

哈希槽与数据分片模型
Redis Cluster 没有使用一致性哈希,而是将整个键空间划分为 16384 个哈希槽。每个主节点负责一部分槽位,节点启动后通过 cluster addslots 命令或配置文件声明自己管理的槽范围。客户端写入一个 key 时,会先根据 key 计算 CRC16 校验值,再对 16384 取模,得到 0 到 16383 之间的槽编号,最后将命令发送到负责该槽的节点。
这种固定槽位方案相比一致性哈希更加直观,节点扩容或缩容时只需要迁移部分槽位,而不是重新计算整个环上的虚拟节点。计算 slot 的典型代码如下:
import binascii
def crc16_slot(key):
# Redis Cluster 使用 CRC16 后对 16384 取模
return binascii.crc_hqx(key.encode(), 0) & 16383
print(crc16_slot("user:1001"))
print(crc16_slot("order:202401"))
如果 key 中包含花括号,Redis 只会对花括号内的部分进行哈希,这被称为 hash tag。例如 user:{1001}:name 和 user:{1001}:age 会落在同一个槽,适合需要事务或多键操作的业务。不过滥用 hash tag 会导致数据倾斜,需要根据实际 key 分布评估。
为什么是 16384 个槽?官方解释是 16384 在心跳包中可以用 2KB 的位图表示,既能保证节点间传播槽信息的开销较小,又足够细粒度地平衡数据。如果槽过少,迁移一个槽会一次性移动大量 key;如果过多,心跳消息中携带的槽位信息会膨胀。实际生产中使用 16384 已经能满足绝大多数集群规模。
客户端路由与 MOVED/ASK 重定向
当客户端向错误的节点发送命令时,Redis 会返回错误信息,而不是自动转发。普通客户端必须处理两种重定向:MOVED 表示槽已经永久迁移到其他节点,ASK 表示槽正在迁移过程中,仅对当前请求临时重定向。例如在迁移过程中访问一个 key,可能收到 ASK 错误,要求先执行 ASKING 命令再重新发送。
现代客户端大多内置了槽位缓存,被称为 Smart Client。它会在首次连接后执行 CLUSTER SLOTS 命令,将槽与节点的映射关系缓存到本地,之后直接计算 slot 并连接对应节点。MOVED 错误会触发缓存更新,而 ASK 错误不会改变缓存。以 redis-py 为例,集群模式通常在构造客户端时指定 RedisCluster,底层会自动完成路由和重定向。
以下命令可以观察手动访问错误节点时的重定向行为:
# 连接 7000 节点,设置一个实际属于 7001 节点的 key redis-cli -p 7000 set username chen (error) MOVED 14282 127.0.0.1:7001 # 使用 -c 参数让 redis-cli 自动跟随重定向 redis-cli -c -p 7000 set username chen OK
MOVED 错误中包含了目标节点地址,客户端可以解析后重试。如果这个节点正在迁移槽,错误会变为 ASK,客户端需要先发送 ASKING 命令,再向目标节点发送原命令。这类机制保证了迁移期间请求不会丢失,但会增加一次额外往返。
故障检测与自动故障转移
Redis Cluster 使用 Gossip 协议在节点之间传播状态。每个节点默认每秒向随机几个节点发送 PING,收到 PONG 后更新最近通信时间。当某个主节点超过 cluster-node-timeout 仍无法响应时,其他节点会将其标记为 PFAIL,即可能失效。PFAIL 只是单节点主观判断,还需要多数主节点达成一致才会升级为 FAIL。
FAIL 状态一旦确定,该主节点的从节点会参与选举。选举条件包括从节点和主节点的复制偏移量不能差距过大,否则可能丢失已写入的数据。从节点会向其他主节点发送投票请求,获得超过半数主节点投票后,晋升为新主节点,并接管原主节点的所有槽位。原主节点恢复后会自动成为新主节点的从节点。
配置文件中与故障转移相关的参数如下:
# redis.conf 集群参数 cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-require-full-coverage yes cluster-migration-barrier 1
cluster-require-full-coverage yes 表示任意槽位不可用时整个集群拒绝写请求,避免出现数据不一致但能保证完整性;如果改为 no,则部分槽不可用时集群仍可对外提供其他槽的读写,但可能产生部分数据丢失的风险。实际场景中需要根据业务可容忍性选择。
故障转移过程中的一个常见问题是脑裂。如果网络分区导致原主节点无法与其他节点通信,但客户端仍能访问它,而集群另一边已经选举出新主节点,就可能出现两个主节点同时写入同一批槽。Redis 通过多数派和配置纪元减少脑裂概率,但无法做到 100% 避免。通常建议每个主节点至少配置一个从节点,并让从节点分布在不同物理机或可用区。
集群扩缩容与运维注意点
集群运行过程中,增加节点或减少节点本质上是槽位迁移。使用 redis-cli --cluster add-node 添加新节点后,新节点默认不分片,需要通过 reshard 命令将部分槽从现有节点迁移过去。迁移过程中,Redis 会以槽为单位逐个移动 key,客户端可能遇到 ASK 错误,Smart Client 会处理这种临时重定向。
迁移时要特别注意大 key 的影响。如果某个 key 是数百 MB 的哈希或列表,迁移操作会阻塞对应节点的事件循环,导致该节点上的其他请求延迟升高。可以通过 redis-cli --bigkeys 扫描大 key,提前拆分或避免在业务高峰期迁移。另一个容易被忽略的问题是 cluster-config-file 路径权限,集群节点重启时会读取该文件恢复槽位信息,如果文件丢失或损坏,节点无法加入集群。
下面是一个检查集群槽位分布的示例:
redis-cli -p 7000 cluster slots redis-cli -p 7000 cluster info redis-cli -p 7000 cluster nodes
运维中还应该关注 cluster_state 是否为 ok,cluster_slots_assigned 是否等于 16384。如果某些槽未分配,集群会拒绝写入。通过这些命令可以快速定位分片异常和节点状态问题。