集中式缓存Redis(二)

2024-08-16

49
0

Memcached 原理剖析


机制

Memcached 以下简称 Mc

Mc 并不是将所有数据放在一起来进行管理的,而是将内存划分为一系列相同大小的 slab 空间后,每个 slab 只管理一定范围内的数据存储。也就是说 Mc 内部采用 slab 机制来管理内存分配。 Mc 内的内存分配以 slab 为单位,默认情况下一个 slab 是 1MB,可以通过 -I 参数在启动时指定其他数值。

slab 空间内部,会被进一步划分为一系列固定大小的 chunk。每个 chunk 内部存储一个 Item,利用 Item 结构存储数据。因为 chunk 大小固定,而 key/value 数据的大小随机。所以,Item 存储完 key/value 数据后,一般还会有多余的空间,这个多余的空间就被浪费了。为了提升内存的使用效率,chunk size 就不能太大,而要尽量选择与 key/value size 接近的 ,从而减少 chunk 内浪费的空间。

Mc 在分配内存时,先将内存按固定大小划分成 slab,然后再将不同 slab 分拆出固定 size 的 chunk。虽然 slab 内的 chunk 大小相同,但不同 slab 的 chunk size 并不同,Mc 会按照一个固定比例,使划分的 chunk size 逐步增大,从而满足不同大小 key/value 存储的需要。

一组具有相同 chunk size 的所有 slab,就组成一个 slabclass。不同 slabclass 的 chunk size 按递增因子一次增加。Mc 就通过 slabclass 来管理一组 slab 内的存储空间的。每个 slabclass 内部有一个 freelist ,包含这组 slab 里所有空闲的 chunk,当需要存储数据时,从这个 freelist 里面快速分配一个 chunk 做存储空间。当 Item 数据淘汰剔除时,这个 Item 所在的 chunk 又被回收至这个 freelist。

Mc 在通过 slab 机制管理内存分配时,实际 key/value 是存在 Item 结构中,所以对 key/value 的存储空间分配就转换为对 Item 的分配。而 Item 空间的分配有 2 种方式,如果 Mc 有空闲空间,则从 slabclass 的 freelist 分配;如果没有空闲空间,则从对应 slabclass id 对应的 LRU 中剔除一个 Item,来复用这个 Item 的空间。

在查找或变更一个 key 时,首先要定位这个 key 所在的存储位置。Mc 是通过哈希表 Hashtable 来定位 key 的。Hashtable 可以看作是一个内存空间连续的大数组,而这个大数据的每一个槽位对应一个 key 的 Hash 值,这个槽位也称 bucket。由于不同 key 的 Hash 值可能相同,所以 Mc 在 Hashtable 的每个捅内部再用一个单向链表,来解决 Hash 冲突的问题。

Mc 内部是通过 LRU 来管理存储 Item 数据的,当内存不足时,会从 LRU 队尾中剔除一个过期或最不活跃的 key,供新的 Item 使用。

系统架构

Mc 的系统架构主要包括网络处理模块、多线程处理模块、哈希表、LRU、slab 内存分配模块 5 部分。Mc 基于 Libevent 实现了网络处理模块,通过多线程并发处理用户请求;基于哈希表对 key 进行快速定位,基于 LRU 来管理冷数据的剔除淘汰,基于 slab 机制进行快速的内存分配及存储。

网络处理模块

Mc 基于 Libevent 开发实现了多线程网络模型。Mc 的多线程网络模型分为主线程、工作线程。这些线程通过多路复用 IO 来进行网络 IO 接入以及读写处理。在 Linux 下,通常使用 epoll。通过多路复用 IO,特别是 epoll 的使用,Mc 线程无须遍历整个被侦听的描述符集,只要在被通知后遍历 Ready 队列的描述符集合就 OK 了。这些描述符是在各项准备工作完成之后,才被内核 IO 事件异步通知。也就是说,只在连接做好准备后,系统才会进行事件通知,Mc 才会进行 I/O 操作。这样就不会发生阻塞,使 Mc 在支持高并发的同时,拥有非常高的 IO 吞吐效率。

多线程处理模块

除了用于 IO 的主线程和工作线程外,还用于多个辅助线程,如 Item 爬虫线程、LRU 维护线程、哈希表维护线程等,通过多线程并发工作,Mc 可以充分利用机器的多个核心,实现很好的网络 IO 性能和数据处理能力。

哈希表

Mc 通过哈希表即 Hashtable 来快速定位 key。数据存储时,数据 Item 结构在存入 slab 中的 chunk 后,也会被存放到 Hashtable 中。同时,Mc 的哈希表会在每个桶,通过 Item 记录一个单向链表,以此来解决不同 key 在哈希表中的 Hash 冲突问题。 当需要查找给定 key 的 Item 时,首先计算 key 的 Hash 值,然后对哈希表中与 Hash 值对应的 bucket 中进行搜索,通过轮询 bucket 里的单向链表,找到该 key 对应的 Item 指针,这样就找到了 key 对应的存储 Item

正常情况下,Mc 对哈希表的插入、查找操作都是在主表中进行的。当表中 Item 数量大于哈希表 bucket 节点数的 1.5 倍时,就对哈希表进行扩容。如下图所示,扩容时,Mc 内部使用两张 Hashtable,一个主哈希表 primary_hashtable,一个是旧哈希表 old_hashtable。当扩容开始时,原来的主哈希表就成为旧哈希表,而新分配一个 2 倍容量的哈希表作为新的主表。扩容过程中,维护线程会将旧表的 Item 指针,逐步复制插入到新主哈希表。迁移过程中,根据迁移位置,用户请求会同时查旧表和新的主表,当数据全部迁移完成,所有的操作就重新回到主表中进行。

LRU 机制

Mc 主要通过 LRU 机制,来进行冷数据淘汰的。自 1.4.24 版本之后,Mc 不断优化 LRU 算法,当前 Mc 版本已默认启用分段 LRU 了。在启用分段 LRU 之前,每个 slabclass id 只对应一个 COLD LRU,在内存不足时,会直接从 COLD LRU 剔除数据。而在启用分段 LRU 之后,每个 slabclass id 就有 TEMP、HOT、WARM 和 COLD 四个 LRU。

如下图所示,TEMP LRU 中 Item 剩余过期时间通常很短,默认是 61 秒以内。该列队中的 Item 永远不会发生在队列内搬运,也不会迁移到其他队列。在插入新 key/value 时,如果 key 的剩余过期时间小于 61 秒,则直接进入 TEMP LRU。后面,在必要时直接进行过期即可。这样避免了锁竞争,性能也更高。

对于 HOT LRU,内部不搬运,当队列满时,如果队尾 Item 是 Active 状态,即被访问过,那么会迁移到 WARM 队列,否则迁移到 COLD 队列。

对于 WARM LRU,如果队列的 Item 被再次访问,就搬到队首,否则迁移到 COLD 队列。

对于 COLD LRU,存放的是最不活跃的 Item,一旦内存满了,队尾的 Item 会被剔除。如果 COLD LRU 里的 Item 被再次访问,会迁移到 WARM LRU。

slab 分配机制

Mc 通过 slab 机制来分配管理内存的,如下图所示。可以说,slab 分配机制的使用,是 Mc 分配及存储高性能的关键所在。在 Mc 启动时,会创建 64 个 slabclass,但索引为 0 的 slabclass 做 slab 重新分配之用,基本不参与其他 slabclass 的日常分配活动。每个 slabclass 会根据需要不断分配默认大小为 1MB 的 slab。

每个 slab 又被分为相同大小的 chunk。chunk 就是 Mc 存储数据的基本存储单位。slabclass 1 的 chunk size 最小,默认最小 chunk 的大小是 102 字节,后续的 slabclass 会按照增长因子逐步增大 chunk size,具体数值会进一步对 8 取整。Mc 默认的增长因子是 1.25,启动时可以通过 -f 将增长因子设为其他值。比如采用默认值,slabclass 1 的 chunk size 是 102,slabclass 2 的 chunk size 是 102×1.25,再对 8 取整后是 128。

Mc slab 中的 chunk 中通过 Item 结构存 key/value 键值对,Item 结构体的头部存链表的指针、flag、过期时间等,然后存 key 及 value。一般情况下,Item 并不会将 chunk 填满,但由于每个 key/value 在存储时,都会根据 kev/value size,选择最接近的 slabclass,所以 chunk 浪费的字节非常有限,基本可以忽略。

每次新分配一个 slab 后,会将 slab 空间等分成相同 size 的 chunk,这些 chunk 会被加入到 slabclass 的 freelist 中,在需要时进行分配。分配出去的 chunk 存储 Item 数据,在过期被剔除后,会再次进入 freelist,供后续使用。

3.2.12 Redis哨兵高可用机制-成品

Redis哨兵机制:实现高可用的核心

Redis哨兵机制基于主从复制,通过自动监控和故障转移实现高可用。哨兵充当服务监视者,检测主服务器状态并自动进行主从切换。哨兵集群提高了系统的可靠性,当哨兵出现问题时,可以通过多哨兵之间的通信避免误判并确保数据一致性。这一机制简化了客户端的配置,使其无需关心后端的具体实现细节,从而提高应用的鲁棒性和可扩展性。

Redis 哨兵(Sentinel)机制是 Redis 实现高可用性的一个重要组件。它主要负责监控、提醒和自动故障恢复。以下是哨兵机制的一些关键点:

1. 监控(Monitoring)

哨兵会不断地检查你的 Master 和 Slave 服务器是否运作正常。每个哨兵进程会以固定的时间间隔发送 PING 命令给所有被监控的服务器(包括 Master 和 Slave),以及其他的哨兵实例。

2. 提醒(Notification)

当被监控的某个 Redis 服务器出现问题时,哨兵可以通过 API 向管理员或者其他的应用程序发送通知。

3. 自动故障转移(Automatic Failover)

