什么是 Redis 集群

Redis 集群是一个分布式(distributed)、容错(fault-tolerant)的 Redis 实现, 集群可以使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset)。

Redis 集群中不存在中心(central)节点或者代理(proxy)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability)。

Redis 集群提供了一种运行 Redis 的方式,其中数据在多个 Redis 节点间自动分区。Redis 集群还在分区期间提供一定程度的可用性,即在实际情况下能够在某些节点发生故障或无法通信时继续运行。但是,如果发生较大故障(例如,大多数主站不可用时),集群会停止运行。

集群的模型


  1. 所有的节点通过服务通道直接相连,各个节点之间通过二进制协议优化传输的速度和带宽。
  2. 客户端与节点之间通过 ascii 协议进行通信
  3. 客户端与节点直连,不需要中间 Proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。
  4. 尽管这些节点彼此相连,功能相同,但是仍然分为两种节点:master 和 slave。

各个节点之间都传递了什么信息



redis_cluster2.png



通过上面的图我们可以知道各个节点之间通过 PING-PONG 机制通信,下面是一段关于 PING-PONG 机制的会话”内容”。

节点M:PING,嘿,朋友你好吗?我是 XYZ 哈希槽的 master ,配置信息是 FF89X1JK。节点N:PONG,我很好朋友,我也是 XYZ 哈希槽的 master ,配置信息是 FF89X1JK。节点M:我这里有一些关于我最近收到的其他节点的信息 ,A 节点回复了我的 PING 消息,我认为 A 节点是正常的。B 没有回应我的消息,我猜它现在可能出问题了,但是我需要一些 ACK(Acknowledgement) 消息来确认。节点N:我也想给你分享一些关于其它节点的信息,C 和 D 节点在指定的时间内回应了我, 我认为它们都是正常的,但是 B 也没有回应我,我觉得它现在可能已经挂掉了。

每个节点会向集群中的其他节点发送节点状态信息,如果某个节点挂掉停止了服务,那么会执行投票容错机制,关于这个机制,会在下面讲到。

Hash 槽(slot)

Redis 集群不使用一致的散列,而是一种不同的分片形式,其中每个键在概念上都是我们称之为散列槽的一部分,目的是使数据均匀的存储在诸多节点中。这点类似于 HashMap 中的桶(bucket)。



redis_cluster3.png



Redis 集群中有 16384 个散列槽,为了计算给定密钥的散列槽,Redis 对 key 采用 CRC16 算法,以下是负责将键映射到槽的算法:

slot = crc16(key) mod NUMER_SLOTS

例如,你可能有 3 个节点,其中一个集群:

节点 A 包含从 0 到 5500 的散列槽。

节点 B 包含从 5501 到 11000 的散列槽。

节点 C 包含 从 11001 到 16383 的散列槽。

Hash 槽可以轻松地添加和删除集群中的节点。例如,如果我想添加一个新节点 D,我需要将节点 A,B,C 中的一些散列槽移动到 D。同样,如果我想从节点 A 中删除节点 A,可以只移动由 A 服务的散列槽到 B 和 C。当节点 A 为空时,可以将它从群集中彻底删除。



redis_cluster4.png



  1. 对象保存到 Redis 之前先经过 CRC16 哈希到一个指定的 Node 上,例如 Object4 最终 Hash 到了 Node1 上。
  2. 每个 Node 被平均分配了一个 Slot 段,对应着 0-16384,Slot 不能重复也不能缺失,否则会导致对象重复存储或无法存储。
  3. Node 之间也互相监听,一旦有 Node 退出或者加入,会按照 Slot 为单位做数据的迁移。例如 Node1 如果掉线了,0-5640 这些 Slot 将会平均分摊到 Node2 和 Node3 上,由于 Node2 和 Node3 本身维护的 Slot 还会在自己身上不会被重新分配,所以迁移过程中不会影响到 5641-16384Slot 段的使用。

redis_cluster4.png



