Redis Cluster模式架构介绍

[toc]

1. 概述

Redis Cluster,采用了去中心化的多主多从架构,以提高数据的可用性和伸缩性。Redis集群的目的是实现数据的横向伸缩,把一块数据分片保存到多个机器,可以横向扩展数据库大小,扩展带宽,计算能力等。

以下是Redis Cluster的几个关键特点和优势的详细阐述:

  • 去中心化的多主多从架构

    • 每个从节点都复制主节点的数据,但不直接参与读写操作,主要用于数据备份和故障恢复。
    • 这种架构使得每个节点都可以在需要时承担主节点的角色,从而提高了整体系统的可靠性和容错能力。
  • 数据处理与性能

    • Redis Cluster在处理涉及多个键的操作时可能面临性能挑战,尤其是在数据量大和高并发的场景下。这是因为多key操作可能需要跨多个节点进行,从而增加了操作的复杂性。
    • 然而,对于单key操作,Redis Cluster能够保持其一贯的高性能,特别是在读操作上。
  • 动态扩容和收缩能力

    • Redis Cluster支持动态地添加或移除节点,这意味着可以根据实际需求调整集群的规模,无需停机或中断服务。
    • 这一特性对于处理不断变化的负载和数据量非常重要,使得Redis Cluster在大型应用中更具弹性。
  • 节点间的通信与故障转移

    • 在Redis Cluster中,主节点之间会进行定期的健康检查和状态同步,确保数据的一致性。
    • 当主节点出现故障时,其他主节点可以通过选举机制快速选出新的主节点,实现故障的自动转移,从而确保服务的连续性。

​ Redis Cluster 是Redis的集群实现,内置数据自动分片机制,集群内部将所有的key映射到16384个Slot中,集群中的每个Redis Instance负责其中的一部分的Slot的读写。集群客户端连接集群中任一Redis Instance即可发送命令,当Redis Instance收到自己不负责的Slot的请求时,会将负责请求Key所在Slot的Redis Instance地址返回给客户端,客户端收到后自动将原请求重新发往这个地址,对外部透明。一个Key到底属于哪个Slot由crc16(key) % 16384 决定。

2. 集群的模型

img

  1. 所有的节点通过服务通道直接相连,各个节点之间通过二进制协议优化传输的速度和带宽。

  2. 客户端与节点之间通过 ascii 协议进行通信

  3. 客户端与节点直连,不需要中间 Proxy 层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

  4. 尽管这些节点彼此相连,功能相同,但是仍然分为两种节点:master 和 slave。

  5. Hash slot。

    • key的空间被分到16384个hash slot里;
    • 计算key属于哪个slot,CRC16(key) & 16384。
  6. 集群内的每个redis实例监听两个tcp端口,6379(默认)用于服务客户端查询,16379(默认服务端口 + 10000)用于集群内部通信。

  7. 节点间状态同步:gossip协议,最终一致性。节点间通信使用轻量的二进制协议,减少带宽占用。

3. Redis-Cluster请求路由方式

  • redis-cluster借助客户端实现了混合形式的路由查询

    查询路由并非直接从一个redis节点到另外一个redis,而是借助客户端转发到正确的节点。根据客户端的实现方式,可以分为以下两种:

    • dummy client
    • smart client
  • 包括Jedis在内的许多redis client,已经实现了对Redis Cluster的支持。

查询路由的流程如下所示:
1521743-65d69d2217bef71f

Redis cluster采用这种架构的考虑:

  • 减少redis实现的复杂度
  • 降低客户端等待的时间。Smart client可以在客户端缓存 slot 与 redis节点的映射关系,当接收到 MOVED 响应时,会修改缓存中的映射关系。请求时会直接发送到正确的节点上,减少一次交互。

4. 实现细节

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

各个节点之间通过 PING-PONG 机制通信,下面是一段关于 PING-PONG 机制的会话”内容”。

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

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

Hash 槽(slot)

Redis 集群没有使用一致性hash, 而是引入了哈希槽的概念。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽(Slot),集群的每个节点负责一部分hash槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。

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

1
slot = crc16(key) mod NUMER_SLOTS

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

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

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

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

  1. 存入数据:例如,存储一个键值对,键名为“key”,其哈希值按照 CRC16(‘key’) % 16384 = 6782 计算。根据这个哈希槽号,数据将被存储在节点B上。

  2. 获取数据:无论连接哪个节点(A、B、C),获取键名为“key”的数据时,都会根据同样的哈希算法路由到节点B上提取数据。

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

  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 段的使用。

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

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

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

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