如果 Master 服务器挂掉了,哨兵可以触发一个自动故障转移操作,将失效 Master 的其中一个 Slave 升级为新的 Master,并且将其他 Slave 重新指向新的 Master。同时,客户端会被自动通知这个新的 Master 地址。

哨兵集群

  • 冗余:通常部署多个哨兵实例来增加系统的可靠性,即使部分哨兵实例失败,剩余的哨兵仍然能够执行监控和故障转移任务。

  • 选举机制:哨兵之间通过协商机制确定哪个哨兵来进行故障转移操作。这通常涉及到一个投票过程,确保只有大多数哨兵同意时才会进行故障转移,以防止误操作。

  • 配置更新:一旦新的 Master 被选中,哨兵会更新其内部的配置信息,并向客户端广播新的 Master 地址。

客户端透明性

对于客户端来说,整个故障转移过程通常是透明的。客户端只需要连接到一个固定的哨兵地址,哨兵会返回最新的 Master 信息。这种方式大大简化了客户端的实现,提高了系统的健壮性和可维护性。

总结

Redis 哨兵机制通过监控、提醒和自动故障转移实现了高可用性。通过使用哨兵集群,可以进一步增强系统的稳定性和可靠性。这种设计模式使得 Redis 在分布式系统中成为一个非常可靠的选择,尤其是在需要高度可用性的场景下。

前面我们说过,redis采用了读写分离的方式实现高可靠。后面我们说了,为了防止主节点压力过大,优化成了主-从-从模式

思考一个问题,主节点此时挂了怎么办


这里主从模式下涉及到的几个问题:

  1. 主库真的挂了吗?

  2. 我们应当选择哪个从库作为主库?

  3. 怎样让其他从库知道新的主库信息呢?

  4. 中断的数据如何恢复?

哨兵机制就完美的解决了以上问题。

什么是哨兵机制?

Redis引入哨兵(Sentinel)机制的主要目的是为了增强其高可用性和自动故障恢复能力。在分布式系统中,特别是用作数据存储的数据库系统中,保障高可用性是至关重要的,以确保系统在面对节点故障等情况时能够继续提供服务。

以下是引入Redis哨兵机制的原因:

  1. 故障检测和自动故障切换: 哨兵允许您配置多个Redis节点,并监视它们的运行状况。如果主节点(Master)出现故障,哨兵可以自动检测到并执行故障切换,将一个可用的从节点(Slave)晋升为新的主节点,从而保证服务的可用性。

  2. 自动配置更新: 当Redis节点的拓扑结构发生变化(比如添加或移除节点)时,哨兵能够自动地通知客户端和其他Redis节点进行配置更新,从而确保整个集群的正确配置。

  3. 监控和报警: 哨兵不仅监视节点的健康状态,还可以提供有关节点运行状况的信息,例如主从复制是否正常、延迟情况等。这可以帮助管理员及时发现问题并采取措施。

  4. 无需人工干预的恢复: 哨兵允许自动故障切换,这意味着当主节点出现问题时,系统可以自动将一个从节点提升为新的主节点,而无需管理员手动介入,从而缩短恢复时间。

Redis引入哨兵机制使得在分布式环境中更容易实现高可用性和故障恢复,而无需太多手动操作。哨兵机制可以确保Redis集群在节点故障时继续提供稳定的服务,对于那些对于高可用性要求较高的应用场景非常有用。

哨兵机制的基本流程

哨兵其实就是一个运行在特殊模式下的Redis进程,其随着主从实例同时运行。

那么哨兵负责哪些活呢?主要是以下三点:

  1. 监控

  2. 选主(选择主库)

  3. 通知

监控

Redis哨兵的监控流程涉及多个步骤,用于实时监控Redis集群中各个节点的状态并采取必要的措施来确保集群的可用性和稳定性。

  1. 节点发现和配置: 哨兵通过配置文件指定要监控的主节点和从节点。启动哨兵后,它会连接到指定的节点,并获取有关其他节点的信息,形成一个初始的监控拓扑。

  2. 心跳检测: 哨兵会定期向监控的节点发送PING命令来检测节点是否存活。这些节点可以是主节点、从节点或其他哨兵节点。如果哨兵在一定时间内没有收到响应,它会认为节点不可用。

  3. 节点状态变更: 当哨兵连续多次无法连接到一个节点时,它会将该节点标记为主观下线。当多个哨兵都将节点标记为主观下线时,这个节点会被认为是客观下线。

  4. 故障判断和选举: 当主节点被标记为客观下线时,哨兵会执行故障判断。它会从剩余的健康主节点中选举一个作为新的主节点,并将该信息广播给其他哨兵和客户端。故障判断的逻辑考虑了多个因素,包括优先级、最近一次复制偏移量等。

  5. 自动故障切换: 如果主节点被标记为客观下线,哨兵会通知从节点晋升为新的主节点。同时,哨兵会更新其他从节点的配置,使其复制新的主节点。这确保了即使主节点发生故障,集群仍然可以继续提供服务。

  6. 监控从节点: 哨兵还会监控从节点的状态,包括从节点是否与主节点保持同步,以及从节点的复制延迟情况。如果从节点无法同步或者复制延迟过高,哨兵会将其标记为不健康。

  7. 节点恢复: 如果一个节点从客观下线状态恢复,哨兵会将其标记为健康,并将其重新纳入集群中。从节点恢复后,它会重新同步主节点的数据。

  8. 配置更新: 如果集群的拓扑发生变化,例如添加或移除节点,哨兵会自动更新配置,以便客户端能够正确连接到集群。

  9. 事件通知: 哨兵通过发布订阅机制向订阅者(通常是客户端)发送有关集群状态变化的消息。这使得应用程序能够根据实时的集群状态做出相应的决策。

  10. 持续监控: 哨兵会持续地监控集群中的节点,定期执行心跳检测、状态更新和故障判断,以确保集群的稳定运行。

主观下线与客观下线

在Redis的哨兵监控机制中,有两个关键概念:主观下线(Subjective Down)和客观下线(Objective Down)。这些概念帮助哨兵判断节点的可用性和故障状态。

  1. 主观下线(Subjective Down): 主观下线是指单个哨兵节点认为一个特定的Redis节点(主节点、从节点或其他哨兵)不可用。主观下线是一种主观的判断,是基于单个哨兵节点的观察结果得出的。当一个哨兵无法连接到某个Redis节点,它会将该节点标记为主观下线。多个哨兵节点可能会对同一个节点发出主观下线标记。

  2. 客观下线(Objective Down): 客观下线是指在整个哨兵集合中达成一致,认为某个特定的Redis节点不可用。客观下线是一种更客观的判断,需要多个哨兵节点共同达成一致。当多个哨兵节点都主观下线同一个Redis节点时,这个节点会被认为是客观下线。

举例说明:

  • 假设有三个哨兵节点:Sentinel A、Sentinel B 和 Sentinel C,以及一个主节点 Master 和一个从节点 Slave。如果 Sentinel A 无法连接到 Master 节点,它会将 Master 标记为主观下线。同样地,如果 Sentinel B 也无法连接到 Master 节点,它也会将 Master 标记为主观下线。但这还不足以让 Master 被认为是客观下线。

  • 当 Sentinel A 和 Sentinel B 都主观下线了 Master 节点,并且他们相互通信时发现了这个情况,他们就会在达成一致意见后将 Master 节点标记为客观下线。这时,整个哨兵集合达成一致,认为 Master 节点已下线。

客观下线是一个更严格的判断,需要多个哨兵节点一致认为某个节点不可用,才会触发后续的故障判断和自动故障切换等动作。这种机制确保了在一个哨兵节点认为某节点下线时,不会立即触发故障切换,以避免误判造成不必要的切换。只有多个哨兵节点一致认为节点下线,才会触发后续的故障处理流程。

如何选定新主库

在Redis Sentinel模式中,当主节点(Master)发生故障导致下线后,哨兵会通过选举过程选择一个新的主节点(Master)来取代原来的主节点。选定新主库的过程如下:

  1. 主观下线和客观下线判断: 当哨兵节点主观下线(单个哨兵认为不可用)一个主节点时,如果多数哨兵都主观下线了同一个主节点,那么这个主节点会被标记为客观下线(多数派共识)。

  2. 选举新主节点: 当一个主节点被标记为客观下线后,哨兵节点会开始选举一个新的主节点。选举过程如下:

    • 哨兵会在所有没有下线的从节点(Slaves)中选择一个作为新主节点。哨兵会选择一个延迟最小、复制偏移量最大的从节点作为新主节点。这确保了新主节点是最接近原主节点的从节点。

    • 如果没有合适的从节点,哨兵会选择一个具备最高优先级的从节点,将其升级为主节点。如果优先级相同,那么哨兵会选择一个复制偏移量最大的从节点。

  3. 故障转移和切换: 一旦新主节点被选定,哨兵会发起故障转移操作。旧主节点会变成新主节点的一个从节点。其他从节点会重新配置,指向新的主节点。这个过程会保证尽量不丢失数据,并且保证整个集群的高可用性。

选定新主库的过程是一个由哨兵节点协同工作的流程,确保了在主节点故障的情况下,尽可能地选择一个合适的从节点作为新的主节点,实现集群的高可用性和数据完整性。

如何配置哨兵

  1. 哨兵配置文件: 在Redis 6.x版本中,哨兵的配置文件名称默认为redis-sentinel.conf

  2. 配置变化: Redis 6.x版本引入了一些新的哨兵配置选项,以适应新的功能和改进。以下是一些常见的配置选项:

    sentinel monitor mymaster 127.0.0.1 6379 2   # 监控名为 "mymaster" 的主节点,2表示至少需要2个哨兵同意主观下线才会执行故障转移
    sentinel down-after-milliseconds mymaster 5000   # 主观下线判定为5秒无响应
    sentinel parallel-syncs mymaster 1   # 执行故障转移时同时同步的从节点数量
    sentinel failover-timeout mymaster 10000   # 故障转移超时时间为10秒
    sentinel auth-pass mymaster mypassword   # 主节点的访问密码
  3. 启动哨兵节点: 在Redis 6.x版本中,启动哨兵节点的命令为:

  1. redis-server /path/to/redis-sentinel.conf --sentinel
  2. 查看哨兵状态: 使用以下命令查看Redis 6.x版本哨兵节点的状态:

    redis-cli -p 26379
    sentinel master mymaster   # 查看主节点的信息
    sentinel slaves mymaster   # 查看从节点的信息
    sentinel sentinels mymaster   # 查看其他哨兵节点的信息

