Redis分片集群原理

1. 为何需要分片集群?

让我们先快速回顾一下 Redis 扩展的演进过程:

  • 单机 Redis: 最简单,但也最受限于服务器的物理资源(CPU、内存、网络带宽)。一旦宕机,服务完全中断。

  • 主从复制: 通过设置一个主节点和多个从节点,实现了读写分离,提高了读并发能力,并提供了数据冗余以应对主节点故障。但所有数据仍存储在主节点上,内存容量和写性能依然受限于单个服务器,无法无限扩展。

当业务数据量达到数十 GB 甚至 TB 级别,或者写入请求 QPS 达到数万甚至更高时,单机主从架构就难以支撑了。这时,我们需要一种方式来将数据分散到多台机器上,共同承担存储和处理的压力,这就是分片集群的核心诉求。


2. Redis Cluster 的核心设计理念

Redis Cluster 的设计目标是提供高可用性、高性能、线性可伸缩性,并且尽可能地简化运维。它实现了去中心化架构,每个节点既是数据存储单元,也参与集群状态管理。

2.1 哈希槽 (Hash Slot) 机制

Redis Cluster 并不是简单地按照 Key 的哈希值来分配节点,而是引入了哈希槽 (Hash Slot) 的概念。

  • 键空间划分: Redis Cluster 将整个键空间划分为 16384 个哈希槽。这个数字是 Redis 作者经过权衡得出的,既足够大以保证数据分布的均匀性,又不会太大导致槽位管理开销过高。

  • Key 到槽位的映射: 任何一个 Key,都可以通过以下公式确定其所属的哈希槽:

    hash_slot=CRC16(key)(mod16384)

    其中 CRC16(key) 是对 Key 进行 CRC16 校验和计算。

  • 槽位分配: 集群中的每个主节点负责管理一部分哈希槽。例如,一个拥有 3 个主节点的集群,可能第一个主节点负责 0-5460 号槽位,第二个负责 5461-10922 号槽位,第三个负责 10923-16383 号槽位。

  • 数据定位: 当客户端要操作某个 Key 时,它会先计算出这个 Key 对应的哈希槽,然后直接连接到负责这个槽位的主节点进行操作。这种设计避免了中心节点的性能瓶颈,实现了数据访问的直接寻址

2.2 去中心化架构与 Gossip 协议

Redis Cluster 采用去中心化 (Decentralized) 架构,这意味着集群中没有独立的中心节点(如 ZooKeeper 或 Etcd)来管理集群状态。每个节点都保存了完整的集群元数据(槽位信息、节点状态等),并通过 Gossip 协议进行通信和同步。

  • 节点互联: 集群中的每个节点都通过 TCP 连接与其他节点通信,使用特殊的 Redis Cluster Bus 协议来交换集群状态信息。

  • 状态同步: 节点之间周期性地发送心跳包,交换各自对集群状态的认知,包括哪些节点在线、哪些节点下线、槽位分配情况等。通过这种方式,所有节点最终会达成一致的集群视图。

  • 优势: 这种去中心化设计避免了中心节点单点故障的风险,提高了集群的健壮性和可用性。

2.3 自动故障转移 (Automatic Failover)

Redis Cluster 内置了强大的自动故障转移机制,确保当主节点发生故障时,集群能够自动恢复服务。

  • 故障检测 (Failure Detection):

    • PFAIL (Possible Failure): 集群中的节点会定期向其他节点发送 Ping 消息。如果一个节点在 cluster-node-timeout 时间内没有收到某个节点的 Pong 回复,它会标记该节点为 PFAIL (可能下线)。

    • FAIL (Failure): 当集群中多数主节点都认为某个节点被标记为 PFAIL 时,这个节点就会被正式标记为 FAIL (确认下线)。

  • 选举 (Election): 当一个主节点被标记为 FAIL 后,其对应的所有从节点中会有一个从节点发起竞选,尝试晋升为新的主节点。

    • 从节点会向集群中的其他所有主节点发送选举请求。

    • 获得集群中半数以上主节点投票的从节点,将成功晋升为新的主节点。

  • 配置传播: 故障转移成功后,新的主节点会广播自己的身份和槽位信息,其他节点也会更新集群配置,客户端也会被重定向到新的主节点。整个过程通常在几秒内完成,对应用的影响较小。


3. 客户端与集群的交互

与单机 Redis 不同,客户端连接 Redis Cluster 需要具备集群感知能力。大多数流行的 Redis 客户端库(如 Java 的 Jedis、Lettuce;Go 的 go-redis)都支持 Redis Cluster 模式。

  • 启动连接: 客户端只需提供集群中任意一个或多个节点的地址即可发起连接。

  • 获取拓扑信息: 客户端连接到某个节点后,会发送 CLUSTER SLOTS 命令来获取整个集群的槽位分布信息(哪个槽位由哪个主节点负责,以及该主节点的从节点信息)。客户端会将这些信息缓存起来。

  • 命令路由: 客户端收到用户请求后,会根据 Key 计算出对应的哈希槽,然后直接连接到负责该槽位的主节点进行操作。

  • 重定向: 如果客户端的槽位缓存信息过时(例如发生了故障转移或槽位迁移),客户端可能会向错误的节点发送请求。此时,目标节点会返回 MOVEDASK 重定向指令:

    • MOVED : 表示该槽位已经永久迁移到新的节点。客户端会更新本地缓存,并重新向新节点发送请求。

    • ASK : 表示该槽位正在迁移中,请先向目标节点发送 ASKING 命令,然后发送实际操作。客户端收到 ASK 后,会先发送 ASKING,然后重发请求。这是一个临时重定向,客户端不会更新本地槽位缓存。


4. 数据一致性与 Key 的设计

在 Redis Cluster 中,数据一致性是一个需要重点关注的问题:

  • 最终一致性: Redis Cluster 保证的是最终一致性。在故障转移过程中,如果主节点宕机前有一些数据尚未同步到从节点,那么这些数据可能会丢失。应用程序需要评估其对数据一致性的要求。

  • 多 Key 操作: Redis Cluster 不支持跨槽位的 Multi-Key 原子操作(如 MGETMSETDEL 多个 Key 等),因为这些 Key 可能分布在不同的节点上。

    • 解决方案: 如果需要操作的多个 Key 必须在同一槽位,可以使用 哈希标签 (Hash Tag)。在 Key 中使用 { } 包裹一部分字符串,Redis 会只对 { } 内的字符串计算哈希槽。例如,{user100}:profile{user100}:orders 会被分到同一个槽位。

  • 事务与 Lua 脚本: Redis 事务和 Lua 脚本的原子性也仅限于单个节点内部。

5. 扩容与缩容

Redis Cluster 支持在线扩容和缩容,无需停机。

  • 扩容:
    • 启动新的 Redis 实例,并以集群模式加入现有集群。
    • 使用redis-cli --cluster reshard命令,将现有集群中部分槽位迁移到新加入的主节点。
    • 如果新节点需要作为某个主节点的从节点,再进行相应的配置。
  • 缩容:
    • 使用 redis-cli --cluster reshard 命令,将要下线的节点上的所有槽位迁移到集群中其他健康的节点上。
    • 使用 redis-cli --cluster del-node 命令,将该节点安全地从集群中移除。

总结

Redis 分片集群是解决单机 Redis 性能和容量瓶颈的强大方案。它通过将键空间划分为 16384 个哈希槽,并将这些槽位分配给不同的主节点,实现了数据的水平分布。其去中心化架构、基于 Gossip 协议的通信和自动故障转移机制,共同保障了集群的高可用性和健壮性。

你可能感兴趣的:(Redis分片集群原理)