为什么Redis集群有16384个槽

CRC16算法产生的hash值有16bit,该算法可以产生2^16-=65536个值。换句话说,值是分布在0~65535之间。那作者在做mod运算的时候,为什么不mod65536,而选择mod16384?

redis集群内节点,每秒都在发ping消息。规律如下

(1)每秒会随机选取5个节点,找出最久没有通信的节点发送ping消息

(2)每100毫秒(1秒10次)都会扫描本地节点列表,如果发现节点最近一次接受pong消息的时间大于cluster-node-timeout/2 则立刻发送ping消息

因此,每秒单节点发出ping消息数量为数量=1+10*num(node.pong_received>cluster_node_timeout/2)

(1)如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。如上所述,在消息头中,最占空间的是myslots[CLUSTER_SLOTS/8]。当槽位为65536时,这块的大小是:65536÷8÷1024=8kb

因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大了,浪费带宽

(2)redis的集群主节点数量基本不可能超过1000个。如上所述,集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。

(3)槽位越小,节点少的情况下,压缩比高Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。

ps:文件压缩率指的是,文件压缩前后的大小比。

综上所述,作者决定取16384个槽,不多不少,刚刚好!

Redis Cluster 的 Gossip 通信机制

Redis Cluster 是在 3.0 版本引入集群功能。为了让让集群中的每个实例都知道其他所有实例的状态信息,Redis 集群规定各个实例之间按照 Gossip 协议来通信传递信息。

Gossip 协议又称 epidemic 协议(epidemic protocol),是基于流行病传播方式的节点或者进程之间信息交换的协议,在P2P网络和分布式系统中应用广泛,它的方法论也特别简单:

在一个处于有界网络的集群里,如果每个节点都随机与其他节点交换特定信息,经过足够长的时间后,集群各个节点对该份信息的认知终将收敛到一致。

这里的“特定信息”一般就是指集群状态、各节点的状态以及其他元数据等。Gossip协议是完全符合 BASE 原则,可以用在任何要求最终一致性的领域,比如分布式存储和注册中心。另外,它可以很方便地实现弹性集群,允许节点随时上下线,提供快捷的失败检测和动态负载均衡等。

此外,Gossip 协议的最大的好处是,即使集群节点的数量增加,每个节点的负载也不会增加很多,几乎是恒定的。这就允许 Redis Cluster 或者 Consul 集群管理的节点规模能横向扩展到数千个。

img

上图展示了主从架构的 Redis Cluster 示意图,其中实线表示节点间的主从复制关系,而虚线表示各个节点之间的 Gossip 通信。

Redis Cluster 中的每个节点都维护一份自己视角下的当前整个集群的状态,主要包括:

  1. 当前集群状态
  2. 集群中各节点所负责的 slots信息,及其migrate状态
  3. 集群中各节点的master-slave状态
  4. 集群中各节点的存活状态及怀疑Fail状态

也就是说上面的信息,就是集群中Node相互八卦传播流言蜚语的内容主题,而且比较全面,既有自己的更有别人的,这么一来大家都相互传,最终信息就全面而且一致了。

Redis Cluster 的节点之间会相互发送多种消息,较为重要的如下所示:

  • MEET:通过「cluster meet ip port」命令,已有集群的节点会向新的节点发送邀请,加入现有集群,然后新节点就会开始与其他节点进行通信;
  • PING:节点按照配置的时间间隔向集群中其他节点发送 ping 消息,消息中带有自己的状态,还有自己维护的集群元数据,和部分其他节点的元数据;
  • PONG: 节点用于回应 PING 和 MEET 的消息,结构和 PING 消息类似,也包含自己的状态和其他信息,也可以用于信息广播和更新;
  • FAIL: 节点 PING 不通某节点后,会向集群所有节点广播该节点挂掉的消息。其他节点收到消息后标记已下线。

5.容错

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

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

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

【为了保证高可用,redis-cluster集群引入了主从模式】,一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点ping一个主节点A时,如果半数以上的主节点与A通信超时,那么认为主节点A宕机了。如果主节点A和它的从节点A1都宕机了,那么该集群就无法再提供服务了。

6.优缺点

优点:

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

缺点:

  1. Client实现复杂,驱动要求实现Smart Client,缓存slots mapping信息并及时更新,提高了开发难度,客户端的不成熟影响业务的稳定性。目前仅Jedis Cluster相对成熟,异常处理部分还不完善,比如常见的“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产生的场景。