哨兵是如何互相发现的?

我们查看配置可以看到,我们并没有配置从节点的哨兵,我们只配置了主节点地址。

那么哨兵之间是如何互相发现通信的呢?

在Redis Sentinel(哨兵)集群中,哨兵节点之间通过发布订阅机制来互相发现和通信。这种方式使得哨兵节点能够监控主节点和从节点的状态,并进行故障检测和故障转移。

以下是哨兵集群如何通过发布订阅机制互相发现的工作流程:

  1. 初始连接: 在启动时,每个哨兵节点会尝试连接到指定的主节点。这些哨兵节点通过配置文件中的sentinel monitor命令指定要监控的主节点信息。

  2. Sentinel命令发布: 当一个哨兵节点成功连接到主节点后,它会开始定期向主节点发送PING命令,以确保主节点处于活跃状态。如果哨兵节点检测到主节点不可用,它会将一个+switch-master命令发布到频道中,通知其他哨兵节点。

  3. 发布订阅机制: Redis的发布订阅机制允许一个节点(发布者)向一个或多个节点(订阅者)广播消息。在哨兵集群中,每个哨兵节点都订阅了一个名为__sentinel__:hello的频道,用于接收其他哨兵节点发送的信息。

  1. 发现其他哨兵节点: 当一个哨兵节点成功连接到主节点后,它会向__sentinel__:hello频道发布一个"Hello"消息,其中包含它自己的信息(如IP地址和端口号)。其他哨兵节点通过订阅这个频道,可以获取所有其他哨兵节点的信息。

  2. 收集哨兵信息: 每个哨兵节点通过订阅__sentinel__:hello频道,收集到其他哨兵节点的信息。这使得每个哨兵节点都知道了集群中其他哨兵节点的存在。

  3. 故障检测和转移: 当一个哨兵节点检测到主节点不可用时,它会通过发布+switch-master命令来通知其他哨兵节点。这个命令包含了新的主节点信息,以及在执行故障转移时需要的其他信息。其他哨兵节点收到这个命令后,会进行判断并可能发起故障转移。

通过以上机制,哨兵节点可以相互发现和通信,共同监控主节点和从节点的状态,并在主节点下线时协同执行故障转移操作。这种发布订阅机制确保了哨兵集群中节点之间的实时信息传递和协作。

由哪个哨兵执行主从切换?

客观下线具体判断流程

  1. 故障检测: 哨兵节点定期向集群中的所有主节点和从节点发送PING命令来检测节点的可用性。如果一个哨兵节点连续一定次数没有收到节点的回复,就会将该节点标记为可能进入客观下线状态。

  2. Quorum判断: 在判断一个节点是否客观下线时,需要考虑Quorum的概念。Quorum是指一个最小的投票数,当达到或超过这个投票数时,哨兵认为节点可能进入客观下线状态。Quorum的值通常设置为哨兵节点数量的一半加一。

  3. 投票过程: 当哨兵节点开始怀疑某个节点可能客观下线时,它会向其他哨兵节点发送一个SENTINEL is-master-down-by-addr命令,询问其他哨兵节点是否也认为该节点客观下线。其他哨兵节点会对此做出回应,根据回应的数量来判断是否达到Quorum。

  4. 达到Quorum: 如果收到的回应数量达到或超过Quorum,那么哨兵节点就会认为该节点进入客观下线状态。这表示集群中有足够多的哨兵都认为该节点可能下线,进而触发后续的主从切换流程。

  5. 执行后续操作: 一旦一个节点被认为客观下线,哨兵节点将开始执行故障转移操作,选择新的主节点并开始同步数据。这将最终导致一个新的主节点被选出,从而实现高可用性。

选举Leader流程

Redis Sentinel(哨兵)是用于监控和管理Redis主从复制以及自动故障切换的工具。当主节点失效时,哨兵会协调选择一个从节点作为新的主节点,这涉及到选举Leader的过程。详细流程如下:

  1. 监控主节点: 哨兵持续监控Redis主节点的状态,包括主节点是否在线,主从复制是否正常,以及哨兵和其他节点的通信情况。

  2. 检测主节点失效: 当哨兵检测到主节点失效(例如,无法响应PING命令),它会将主节点标记为“主观下线”。

  3. 广播主观下线状态: 一旦主观下线状态被确认,哨兵会广播该信息给其他哨兵和节点,告知主节点已经“主观下线”。

  4. 投票: 当其他哨兵收到关于主观下线状态的广播时,它们会进行投票来决定是否需要进行领导者选举。

  5. 选举Leader: 如果多个哨兵都认为主节点失效,它们将进入领导者选举过程。选举过程使用了Raft算法的变体。

  6. 提议投票: 在选举过程中,哨兵会提议自己作为领导者,然后请求其他哨兵投票支持。

  7. 投票表决: 哨兵在收到提议后会表决是否支持该提议。通常,哨兵会投票给具有最高配置版本号的提议者。

  8. Quorum判断: 在选举过程中,哨兵需要收集足够数量的投票,达到Quorum(大多数)的支持才能选举成功。

  9. 选出新领导者: 如果某个哨兵获得足够多的投票,超过了Quorum,那么它将被选为新的领导者。

  10. 通知其他节点: 新选出的Leader会向其他哨兵和节点广播其成为领导者的消息,确保集群中的所有节点都知道领导者的变更。

  11. 故障切换: 一旦新的Leader选举完成,哨兵会协调进行故障切换,将一个从节点提升为新的主节点,使整个集群继续正常运行。

  12. 恢复正常状态: 一旦故障切换完成,新的主节点将开始处理客户端请求,集群会恢复到正常运行状态。

需要注意的是,Redis Sentinel的选举Leader过程受到Paxos算法和Raft算法等分布式一致性算法的影响,以保证在主节点失效时能够选择合适的节点作为新的主节点,从而保持数据的一致性和高可用性。

TIP

  1. 如果哨兵集群只有 2 个实例,此时,一个哨兵要想成为 Leader,必须获得 2 票,而不是 1 票。所以,如果有个哨兵挂掉了,那么,此时的集群是无法进行主从库切换的。因此,通常我们至少会配置 3 个哨兵实例。

  2. 要保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值 down-after-milliseconds。

哨兵间通信与故障转移机制 详解Raft分布式一致性算法及其选举过程

详解分布式系统中的选举机制与主从切换 哨兵模式在Redis中的应用与优化

好的,让我们分别详细探讨这几个主题:

1. 哨兵间的通信与故障转移机制

哨兵间的通信主要用于互相发现、状态同步以及在故障发生时协调故障转移操作。哨兵使用 Redis 命令集来进行通信,主要包括以下几种类型:

  • 哨兵间的发现:哨兵启动后会尝试通过配置文件中指定的其他哨兵实例地址建立连接,并使用 SENTINEL 命令获取其他哨兵的信息。

  • 状态同步:哨兵会定期(默认每秒一次)发送 PING 命令来维持与其他哨兵的连接,并使用 SENTINEL 命令来同步有关主服务器和其他哨兵的信息。

  • 故障转移协调:当哨兵认为主服务器不可用时,它会通过与其他哨兵的交流来决定是否进行故障转移操作。这通常涉及投票过程。

2. Raft 分布式一致性算法及其选举过程

Raft 是一种分布式一致性算法,它被设计成易于理解和实现,同时保证数据的一致性和可用性。Raft 主要包含三种角色:Leader、Candidate 和 Follower。下面是一个简化的 Raft 选举过程:

  1. 初始化状态:所有节点都处于 Follower 状态。

  2. 超时事件:当 Follower 没有在一定时间内接收到 Leader 或 Candidate 的消息时,它会成为 Candidate 并开始新的选举轮次。

  3. 请求投票:Candidate 向其他节点发送请求投票的消息,其中包括自己的任期号和最后日志条目的索引和任期号。

  4. 投票:如果接收者是最新的或者没有更优的投票,则接收者会投票给 Candidate,并发送一个包含当前任期号的投票确认消息。

  5. 多数票胜出:如果 Candidate 收到了多数票(包括自己的一票),那么它就变成了 Leader。

  6. 发送心跳消息:一旦成为 Leader,它会定期向所有节点发送心跳消息,以维持其领导地位。

  7. 日志复制:Leader 接收客户端写入请求,并将请求追加到自己的日志中。然后将这些日志条目发送给其他节点,以确保所有节点的日志是一致的。

3. 分布式系统中的选举机制与主从切换

在分布式系统中,选举机制用于选择一个节点作为协调者或领导者。选举机制的目标是确保在任何时候只有一个领导者,并且在领导者失败时能够快速地选举出一个新的领导者。主从切换是指当主节点失败时,将其中一个从节点提升为主节点的过程。

选举机制的关键要素:

  • 容错性:选举过程必须能够容忍一定程度的故障,例如节点的失败或网络分区。

  • 一致性:必须确保只有一个领导者被选举出来。

  • 及时性:选举过程应该是高效的,以便尽快恢复正常服务。

主从切换的关键要素:

  • 数据一致性:在切换过程中必须确保数据的一致性,即从节点的数据必须与主节点的数据相同。

  • 平滑过渡:切换过程应该尽可能平滑,减少对用户的影响。

  • 自动恢复:系统应该能够自动检测故障并自动执行主从切换。

