NoSQL之Redis集群

发布于:2025-06-10 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、 核心目标

  1. 自动分片 (Automatic Sharding): 将数据分布存储在集群的多个节点上。

  2. 高可用 (High Availability): 通过主从复制,在主节点故障时自动进行故障转移(failover),由从节点接替主节点工作。

  3. 线性扩展 (Linear Scalability): 通过增加节点可以近乎线性地提升集群的存储容量和处理能力。

二、 关键概念

  1. 节点 (Node):

    • 集群由多个 Redis 节点组成。

    • 每个节点都有一个唯一的 ID。

    • 每个节点都存储一份集群配置信息(cluster state),包含所有节点的信息、槽映射等。

    • 节点分为两种角色:主节点 (Master) 和 从节点 (Replica/Slave)

  2. 槽 (Slot):

    • Redis 集群将所有数据划分为 16384 (0 - 16383) 个哈希槽。

    • 数据分片的基本单位是槽,而不是节点或键。

    • 集群通过将不同的槽分配给不同的主节点来实现数据分片。

  3. 键空间分片 (Keyspace Sharding):

    • 客户端写入或读取一个键时,Redis 使用 CRC16 算法计算该键的哈希值。

    • 哈希值对 16384 取模 (CRC16(key) % 16384),得到一个介于 0 到 16383 之间的整数,这个整数就是该键所属的槽号

    • 客户端根据集群配置信息,知道哪个槽由哪个主节点负责,从而将命令路由到正确的节点。

  4. 主节点 (Master):

    • 负责处理客户端对其所负责槽的读写请求。

    • 存储其所负责槽的所有数据。

    • 参与集群状态管理和故障检测。

  5. 从节点 (Replica / Slave):

    • 复制其关联主节点的数据。

    • 当主节点发生故障时,在满足一定条件下,从节点可以通过选举被提升为新的主节点(故障转移),接管原主节点负责的槽。

    • 提供只读访问能力(可以配置readonly),分担主节点的读压力。

  6. 集群总线 (Cluster Bus):

    • 每个 Redis 节点都额外监听一个端口(通常是客户端端口 + 10000,如 6379 -> 16379),用于节点间通信。

    • 节点间使用二进制协议 (Gossip Protocol) 进行通信,包括:

      • 传播节点信息: 告知其他节点自己的存在、角色、负责的槽、状态等。

      • 传播集群状态: 更新集群配置纪元(epoch)和槽分配信息。

      • 故障检测 (Failure Detection): 通过 Ping/Pong 消息检测其他节点是否可达。节点会标记疑似下线 (PFAIL) 和确认下线 (FAIL) 的节点。

      • 故障转移授权: 在主节点确认下线 (FAIL) 后,协调从节点进行故障转移。

      • 发布/订阅消息: 用于手动故障转移 (CLUSTER FAILOVER) 等操作。

  7. 配置纪元 (Configuration Epoch):

    • 一个单调递增的计数器。

    • 用于在集群状态变更(如槽迁移、故障转移)时进行版本控制冲突解决

    • 拥有更高配置纪元的节点信息会覆盖旧纪元的信息,确保集群最终达成一致。