想扩展并发读就添加 Slaver,想扩展并发写就添加 Master,想扩容也就是添加 Master,任何一个 Slaver 或者几个 Master 挂了都不会是灾难性的故障。

简单总结下哈希 Slot 的优缺点:

缺点:每个 Node 承担着互相监听、高并发数据写入、高并发数据读出,工作任务繁重

优点:将 Redis 的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。

容错



redis_cluster6.png



  • 集群中的节点不断的 PING 其他的节点,当一个节点向另一个节点发送 PING 命令, 但是目标节点未能在给定的时限内回复, 那么发送命令的节点会将目标节点标记为 PFAIL(possible failure,可能已失效)。
  • 当节点接收到其他节点发来的信息时, 它会记下那些被其他节点标记为失效的节点。 这被称为失效报告(failure report)。
  • 如果节点已经将某个节点标记为 PFAIL , 并且根据节点所收到的失效报告显式, 集群中的大部分其他主节点也认为那个节点进入了失效状态, 那么节点会将那个失效节点的状态标记为 FAIL 。
  • 一旦某个节点被标记为 FAIL , 关于这个节点已失效的信息就会被广播到整个集群, 所有接收到这条信息的节点都会将失效节点标记为 FAIL 。

简单来说, 一个节点要将另一个节点标记为失效, 必须先询问其他节点的意见, 并且得到大部分主节点的同意才行。

  • 如果被标记为 FAIL 的是从节点, 那么当这个节点重新上线时, FAIL 标记就会被移除。 一个从节点是否处于 FAIL 状态, 决定了这个从节点在有需要时能否被提升为主节点。
  • 如果一个主节点被打上 FAIL 标记之后, 经过了节点超时时限的四倍时间, 再加上十秒钟之后, 针对这个主节点的槽的故障转移操作仍未完成, 并且这个主节点已经重新上线的话, 那么移除对这个节点的 FAIL 标记。在不符合上面的条件后,一旦某个主节点进入 FAIL 状态, 如果这个主节点有一个或多个从节点存在, 那么其中一个从节点会被升级为新的主节点, 而其他从节点则会开始对这个新的主节点进行复制。

优缺点

优点:

  1. 无中心架构;
  2. 数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布;
  3. 可扩展性:可线性扩展到 1000 多个节点,节点可动态添加或删除;
  4. 高可用性:部分节点不可用时,集群仍可用。通过增加Slave做standby数据副本,能够实现故障自动failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升;
  5. 降低运维成本,提高系统的扩展性和可用性。

缺点:

  1. Client实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。目前仅JedisCluster相对成熟,异常处理部分还不完善,比如常见的“max redirect exception”。
  2. 节点会因为某些原因发生阻塞(阻塞时间大于clutser-node-timeout),被判断下线,这种failover是没有必要的。
  3. 数据通过异步复制,不保证数据的强一致性。
  4. 多个业务使用同一套集群时,无法根据统计区分冷热数据,资源隔离性较差,容易出现相互影响的情况。
  5. Slave在集群中充当“冷备”,不能缓解读压力,当然可以通过SDK的合理设计来提高Slave资源的利用率。
  6. Key批量操作限制,如使用mset、mget目前只支持具有相同slot值的Key执行批量操作。对于映射为不同slot值的Key由于Keys不支持跨slot查询,所以执行mset、mget、sunion等操作支持不友好。
  7. Key事务操作支持有限,只支持多key在同一节点上的事务操作,当多个Key分布于不同的节点上时无法使用事务功能。
  8. Key作为数据分区的最小粒度,不能将一个很大的键值对象如hash、list等映射到不同的节点。
  9. 不支持多数据库空间,单机下的redis可以支持到 16 个数据库,集群模式下只能使用 1 个数据库空间,即 db 0。
  10. 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构。
  11. 避免产生hot-key,导致主库节点成为系统的短板。
  12. 避免产生big-key,导致网卡撑爆、慢查询等。
  13. 重试时间应该大于cluster-node-time时间。
  14. Redis Cluster不建议使用pipeline和multi-keys操作,减少max redirect产生的场景。