4. 哨兵模式在 Redis 中的应用与优化

Redis 哨兵模式利用哨兵来监控主服务器和从服务器的状态,并在主服务器出现故障时自动进行故障转移。哨兵模式的关键在于哨兵之间的通信和协调。

应用要点:

  • 哨兵配置:正确配置哨兵实例,包括监控的主服务器信息和哨兵之间的相互发现配置。

  • 哨兵选举:哨兵之间通过协商机制确定哪个哨兵负责故障转移操作。这通常涉及投票过程,以确保只有大多数哨兵同意时才会进行故障转移。

  • 自动故障转移:一旦哨兵检测到主服务器不可用,它会自动选择一个从服务器升级为主服务器,并通知其他从服务器和客户端连接新的主服务器。

优化建议:

  • 哨兵数量:配置足够数量的哨兵来提高系统的可靠性。

  • 网络优化:减少哨兵之间的网络延迟,以提高响应速度。

  • 资源分配:合理分配哨兵的硬件资源,确保哨兵有足够的性能来处理监控和故障转移任务。

  • 健康检查:定期检查哨兵实例的健康状况,确保它们能够正常工作。

通过以上方法,Redis 哨兵模式可以有效提高 Redis 集群的高可用性和容错能力。

3.2.13 Redis集群分片存储

如果 Redis 内存很大怎么办?

假设一台 32G 内存的服务器部署了一个 Redis,内存占用了 25G,会发生什么?

此时最明显的表现是 Redis 的响应变慢,甚至非常慢。

这是因为 RDB 快照是通过 fork 子线程来实现的,fork 操作时间和 Redis 数据量成正相关,而 fork 时会阻塞主线程。

随着数据量的增加,fork 耗时也会增加。所以,当对 25G 的文件进行 fork 时,Redis 的响应就会变慢。

针对这种大数据量的存储,有什么其他的方案呢?

什么是切片集群?

Redis 分片集群是一种将 Redis 数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性。在分片集群中,数据被分为多个片段,每个片段存储在不同的节点上,这些节点可以是物理服务器虚拟服务器

Redis 分片集群的主要目的是将数据分布在多个节点上,以便可以通过并行处理来提高读写吞吐量。每个节点负责处理一部分数据,并且在需要时可以进行扩展以适应更多的负载。此外,分片集群还提供了故障容错和高可用性的功能,即使其中一个节点发生故障,其他节点仍然可以继续工作。

比如我们将 25GB 的数据平均分成 5 份(当然,也可以不做均分),使用 5 个实例来保存,每个实例只需要保存 5GB 的数据。如下图所示:

这样,每个实例只有 5GB 内存,执行 fork 的时候就快得多,不会阻塞主线程。

实际业务中,大数据量通常是无法避免的。而切片集群,就是一个非常好的方案。

如何保存更多数据?

我们可以纵向扩展也可以横向扩展

纵向扩展

即升级单个 Redis 实例的配置,如内存、硬盘、带宽、CPU 等

横向扩展

即增加 Redis 实例的个数

纵向扩展和横向扩展对比图

那么,纵向扩展和横向扩展的区别是什么呢?

纵向扩展(Scale Up)和横向扩展(Scale Out)是常见的两种扩展方式,用于提升系统的性能和处理能力。它们有着不同的特点和适用场景。

  1. 纵向扩展: 纵向扩展是通过增加单个节点的硬件资源来提升系统性能。具体来说,是增加服务器的处理能力、存储容量或内存大小等。这可以通过升级服务器的 CPU、内存、硬盘等硬件设备来实现。

优点:

  • 简单方便:纵向扩展只需要升级现有的服务器,不需要进行系统的重构和数据迁移

  • 成本相对较低:相对于横向扩展,纵向扩展的成本通常更低,因为只需要购买更高配置的硬件设备。

缺点:

  • 有限的扩展能力:纵向扩展的扩展能力受限于单个节点的硬件资源,无法无限扩展。

  • 单点故障:如果纵向扩展的节点发生故障,整个系统的可用性将会受到影响。

适用场景:

  • 对于单个节点负载较高、需要处理大量并发请求的应用场景,纵向扩展可以提供更好的性能和响应能力。

  • 当数据集较小,可以被一个节点的硬件资源容纳时,纵向扩展是一种经济有效的方式。

  1. 横向扩展: 横向扩展是通过增加多个节点来提升系统的性能和处理能力。每个节点可以是一台独立的服务器或者虚拟机。数据在多个节点上进行分片存储,各个节点共同处理请求,并共享负载。

优点:

  • 无限扩展能力:横向扩展可以通过增加更多节点来实现无限的扩展能力,可以根据需求动态添加或移除节点。

  • 高可用性:由于数据分布在多个节点上,即使其中一个节点发生故障,其他节点仍然可以继续工作,提供高可用性。

缺点:

  • 复杂性增加:横向扩展需要进行数据分片负载均衡的设计和实现,增加了系统的复杂性。

  • 成本较高:相对于纵向扩展,横向扩展需要购买更多的服务器或虚拟机,成本较高。

适用场景:

  • 对于需要处理大量并发请求、数据集较大的应用场景,横向扩展可以提供更好的性能和可伸缩性。

  • 当需要保证系统的高可用性和故障容错能力时,横向扩展是一种可行的方案。

纵向扩展和横向扩展是两种不同的扩展方式,各自有着不同的优点和适用场景。在实际应用中,应根据具体需求和限制,选择合适的扩展方式来提升系统性能和可伸缩性。

在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。

Redis 是如何做分片的

Redis 通过一种称为哈希槽(hash slot)的机制来实现分片集群。哈希槽将整个数据集分成固定数量的槽,每个槽都有一个唯一的编号,通常是从 0 到 16383。

在 Redis 分片集群中,有多个节点(主节点和从节点),每个节点负责存储其中一部分的槽数据。节点之间通过集群间通信协议进行数据的交互和同步。

当一个客户端发送一个命令到 Redis 分片集群时,集群会根据命令涉及的键的哈希值将命令路由到正确的槽上。这个槽所在的节点负责处理这个命令并返回结果给客户端。

具体的分片过程如下:

  1. 客户端发送命令到 Redis 分片集群中的任意一个节点。

  2. 节点根据命令涉及的键的哈希值计算出对应的槽号。

  3. 节点根据槽号确定该槽所在的节点,并将命令路由到该节点。

  4. 该节点处理命令并返回结果给客户端。

当节点加入或离开集群时,Redis 分片集群会自动进行数据的重新分片和迁移,以保持数据的均衡和高可用性。具体的过程如下:

  1. 当一个新节点加入集群时,集群会将一部分槽从现有节点迁移到新节点上,以平衡数据负载。

  2. 当一个节点离开集群时,集群会将该节点负责的槽迁移到其他可用节点上,以保证数据的可用性。

通过哈希槽的机制,Redis 分片集群实现了数据的分片和自动迁移,以提供高可用性、扩展性和容错性。同时,节点间的通信和数据同步保证了集群的一致性和可用性。

详细说说哈希槽

Redis 哈希槽是 Redis 集群中用于分片数据的一种机制。哈希槽的概念可以简单理解为一种数据分片的方式,将所有的数据分散存储在多个节点上,以实现数据的高可用和扩展性。

Redis 集群中共有 16384 个哈希槽,每个槽可以存储一个键值对。当有新的键值对需要存储时,Redis 使用一致性哈希算法将键映射到一个哈希槽中。每个 Redis 节点负责管理一部分哈希槽,节点之间通过 Gossip 协议来进行信息交换,以保证集群的一致性。

在 Redis 集群中,当一个节点宕机或者新增加一个节点时,哈希槽会重新分配。集群会自动将宕机节点上的槽重新分配给其他节点,并且保证每个节点分配的槽数尽量均等。这样可以保证数据的高可用性和负载均衡。

使用 Redis 哈希槽的好处是可以方便地扩展集群的容量,当数据量增大时,可以通过增加节点来分担数据的存储压力。同时,由于哈希槽的分配是自动的,所以对于应用程序而言是透明的,不需要额外的逻辑来处理数据分片。

手动分配哈希槽

示意图中的切片集群一共有 3 个实例,假设有 5 个哈希槽,我们可以通过下面的命令手动分配哈希槽:实例 1 保存哈希槽 0 和 1,实例 2 保存哈希槽 2 和 3,实例 3 保存哈希槽 4。

代码语言:javascript

复制

redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

在集群运行的过程中,key1 和 key2 计算完 CRC16 值后,对哈希槽总个数 5 取模,再根据各自的模数结果,就可以被映射到对应的实例 1 和实例 3 上了。

!! 在手动分配哈希槽时,需要把 16384 个槽都分配完,否则 Redis 集群无法正常工作。

客户端如何定位数据?

在 Redis 集群中,客户端定位数据的过程如下:

  1. 客户端根据键使用一致性哈希算法(Consistent Hashing)计算哈希值。

  2. 根据哈希值,客户端将键映射到某个哈希槽。

  3. 客户端向集群的其中一个节点发送命令请求。

  4. 接收到请求的节点根据哈希槽的分配信息,确定哪个节点负责管理该哈希槽。

  5. 负责该哈希槽的节点将命令请求转发给对应的数据节点。

  6. 数据节点执行命令,将结果返回给负责该哈希槽的节点。

  7. 负责该哈希槽的节点将结果返回给客户端。

通过这个过程,客户端可以定位到存储在 Redis 集群中的数据,并且可以与集群进行交互。这种方式使得客户端可以直接与任意一个节点进行通信,而不需要知道具体的数据分布和节点拓扑。

一致性哈希算法是用来解决数据分片和负载均衡的常用方法,它可以将数据均匀地分布到不同的节点上,避免某个节点负载过高。同时,当节点发生故障或者新增节点时,一致性哈希算法可以最小化数据的迁移量,使得集群可以快速调整和恢复。