三、 集群如何工作?

  1. 启动集群:

    • 启动多个 Redis 节点,配置 cluster-enabled yes

    • 使用 redis-cli --cluster create 命令或手动执行 CLUSTER MEET 命令让节点互相发现并组成集群。

    • 将 16384 个槽分配给各个主节点 (CLUSTER ADDSLOTS 或 redis-cli --cluster addslots)。

    • 为每个主节点配置一个或多个从节点 (CLUSTER REPLICATE <master-node-id> 或 redis-cli --cluster add-node 时指定 --cluster-slave)。

  2. 客户端访问:

    • 客户端需要是 Redis 集群感知 (Cluster-Aware) 的客户端(如 JedisCluster, redis-py-cluster, StackExchange.Redis 等)。

    • Smart Client:

      • 客户端启动时获取一份集群槽位配置映射关系(可通过连接任意节点执行 CLUSTER SLOTS 或 CLUSTER NODES 命令获得)。

      • 计算键对应的槽号 (CRC16(key) % 16384)。

      • 根据本地缓存的槽-节点映射,将命令直接发送给负责该槽的主节点。

    • 重定向 (Redirection):

      • 如果客户端缓存过期或槽正在迁移,节点可能返回:

        • MOVED <slot> <ip>:<port>: 表示该槽已永久迁移到另一个节点。客户端应更新本地映射并重试到新节点。

        • ASK <slot> <ip>:<port>: 表示该槽正在迁移过程中,目标节点临时负责处理该槽的请求(仅针对本次查询)。客户端应先发送 ASKING 命令到目标节点,然后再发送原命令(只针对这个键)。客户端不需要更新本地槽映射。

  3. 主从复制:

    • 与单机 Redis 的主从复制机制相同。从节点异步复制主节点的数据。

  4. 故障检测与转移 (Failover):

    • 故障检测:

      • 节点间通过 Gossip 协议定期发送 Ping/Pong 消息。

      • 如果一个节点在 cluster-node-timeout 时间内无法与另一个节点通信,它会将其标记为 PFAIL (Possible Failure)。

      • 当某个节点收集到集群中大多数主节点都认为某个节点 PFAIL 时,它会将该节点标记为 FAIL (确认下线)。

    • 故障转移:

      • 当主节点被标记为 FAIL 时,其下的从节点会尝试发起故障转移。

      • 从节点会等待一个延迟时间(基于其复制偏移量排名,复制偏移量越接近主节点的从节点延迟越短),然后发起选举。

      • 从节点向集群中所有主节点广播 FAILOVER_AUTH_REQUEST 消息请求投票。

      • 主节点收到请求后,在一个纪元内只能投一票。如果主节点认为该从节点的主节点确实已 FAIL,并且该主节点尚未投票给其他从节点,则投赞成票 (FAILOVER_AUTH_ACK)。

      • 获得大多数主节点投票的从节点赢得选举。

      • 赢得选举的从节点执行以下操作:

        • 提升自己为新主节点。

        • 接管原主节点负责的所有槽。

        • 向集群广播 PONG 消息,宣布自己成为新主节点并更新槽分配信息。

      • 其他节点更新本地集群配置信息(槽映射、节点角色)。

      • 如果原主节点恢复,它会成为新主节点的从节点。

  5. 重新分片 (Resharding) - 槽迁移:

    • 可以在线动态地增加或移除节点,并重新分配槽。

    • 迁移过程:

      • 确定要从源节点迁移到目标节点的槽。

      • 在目标节点上执行 CLUSTER SETSLOT <slot> IMPORTING <source-node-id>,设置目标节点准备接收该槽的数据。

      • 在源节点上执行 CLUSTER SETSLOT <slot> MIGRATING <target-node-id>,设置源节点准备迁移该槽的数据。

      • 客户端向源节点请求迁移槽中的键时:

        • 如果键存在于源节点,正常处理。

        • 如果键不存在于源节点(已迁移走)或请求的是 ASKING 之后的命令,返回 ASK 重定向。

      • 使用 CLUSTER GETKEYSINSLOT <slot> <count> 获取槽中的键列表。

      • 对每个键,使用 MIGRATE <target-ip> <target-port> <key> 0 <timeout> 命令将键原子性地迁移到目标节点(键会被序列化传输,在目标节点反序列化,然后从源节点删除)。

      • 迁移完槽中的所有键后:

        • 在集群中任意节点执行 CLUSTER SETSLOT <slot> NODE <target-node-id>(通常在所有主节点上执行),将槽的归属权正式交给目标节点。这个命令会传播到整个集群。

        • 在源节点和目标节点上执行 CLUSTER SETSLOT <slot> NODE <target-node-id> 清除迁移/导入状态。

    • 自动化工具: 强烈建议使用 redis-cli --cluster reshard 命令来执行复杂的重新分片操作,它封装了上述步骤并处理了大量细节。

四、 Redis 集群的优势

  1. 高可用性: 自动故障转移保证服务连续性。

  2. 大容量: 突破单机内存限制,数据分布在多个节点。

  3. 高性能: 读写请求分散到多个节点处理,提高整体吞吐量。

  4. 线性扩展: 可通过增加节点轻松扩展容量和性能。

  5. 原生支持: Redis 官方维护,兼容性好,社区支持完善。

五、 Redis 集群的限制 (重要!)

  1. 多键操作限制:

    • 只支持对同一个槽中的多个键进行操作 (MGETMSETDELSINTERSUNIONSTORELUA Script 等)。

    • 如果操作的键分布在不同的槽或不同的节点上,命令会失败并报错 CROSSSLOT。需要使用 hash tags 强制将相关键映射到同一个槽。

  2. Lua 脚本限制:

    • Lua 脚本中访问的所有键必须在同一个槽中(除非使用 {hash tags} 确保这点)。否则脚本执行会报错。

    • Redis 7.0 新增了 FUNCTION 命令,提供更灵活的脚本管理,但多键限制仍然存在。

  3. 事务限制:

    • 事务中的命令涉及的键也必须都在同一个槽中。分布式事务需要应用层实现。

  4. 数据库: 集群模式下只使用 database 0 (SELECT 命令被禁用)。

  5. Pub/Sub: 可以工作,但消息会广播到所有节点。如果需要精确的频道订阅,客户端需要连接到所有节点订阅。

  6. 批量操作: KEYSSCANFLUSHALLFLUSHDB 等命令只作用于当前节点,不会跨节点执行。需要遍历所有节点或使用 redis-cli --cluster call

  7. 复杂性: 相比单机或主从,部署、配置、监控、故障排查更复杂。

  8. 网络分区: 在发生网络分区(脑裂)时,可能牺牲小分区的可用性以保证数据一致性(需要大多数主节点可达才能进行故障转移和写操作)。

六、 何时使用 Redis 集群?

  • 数据量超过单机内存容量。

  • 读写吞吐量超过单机 Redis 处理能力。

  • 需要高可用性,无法容忍单点故障。

  • 应用能够接受 Redis 集群的限制(尤其是多键操作、Lua 脚本限制)。

七、 替代方案 (何时不用集群?)

  • 数据量/流量不大: 单机 Redis 或主从复制+哨兵足够。

  • 需要多键跨节点操作/Lua脚本跨键: 考虑客户端分片(Twemproxy, Codis)或垂直拆分应用逻辑,或评估是否必须。

  • 需要强事务: Redis 集群不支持分布式事务。

  • 需要多数据库: 集群只支持 db0。

  • 运维复杂度敏感: 集群运维比单机/主从复杂。