需要注意的是,Redis 集群的客户端不需要手动实现一致性哈希算法,因为该算法已经由 Redis 集群内部实现。客户端只需要使用对应的库或驱动程序,如redis-py-cluster库,来连接 Redis 集群,并且直接使用普通的 Redis 命令进行数据操作。库会自动处理数据的定位和节点间的转发。

Moved 重定向命令

在 Redis 集群中,当客户端向一个节点发送命令请求时,如果该节点不负责处理该命令所涉及的哈希槽,它会返回一个 MOVED 重定向错误。

MOVED 重定向错误包含了正确的节点信息,告诉客户端应该向哪个节点重新发送命令。客户端可以根据 MOVED 错误中的信息,更新自己的节点映射表,然后重新发送命令到正确的节点。

以下是一个使用 Python 的 redis-py 库处理 MOVED 重定向错误的示例:

import redis

# 创建Redis集群连接
cluster = redis.RedisCluster(host='127.0.0.1', port=7000)

# 存储数据
cluster.set('key1', 'value1')

# 获取数据
try:
    value = cluster.get('key1')
    print(value)
except redis.exceptions.RedisClusterError as e:
    if isinstance(e, redis.exceptions.ResponseError) and e.args[0].startswith('MOVED'):
        # 解析MOVED错误信息
        _, slot, addr = e.args[0].split()
        host, port = addr.rsplit(':', 1)
        # 更新节点映射表
        cluster.connection_pool.nodes.set_node(host, int(port))
        # 重新发送命令
        value = cluster.get('key1')
        print(value)

# 关闭连接
cluster.close()

在以上示例中,如果客户端收到一个 MOVED 错误,它会解析错误信息,获取正确的节点地址,并更新节点映射表。然后,客户端可以重新发送命令到正确的节点进行数据操作。

需要注意的是,MOVED 重定向错误只会在 Redis 集群模式下发生,单机模式不会出现该错误。因此,只有在使用 Redis 集群时,才需要处理 MOVED 重定向错误。在实际开发中,可以使用相应的库或驱动程序来自动处理 MOVED 错误,而无需手动编写处理逻辑。

ASK 命令

在 Redis 集群中,当客户端向一个节点发送一个不可处理的命令时,节点会返回一个 ASK 错误,指示客户端应该向指定的节点发送命令。客户端可以根据 ASK 错误中的信息,更新自己的节点映射表,并将命令发送到正确的节点上。

以下是一个使用 Python 的 redis-py 库处理 ASK 命令的示例:

import redis

# 创建Redis集群连接
cluster = redis.RedisCluster(host='127.0.0.1', port=7000)

# 存储数据
cluster.set('key1', 'value1')

# 获取数据
try:
    value = cluster.get('key1')
    print(value)
except redis.exceptions.RedisClusterError as e:
    if isinstance(e, redis.exceptions.ResponseError) and e.args[0].startswith('ASK'):
        # 解析ASK错误信息
        _, slot, addr = e.args[0].split()
        host, port = addr.rsplit(':', 1)
        # 更新节点映射表
        cluster.connection_pool.nodes.set_node(host, int(port))
        # 重新发送命令
        value = cluster.get('key1')
        print(value)

# 关闭连接
cluster.close()

举个例子

可以看到,由于负载均衡,Slot 2 中的数据已经从实例 2 迁移到了实例 3,但是,客户端缓存仍然记录着“Slot 2 在实例 2”的信息,所以会给实例 2 发送命令。实例 2 给客户端返回一条 MOVED 命令,把 Slot 2 的最新位置(也就是在实例 3 上),返回给客户端,客户端就会再次向实例 3 发送请求,同时还会更新本地缓存,把 Slot 2 与实例的对应关系更新过来。

需要注意的是,在上图中,当客户端给实例 2 发送命令时,Slot 2 中的数据已经全部迁移到了实例 3。在实际应用时,如果 Slot 2 中的数据比较多,就可能会出现一种情况:客户端向实例 2 发送请求,但此时,Slot 2 中的数据只有一部分迁移到了实例 3,还有部分数据没有迁移。在这种迁移部分完成的情况下,客户端就会收到一条 ASK 报错信息,如下所示:

GET hello:key
(error) ASK 13320 172.16.19.5:6379

这个结果中的 ASK 命令就表示,客户端请求的键值对所在的哈希槽 13320,在 172.16.19.5 这个实例上,但是这个哈希槽正在迁移。此时,客户端需要先给 172.16.19.5 这个实例发送一个 ASKING 命令。这个命令的意思是,让这个实例允许执行客户端接下来发送的命令。然后,客户端再向这个实例发送 GET 命令,以读取数据。

ASK 命令详解

在下图中,Slot 2 正在从实例 2 往实例 3 迁移,key1 和 key2 已经迁移过去,key3 和 key4 还在实例 2。客户端向实例 2 请求 key2 后,就会收到实例 2 返回的 ASK 命令。

ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。

和 MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息。所以,在上图中,如果客户端再次请求 Slot 2 中的数据,它还是会给实例 2 发送请求。这也就是说,ASK 命令的作用只是让客户端能给新实例发送一次请求,而不像 MOVED 命令那样,会更改本地缓存,让后续所有命令都发往新实例。

Redis集群高级特性:动态负载均衡与性能优化

主从节点切换与故障恢复 Redis集群扩容与缩容操作详解

Redis系列(三):深入解读Redis主从同步机制


Redis高可靠靠什么保证?

为什么要提这个呢,因为Redis主从库目的呢其实就是为了实现高可靠。上篇文章中我们说过Redis的AOF、RDB日志其实就是为了减少数据丢失,这是高可靠的一部分。

这篇文章呢,我们聊聊Redis实现高可靠的另一方面:尽量减少服务中断。这里Redis是怎么做的呢?Redis的做法是增加副本冗余,将一份数据同时保存在多个实例上。这样某个实例挂掉并不影响其它实例提供对外服务,保证我们的业务正常运行。

Redis有哪些手段提高高可用呢?

  1. 数据持久化:Redis 支持多种数据持久化方式,包括快照(snapshotting)和日志(append-only file)。快照会定期将内存中的数据保存到磁盘文件,而日志会记录每次写操作,以便在重启时进行恢复。这些持久化方式可以确保即使服务器意外关闭,数据也不会丢失。

  2. 主从复制:Redis 支持主从复制机制,其中一个 Redis 实例作为主节点,负责写操作,而其他实例作为从节点,负责复制主节点的数据。这种方式可以实现数据的备份和负载均衡,从而提高可靠性和性能。

  3. Sentinel 哨兵:Redis Sentinel 是一个监控和自动故障恢复系统,可以监控 Redis 实例的健康状态并在主节点故障时自动进行故障切换。它可以确保系统在主节点发生故障时能够自动切换到备用的从节点,保证服务的连续性。

  4. Cluster 集群:Redis Cluster 是一种分布式系统,将数据分布在多个节点上,以提高可用性和扩展性。每个节点都持有部分数据,并且可以容忍部分节点的故障。当节点发生故障时,集群可以自动重新分配数据,确保服务的可靠性和高可用性。

如何保证副本数据一致?

首先我们要知道,Redis提供了主从库模式,以保证副本一致,主从库之间采用的是读写分离的方式。

Redis中的读写分离基本原理和步骤

Redis 读写分离是一种架构设计,将读操作和写操作分别路由到不同的 Redis 节点上,以提高性能和扩展性。在 Redis 读写分离中,通常会有一个主节点负责写操作,多个从节点负责读操作。

  1. 主节点(写节点)

    • 主节点负责处理所有的写操作,包括写入、更新和删除等。

    • 写操作在主节点上执行,然后主节点将写操作的结果同步到所有从节点。

  2. 从节点(读节点)

    • 从节点负责处理读操作,例如获取数据、查询等。

    • 从节点从主节点复制数据,并在本地保存一份与主节点相同的数据副本。

  3. 读写分离的实现

    • 客户端根据需要的操作类型将请求分发到主节点或从节点。

    • 读操作可以通过负载均衡策略,将请求分发到不同的从节点,实现负载分担。

    • 写操作仍然发送给主节点,确保数据的一致性和完整性。

需要注意的是,Redis 读写分离并不是完全的数据实时同步,因为从节点的数据可能会有一定的延迟。另外,读写分离适用于大多数场景下的负载均衡和性能优化,但在一些特定情况下,例如有序集合等复杂数据结构的查询,仍然需要访问主节点。

实现 Redis 读写分离需要正确配置主从节点的关系,以及在客户端中使用合适的策略进行读写操作的路由。同时,需要注意主节点和从节点之间的数据同步和故障处理,以确保系统的稳定性和可靠性。

Redis主从库第一次同步是如何实现的?

  1. 建立连接: 从服务器会向主服务器发送 PSYNC 命令,表示要进行同步。主服务器收到 PSYNC 命令后,会创建一个专门用于复制的后台线程(replication thread),并等待从服务器的连接。

  2. 全量复制(第一次同步): 当从服务器连接到主服务器后,主服务器会将自己的数据发送给从服务器。这个过程叫做全量复制,主服务器会遍历自己的数据集,将所有数据发送给从服务器。

    • 主服务器会在一个 RDB 文件中保存当前数据集的快照,然后将这个 RDB 文件发送给从服务器。从服务器接收到 RDB 文件后,会加载这个文件,将自己的数据集替换成主服务器的数据集。

    • 在 RDB 文件传输的过程中,主服务器会将在传输期间的写操作记录下来,称为命令传播(command propagation)。这样一来,主服务器就能够在发送完 RDB 文件后,将期间的写操作重新发送给从服务器,以保证从服务器的数据集与主服务器保持一致。

  3. 增量复制: 在完成全量复制后,主从服务器之间会保持一个 TCP 连接,主服务器会将自己的写操作发送给从服务器,从服务器执行这些写操作,从而保持数据一致性。增量复制的数据同步是异步的,但通过记录写操作,主从服务器之间的数据最终会达到一致状态。

需要注意的是,在第一次全量复制的过程中,可能会有一些网络故障、主从服务器负载等情况影响同步。为了提高稳定性和安全性,Redis 提供了一些配置选项和机制,如持久化、复制偏移量、主服务器验证等,来确保主从复制的正常进行。

主从库第一次同步的流程

PSYNC命令

当 Redis 主从复制中的从服务器(Slave)需要与主服务器(Master)进行数据同步时,可以使用 PSYNC(Partial SYNC)命令。PSYNC 命令在 Redis 2.8 版本引入,用于提高数据同步的效率和可靠性。

PSYNC 命令包括两种模式:完全同步(Full Sync)和部分同步(Partial Sync)。

  1. 完全同步(Full Sync): 完全同步在以下情况下发生:

    • 从服务器初次连接主服务器时。

    • 从服务器需要进行初次同步,或者复制偏移量与主服务器的偏移量差距较大时。

    • 主服务器没有保存 RDB 快照文件,所以无法进行部分同步。

    完全同步的过程如下:

    • 从服务器向主服务器发送一条 PSYNC 命令,并附带上自己的复制积压缓冲区的偏移量(offset)和 replid(复制 ID)。

    • 主服务器使用 bgsave命令,生成RDB文件,接着将文件发给从库。

    • 从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件。

  2. 部分同步(Partial Sync): 部分同步在以下情况下发生:

    • 从服务器已经复制了一部分数据,并且复制偏移量与主服务器的偏移量差距较小时。

    部分同步的过程如下:

    • 主库将后续所有 写操作记录到内存中的replication buffer中

    • 从服务器向主服务器发送一条 PSYNC 命令,并附带上自己的复制积压缓冲区的偏移量和 replid。

    • 主库将所有保存的写操作发送给从库,具体来说,就是当RDB发送完成后,就会把此时replication buffer中的修改发给从库,从库再重新执行这些操作。这样一来,主从库就实现同步了

PSYNC 命令的目标是在保证数据一致性的前提下,尽可能地减少数据同步所需的数据传输量,从而提高复制效率。完全同步和部分同步的选择取决于从服务器与主服务器之间的复制状态和数据差距。

主库的烦恼

这里我们能分析得到主库做全量同步时的两个耗时操作:

  1. 生成RDB文件

  2. 传输RDB文件

这里设想一个场景,如果是一主多从的架构,那么主节点就要生成多份RDB并传输给从节点,很显然,这种操作是非常耗时的。这里主要占用两块资源

  1. 通过fork子进程生成RDB快照会 阻塞主线程处理请求

  2. 传输RDB文件会占用 网络带宽

那么有什么方法可以解决这些问题呢? 这里呀,我们就引入了“主-从-从”架构,很容易理解,就是主库只需要同步一份给某从库A,其他从库从从库A同步数据。

如何理解 主-从-从 架构?

主从(Master-Slave)架构是一种常见的数据库复制和数据备份方案。在这种架构中,存在一个主数据库(主服务器)和一个或多个从数据库(从服务器),主数据库负责处理写操作和读操作,从数据库负责复制主数据库的数据,以提供读取操作和备份。

主从架构的工作方式如下:

  1. 主数据库(主服务器):

    • 主数据库是系统的主要数据库,负责处理所有的写操作(数据的插入、更新、删除)和部分读操作。

    • 当主数据库接收到写操作时,会将这些写操作记录到自己的日志文件(例如 MySQL 的二进制日志)中,并发送给从数据库。

    • 主数据库也会保存一个复制积压缓冲区(replication backlog buffer),其中存储了一部分的写操作数据,用于满足部分同步和断线重连的需求。

  2. 从数据库(从服务器):

    • 从数据库是主数据库的复制副本,负责从主数据库复制数据以供读取操作和备份。

    • 从数据库会连接到主数据库,并发送复制请求(如 PSYNC 命令)以获取主数据库的数据更新。

    • 从数据库会持续地复制主数据库的写操作,将写操作应用到自己的数据副本中,以保持与主数据库的数据一致性。

    • 从数据库可以处理读取请求,从而减轻主数据库的读取压力。

主从架构的优势:

  • 负载均衡: 通过将读操作分发给从数据库,可以分担主数据库的读取压力,提高整体系统的吞吐量。

  • 高可用性: 当主数据库出现故障时,可以将其中一个从数据库提升为新的主数据库,从而实现快速故障切换。

  • 数据备份: 从数据库可以作为主数据库的数据备份,用于恢复数据和灾难恢复。

  • 数据分析 从数据库可以用于读取操作,以进行数据分析、报表生成等工作,而不影响主数据库的性能。

需要注意的是,主从架构并不是完全实时的,因为从数据库需要时间来同步主数据库的数据更新。因此,在考虑使用主从架构时,需要权衡数据一致性和性能之间的需求。



如何配置主从从架构呢

安装和配置主服务器(Master):

安装Redis主服务器并确保主服务器正常运行。

在主服务器的配置文件(redis.conf)中开启持久化(通常使用RDB快照或AOF日志)和监听端口,确保配置项如下:

port 6379
save 900 1
appendonly yes  # 如果使用AOF日志

果需要对外提供访问,确保防火墙或网络设置允许访问主服务器的6379端口。

安装和配置第一个从服务器(Slave1):

在从服务器1上安装Redis数据库

在从服务器1的配置文件中配置主从关系。在配置文件中添加类似如下的内容,其中 masterauth是主服务器的密码, master是主服务器的IP和端口:

slaveof master_ip master_port
masterauth your_master_password

重启从服务器1使配置生效。

安装和配置第二个从服务器(Slave2):

  • 在从服务器2上安装Redis数据库。

  • 在从服务器2的配置文件中配置主从关系,与从服务器1相似。确保配置项不冲突。

  • 重启从服务器2使配置生效。

重启主服务器:

在主服务器上查看主服务器的信息,如IP和端口。通常使用以下命令:

INFO server

测试主从从架构:

  • 在主服务器上进行写操作,如插入、更新或删除数据。

  • 查看从服务器1和从服务器2是否同步了主服务器的数据。

需要注意的是,Redis的主从从架构在部署和配置上与主从架构类似,只是需要在从服务器上再次配置主从关系。另外,Redis还可以配置更多高可用性的功能,如哨兵(Sentinel)和集群(Cluster),以实现更强大的架构。具体配置细节可能会因版本和需求而有所不同,建议参考官方文档或相关资源进行详细了解和配置。

主从库间网络断了怎么办?

在 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。

2.8之后呢是支持增量同步的,那么Redis是怎么实现增量同步的呢? 当Redis主从库之间的网络断开后,网络恢复时从库需要进行增量同步,以获取在网络断开期间主库中的更新数据。Redis实现增量同步的方式是通过Redis复制机制,具体流程如下:

  1. 保存主服务器的数据: 主服务器会将更新的数据写入内存,并在内存中保存一份副本。同时,主服务器会将更新的数据写入AOF(Append-Only File)日志文件,以便在断电或宕机情况下能够进行数据恢复

  2. 记录复制偏移量: 在主服务器的复制过程中,主服务器会记录一个复制偏移量(replication offset),表示从服务器在主服务器中的数据位置。这个偏移量会随着数据的更新而递增。

  3. 网络恢复: 当网络恢复时,从服务器会尝试连接主服务器并请求进行复制。

  4. 发送SYNC命令: 从服务器会发送SYNC命令给主服务器。如果是初次连接复制,从服务器发送的SYNC命令中不包含任何参数。如果是增量同步,从服务器会发送带有偏移量参数的SYNC命令。

  5. 全量复制或部分复制: 根据情况,主服务器会执行全量复制或部分复制:

    • 全量复制(初次连接): 如果是初次连接复制,主服务器会执行全量复制。它会创建一个RDB快照(数据库快照),将数据库中的数据快照发送给从服务器。这样从服务器就能够拥有主服务器的完整数据集。

    • 部分复制(增量同步): 如果是增量同步,主服务器会从记录的偏移量处开始,将从偏移量后的所有更新数据发送给从服务器。这样从服务器就能够获取在断开网络期间主服务器的更新数据。

  6. 复制数据传输: 主服务器会将全量数据或增量数据通过网络传输给从服务器。从服务器会接收并处理这些数据,更新自己的数据集。

  7. 复制过程继续: 一旦复制数据传输完成,从服务器会持续地与主服务器保持连接,接收来自主服务器的增量更新。这样,主从库之间的数据保持同步。

需要注意的是,当网络断开时间较长或断开期间数据更新较大时,增量同步可能会导致从服务器落后于主服务器。在网络恢复后,从服务器需要足够的时间来接收和处理更新数据,以保持与主服务器的数据同步。


一般的排查流程

  1. 检查网络连接问题: 首先,确保网络连接问题的确是造成主从库通信中断的原因。检查网络配置、防火墙规则、路由等设置,确保主从库之间可以互相访问。

  2. 重新连接网络: 如果网络问题是暂时的,你可以尝试恢复网络连接,让主从库之间恢复通信。

  3. 检查主从状态: 在主从库网络连接恢复后,使用INFO replication命令检查主从库的同步状态。确保主库已将数据同步到从库。

  4. 手动重新同步: 如果主从库之间的网络断开时间较长,可以考虑进行手动重新同步:

    • 在从库上,使用 SLAVEOF NO ONE命令解除从库状态。

    • 在从库上,删除持久化文件(RDB文件或AOF文件)。

    • 在从库上,执行 SLAVEOF master_ip master_port命令,将其重新设置为主库的从库。

    • 在主库上,执行 SLAVEOF NO ONE命令解除主库状态。

    • 在主库上,执行 SLAVEOF slave_ip slave_port命令,将其重新设置为从库的主库。

  5. 手动复制数据: 如果网络断开时间较长且重新同步不可行,你可能需要手动复制数据。在主库上导出数据,并在从库上导入数据。

  6. 备份和恢复: 如果网络问题无法解决,你可能需要在网络恢复后考虑从主库重新备份数据,然后在从库上进行数据恢复。

总结

文章中介绍了Redis主从库架构以及如何配置、维护和解决主从库网络断开的问题。以下是文章中涉及到的主要内容:

  1. Redis主从库架构及其保证的高可靠性:

    • Redis主从库的目的是实现高可靠性,通过数据持久化、主从复制、Sentinel哨兵和Cluster集群等方式来保障数据的安全性和可用性。

  2. 如何保证副本数据一致:

    • Redis通过全量复制和部分复制(增量同步)来保证主从库之间的数据一致性。复制偏移量和复制积压缓冲区等机制用于记录和传输数据。

  3. 主从库第一次同步的过程:

    • 主从库之间的第一次同步涉及主服务器创建RDB快照,发送给从服务器,以及记录期间的写操作进行命令传播。

  4. PSYNC命令和增量同步:

    • PSYNC命令用于主从库网络断开后的增量同步。完全同步用于初次连接,部分同步用于增量同步,从而减少数据传输量。

  5. 主从从架构及其优势:

    • 主从从架构是在主从架构基础上的扩展,通过级联的方式减轻主服务器的复制压力,实现更高的可用性和负载均衡。

  6. 配置主从从架构的步骤:

    • 安装和配置主服务器,从服务器1和从服务器2。

    • 重启主服务器,查看主服务器信息。

    • 进行测试,验证主从库之间是否同步。

  7. 解决主从库间网络断开问题:

    • 检查网络连接问题,确保主从库之间可以互相访问。

    • 重新连接网络,恢复通信。

    • 检查主从状态,确保同步。

    • 手动重新同步,尝试恢复数据一致性。

    • 手动复制数据或备份恢复数据。

Redis Cluster数据分片实现原理、及请求路由实现

Redis在3.0上加入了 Cluster 集群模式,实现了 Redis 的分布式存储,也就是说每台 Redis 节点上存储不同的数据。但redis cluster发布得比较晚(2015年才发布),在这期间各个大厂在redis主从模式上开发了自己的集群,想进一步详细了请参考:聊聊Redis的各种集群方案、及优缺点对比。

Redis Cluster模式为了解决单机Redis容量有限的问题,将数据按一定的规则分配到多台机器,内存/QPS不受限于单机,可受益于分布式集群高扩展性。Redis Cluster是一种服务器Sharding技术(分片和路由都是在服务端实现),采用多主多从,每一个分区都是由一个Redis主机和多个从机组成,片区和片区之间是相互平行的。Redis Cluster集群采用了P2P的模式,完全去中心化。

如上图,官方推荐,集群部署至少要 3 台以上的master节点,最好使用 3 主 3 从六个节点的模式。Redis Cluster集群具有如下几个特点:

集群完全去中心化,采用多主多从;所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

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

每一个分区都是由一个Redis主机和多个从机组成,片区和片区之间是相互平行的。

每一个master节点负责维护一部分槽,以及槽所映射的键值数据。

redis cluster主要是针对海量数据+高并发+高可用的场景,海量数据,如果你的数据量很大,那么建议就用redis cluster,数据量不是很大时,使用sentinel就够了。redis cluster的性能和高可用性均优于哨兵模式。

1.分片机制-虚拟槽

Redis Cluster采用虚拟哈希槽分区而非一致性hash算法,预先分配16384(2^14)个卡槽,所有的键根据哈希函数映射到 0 ~ 16383整数槽内,每一个分区内的master节点负责维护一部分槽以及槽所映射的键值数据。

 #key到hash槽映射算法:对每个key计算CRC16值,然后对16384取模
计算公式:slot = CRC16(key) & 16383

这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。使用哈希槽的好处就在于可以方便的添加 或 移除节点,当添加或移除节点时,只需要移动对应槽和数据移动到对应节点就行。

需要注意的是:

(1) hash卡槽只会分配给每个片区的主节点上,从节点不会分配卡槽,从节点会同步master上的hash槽。

(2)每个hash卡槽可以存放多个Key,每一个数据key对应一个hash槽。

(3)hash卡槽的目的是确认数据存放到哪个片区的Redis主节点上,实现Redis集群分摊Key。

(4) 每个片区的Redis主节点卡槽数都对应一个范围,多个片区之间卡槽数范围是等比分配的。比如:存在3个片区对应3个Redis主机,那么3个Redis主机的卡槽总数分别是:16384/3。3个Redis主机的卡槽范围分别是:

第一台Redis主机:0~5461

第二台Redis主机:5462 ~ 10922

第三台Redis主机:10923~16383

(5)写操作时,会根据Key值计算出对应的卡槽所在的位置,再将数据存入卡槽区对应的master中;读数据也是一样,通过key得到slot,再通过slot找到node获取数据(客户端读请求是打到任意节点上的,当请求的数据没有在接受请求的node上时,会出现重定向,后面有详细讲解)。

(6)Redis Cluster的节点之间会共享消息,每个节点都会知道是哪个节点负责哪个范围内的数据槽。所以客服端请求任意一个节点,都能获取到slot对应的node信息。

Redis 虚拟槽分区的特点:

解耦数据和节点之间的关系,简化了节点扩容和收缩难度。

节点自身维护槽的映射关系,不需要客户端 或 代理服务维护数据分片关系。

Redis Cluster的节点之间会共享消息,每个节点都知道另外节点负责管理的槽范围。每个节点只能对自己负责的槽进行维护 和 读写操作。

 #面试题:
#1.redis cluster为什么没有使用一致性hash算法,而是使用了哈希槽预分片?
    缓存热点问题:一致性哈希算法在节点太少时,容易因为数据分布不均匀而造成缓存热点的问题。一致性哈希算法可能集中在某个hash区间内的值特别多,会导致大量的数据涌入同一个节点,造成master的热点问题(如同一时间20W的请求都在某个hash区间内)。
 
#2.redis的hash槽为什么是16384(2^14)个卡槽,而不是65536(2^16)个?
(1)如果槽位为65536,发送心跳信息的消息头达8k,发送的心跳包过于庞大。
(2)redis的集群主节点数量基本不可能超过1000个。
	集群节点越多,心跳包的消息体内携带的数据越多。如果节点过1000个,也会导致网络拥堵。因此redis作者,不建议redis cluster节点数量超过1000个。 那么,对于节点数在1000以内的redis cluster集群,16384个槽位够用了。没有必要拓展到65536个。
(3)槽位越小,节点少的情况下,压缩率高。

集群配置:

#1.redis cluster的集群模式可以部分提供服务,当redis.conf的配置cluster-require-full-coverage为no时,表示当一个小主从整体挂掉的时候集群也可以用,也是说0-16383个槽位中,落在该主从对应的slots上面的key是用不了的,但key落在其他的范围是仍然可用的。
 
#2.在cluster架构下,默认的,一般redis-master用于接收读写,而redis-slave则用于备份,当有请求是在向slave发起时,会直接重定向到对应key所在的master来处理。但如果不介意读取的是redis-cluster中有可能过期的数据并且对写请求不感兴趣时,则亦可通过readonly命令,将slave设置成可读,然后通过slave获取相关的key,达到读写分离。
 
#3.redis-cluster 不可用的情况
(1)集群主库半数宕机(无论是否从库存活)。
(2)集群某一节点的主从全数宕机。

2.Redis cluster伸缩的原理

Redis集群中的每个node(节点)负责分摊这16384个slot中的一部分,也就是说,每个slot都对应一个node负责处理。当动态添加或减少node节点时,只需要将16384个槽做个再分配,将槽中的键值和对应的数据迁移到对应的节点上。redis cluster提供了灵活的节点扩容和收缩方案。在不影响集群对外服务的情况下,可以为集群添加节点进行扩容,也可以下线部分节点进行缩容。可以说,槽是 Redis 集群管理数据的基本单位,集群伸缩就是槽和数据在节点之间的移动。

(1)集群扩容

当一个 Redis 新节点运行并加入现有集群后,我们需要为其迁移槽和槽对应的数据。首先要为新节点指定槽的迁移计划,确保迁移后每个节点负责相似数量的槽,从而保证这些节点的数据均匀。如下图:向有三个master集群中加入M4(即node-4),集群中槽和数据的迁移。

#集群扩容过程:
1.首先启动一个 Redis 节点,记为 M4。
 
2.使用 cluster meet 命令,让新 Redis 节点加入到集群中。新节点刚开始都是主节点状态,由于没有负责的槽,所以不能接受任何读写操作,后续我们就给他迁移槽和填充数据。
 
3.对M4节点发送 cluster setslot { slot } importing { sourceNodeId } 命令,让目标节点准备导入槽的数据。 对源节点,也就是 M1,M2,M3 节点发送 cluster setslot { slot } migrating { targetNodeId } 命令,让源节点准备迁出槽的数据。
 
4.源节点执行 cluster getkeysinslot { slot } { count } 命令,获取 count 个属于槽 { slot } 的键,然后执行步骤五的操作进行迁移键值数据。
 
5.在源节点上执行 migrate { targetNodeIp} " " 0 { timeout } keys { key... } 命令,把获取的键通过 pipeline 机制批量迁移到目标节点,批量迁移版本的 migrate 命令在 Redis 3.0.6 以上版本提供。
 
6.重复执行步骤 5 和步骤 6 直到槽下所有的键值数据迁移到目标节点。
 
7.向集群内所有主节点发送 cluster setslot { slot } node { targetNodeId } 命令,通知槽分配给目标节点。为了保证槽节点映射变更及时传播,需要遍历发送给所有主节点更新被迁移的槽执行新节点

(2)集群收缩

收缩节点就是将 Redis 节点下线,整个流程需要如下操作流程:

首先需要确认下线节点是否有负责的槽,如果有,需要把槽和对应的数据迁移到其它节点,保证节点下线后整个集群槽节点映射的完整性。

当下线节点不再负责槽或者本身是从节点时,就可以通知集群内其他节点忘记下线节点,当所有的节点忘记改节点后可以正常关闭。

下线节点需要将节点自己负责的槽迁移到其他节点,原理与之前节点扩容的迁移槽过程一致。迁移完槽后,还需要通知集群内所有节点忘记下线的节点,也就是说让其它节点不再与要下线的节点进行 Gossip 消息交换。

3.客户端请求路由

(1)moved重定向

客服端请求产生moved重定向的执行过程:

1.每个节点通过通信都会共享Redis Cluster中槽和集群中对应节点的关系。

2.客户端向Redis Cluster的任意节点发送命令,接收命令的节点会根据CRC16规则进行hash运算与16383取余,计算自己的槽和对应节点 。

3.如果保存数据的槽被分配给当前节点,则去槽中执行命令,并把命令执行结果返回给客户端。

4.如果保存数据的槽不在当前节点的管理范围内,则向客户端返回moved重定向异常 。

5.客户端接收到节点返回的结果,如果是moved异常,则从moved异常中获取目标节点的信息。

6.客户端向目标节点发送命令,获取命令执行结果。

(2)ask重定向

在对集群进行扩容和缩容时,需要对槽及槽中数据进行迁移。当槽及槽中数据正在迁移时,客服端请求目标节点时,目标节点中的槽已经迁移支别的节点上了,此时目标节点会返回ask转向给客户端。

当客户端向某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应的槽的节点信息;客户端再向正确的节点发送命令时,如果此时正在进行集群扩展或者缩空操作,槽及槽中数据已经被迁移到别的节点了,就会返回ask,这就是ask重定向机制。如下图:

请求执行步骤:

1.当客户端向集群中某个节点发送命令,节点向客户端返回moved异常,告诉客户端数据对应目标槽的节点信息。

2.客户端再向目标节点发送命令,目标节点中的槽已经迁移出别的节点上了,此时目标节点会返回ask重定向给客户端。

2.客户端向新的target节点发送Asking命令,然后再次向新节点发送请求请求命令。

3.新节点target执行命令,把命令执行结果返回给客户端。

#moved和ask重定向的区别:
两者都是客户端重定向
moved异常:槽已经确定迁移,即槽已经不在当前节点
ask异常:槽还在迁移中

(3)smart智能客户端

Redis的Smart客户端是一种设计用于提高在Redis集群环境中数据访问效率的客户端实现。它通过在客户端本地维护一份键到槽(hash slot)再到节点(node)的映射关系,避免了每次数据访问时都进行昂贵的重定向操作,从而提高了数据访问的性能和效率。以下是Smart客户端的相关信息:

Smart客户端的工作原理

  • 初始化时:Smart客户端在初次连接到Redis集群时,会获取集群的节点信息和槽(slot)的分布信息,并在本地缓存一份hash slot与node关系的路由表。

  • 数据访问时:当客户端收到数据访问请求时,它首先在本地使用CRC16算法计算出该key对应的slot,然后从路由表中查询该slot所属的节点信息,并直接连接到该节点执行请求。

  • 处理重定向:如果Redis集群中的节点或槽发生了变化,服务端会返回重定向信息(如MOVED或ASK)。Smart客户端在接收到这些重定向信息时,会更新本地的映射关系,并重新发送请求到正确的节点。

Smart客户端的优势

  • 减少网络开销:通过避免不必要的重定向,Smart客户端可以减少网络通信量,提高数据访问速度。

  • 提高响应时间:由于减少了与Redis节点的交互次数,Smart客户端能够显著提高应用的响应时间。

  • 自动处理集群变化:Smart客户端能够自动处理集群中的节点添加、删除或槽迁移等变化,无需用户干预。

Smart客户端的实现方式

  • 维护路由表:Smart客户端在本地维护一份hash slot到node的映射表,这样大部分情况下可以直接从本地缓存中找到正确的节点。

  • 处理重定向:当客户端收到重定向信息时,它会更新路由表,并重新发送请求到新的节点。如果是ASK重定向,客户端会在本次请求中临时重定向,而MOVED重定向则意味着槽已经明确分派到另一个节点,客户端需要更新槽节点缓存。

通过使用Smart客户端,Redis集群的客户端可以更加高效地访问数据,同时减少了网络延迟和集群管理的复杂性。

在分布式系统中,智能客户端(Smart Client)是一种设计模式,它能够智能地管理和优化与后端服务的交互。在 Redis 集群的上下文中,智能客户端指的是那些能够理解集群拓扑结构并直接与集群中的各个节点进行高效通信的客户端库。这样的客户端能够更好地利用集群的特性,提高应用程序的性能和可扩展性。

智能客户端的特点

  • 了解集群拓扑:智能客户端能够获取集群的当前状态,包括节点的数量、槽的分配情况等。

  • 键名路由:客户端可以根据键名计算出对应的哈希槽,并直接将请求发送到正确的节点。

  • 自动重定向:如果客户端尝试访问的键不在当前节点上,该节点会返回一个重定向信息,智能客户端能够根据这个信息自动重定向请求到正确的节点。

  • 错误处理:智能客户端能够处理常见的集群错误,如节点故障或槽迁移期间的问题。

  • 性能优化:通过减少不必要的重定向和网络往返次数,智能客户端能够提高应用程序的性能。

Redis 智能客户端的实现

在 Redis 集群中,智能客户端通常具备以下功能:

1. 获取集群拓扑

  • 初始化:客户端在初次连接时从集群中的一个节点获取集群拓扑信息。

  • 持续同步:客户端周期性地或在特定事件发生时(如节点加入或离开集群)更新集群拓扑信息。

2. 键名路由

  • 哈希槽计算:客户端根据键名计算出哈希槽编号。

  • 槽到节点映射:客户端使用槽到节点的映射表来确定键值对所在的节点。

3. 自动重定向

  • 重定向处理:客户端能够识别重定向响应,并自动重试请求到正确的节点。

4. 错误处理

  • 故障恢复:客户端能够处理节点故障的情况,并尝试连接到其他健康的节点。

  • 异常处理:客户端能够优雅地处理各种网络异常和 Redis 服务器错误。

5. 性能优化

  • 缓存槽映射:客户端可以缓存槽到节点的映射,减少每次请求

当数据过多,集群节点较多时,客服端大多数请求都会发生重定向,每次重定向都会产生一次无用的请求,严重影响了redis的性能。如果客户端在请求时就知道由哪个节点负责管理哪个槽,再将请求打到对应的节点上,那就有效的解决了这个问题。

提高redis的性能,避免大部分请求发生重定向,可以使用智能客户端。智能客户端知道由哪个节点负责管理哪个槽,而且某个当节点与槽的映射关系发生改变时,客户端也会进行响应的更新,这是一种非常高效的请求方式。

Jedis为Redis Cluster提供了Smart客户端,也就是JedisCluster类。JedisCluster会从集群中选一个可运行的节点,使用 cluster slots 初始化槽和节点映射,将映射关系保存到本地,为每个节点创建JedisPool,相当于为每个redis节点都设置一个JedisPool,然后就可以进行数据读写操作。

smart智能客户端读写数据命令执行过程如下:

1.JedisCluster启动时,会从集群中选一个可运行的节点,使用 cluster slots 初始化槽和节点映射,将映射关系保存到本地。

2.smart 客户端将请求要操作的 key 发送到目标节点,如果请求成功,就得到响应,并返回结果。

3.如果目标节点出现连接出错(说明节点的slot->node的映射有更新),客户端将随机找个活跃节点,向其发送命令,大概率会得到 moved异常,然后根据moved响应更新 slot 和 node 的映射关系,再向新的目标节点发送命令。

如果这样的情况连续出现 5 次未找到目标节点,则抛出异常:Too many cluster redirection!。

总结:mart智能客户的目标:追求性能。避免了大量请求的moved重定向操作,在数据量和请求量大的环境下,极高的提升了redis性能。

4.Redis Cluster主从选举

当某个master挂掉后,在cluster集群仍然可用的前提下,由于某个master可能有多个slave,某个salve将提升为master节点,那么就会存在竞争,那么此时它们的选举机制是怎样的呢?

#1.currentEpoch选举轮次标记
    一个集群状态的相关概念,记录集群状态变更的递增版本号。集群中每发生一次master选举currentEpoch就加一,集群节点创建时,不管是 master还是slave,都置currentEpoch为0。
当前节点在接受其他节点发送的请求时,如果发送者的currentEpoch(消息头部会包含发送者的 currentEpoch)大于当前节点的currentEpoch,那么当前节点会更新currentEpoch。
因此,集群中所有节点的 currentEpoch最终会达成一致,相当于对集群状态的认知达成了一致。

master节点选举过程:

1.slave发现自己的master变为FAIL。

2.发起选举前,slave先给自己的epoch(即currentEpoch)加一,然后请求集群中其它master给自己投票,并广播信息给集群中其他节点。

3.slave发起投票后,会等待至少两倍NODE_TIMEOUT时长接收投票结果,不管NODE_TIMEOUT何值,也至少会等待2秒。

4.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送结果。

5.尝试选举的slave收集master返回的结果,收到超过半投票数master的统一后变成新Master,如果失败会发起第二次选举,选举轮次标记+1继续上面的流程。

6.选举成功后,广播Pong消息通知集群其它节点。

之所以强制延迟至少0.5秒选举,是为确保master的fail状态在整个集群内传开,否则可能只有小部分master知晓,而master只会给处于fail状态的master的slaves投票。
如果一个slave的master状态不是fail,则其它master不会给它投票,Redis通过八卦协议(即Gossip协议,也叫谣言协议)传播fail。
而在固定延迟上再加一个随机延迟,是为了避免多个slaves同时发起选举。
 
#延迟计算公式:
    DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
 
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。