type
status
date
slug
summary
tags
category
password

1、概述

Redis 的高可用架构主要通过以下几种方式实现:
  • 主从复制模式:Redis 为了解决单机故障和实现读写分离功能引入了主从模式,但主从模式存在一个问题:master 节点故障后服务,需要人为的手动将 slave 节点切换成为 maser 节点后服务才恢复。
  • 哨兵(Sentinel)模式:Redis 为解决手动切换 master 节点的问题又引入了哨兵模式,哨兵模式能在 master 节点故障后能自动将 salve 节点提升成 master 节点,不需要人工干预操作就能恢复服务可用。
  • 客户端代理模式:但是主从模式、哨兵模式都没有达到真正的数据分片存储,每个 Redis 实例中存储的都是全量数据,各大厂等不及了官方的 sharding 集群版本,陆陆续续开发了自己的 Redis 数据分片集群模式,比如:Twemproxy、Codis 等。
  • 集群(Cluster)模式:Redis Cluster 在 2015 年才正式发布,实现了真正的数据分片存储:数据被切片分散到 16384 个哈希槽上,然后哈希槽分配到多个主节点上,每个主节点只保存一部分哈希槽,另外每个主节点还有主从结构,主节点上的哈希槽会同步到从节点上,实现了真正的数据分片和高可用架构。
集群模式
描述
优点
缺点
主从复制模式
为了避免单点故障和实现读写分离功能,Redis 提供了主从复制模式,允许将一个 Redis 服务器(master 节点)的数据复制到一个或多个 Redis 服务器(slave 节点)。 1、master 节点提供读写功能,一个 master 节点可以有一到多个 salve 节点,master节点通过主从复制向 slave 节点同步写操作。 2、slave 节点只提供读功能,一个 salve 节点也可以有 slave 节点。
主从结构具有读写分离,提高效率、数据备份,提供多个副本等优点。
1、不具备自动容错和恢复功能,主节点故障,集群则无法进行工作,可用性比较低,从节点升主节点需要人工手动干预。 2、只有一个Redis主机来接收和处理写请求,写操作还是受单机瓶颈影响,没有实现真正的分布式架构。
哨兵(Sentinel)模式
哨兵模式基于主从复制模式,只是引入了哨兵来监控与自动处理故障。哨兵本身就是一个运行在特殊模式下的Redis进程,不存储数据,只用来监控。哨兵本身也需要高可用,所以一般会搭建3个以上哨兵节点来组成哨兵集群。 哨兵集群的作用有三个: 1. 监控:判断主从下线。 2. 选主:选出新的主库。 3. 通知:通知从库与新主库实现数据同步,通知客户端与新主库连接。
相比起主从复制模式,哨兵模式下master挂掉可以自动进行切换,系统可用性更高
1、只有一个Redis主机来接收和处理写请求,写操作还是受单机瓶颈影响,没有实现真正的分布式架构。 2、每台 Redis 服务器都存储全量数据,浪费内存。
集群(Cluster)模式
Redis 3.0之后,官方开始支持 Cluster 模式。Cluster 模式真正实现了数据分片和高可用架构(Cluster 模式搭建最少需要 6 个节点 = 3个 master + 3 个 slave) 1、每个 master 节点存储一部分数据分片。 2、每个 master 节点可以挂一个到多个 slave 节点,salve 节点负责从 master 节点同步数据,当 master 节点挂了之后,slave 节点会自动切换为 master 节点。 3、集群内部去中心化,所有节点彼此通过 PING-PONG 机制关联,集群内部通过 Gossip 协议进行通讯。 4、客户端只需要与其中一个 Redis 节点连接。因为每个节点都保存整个 Cluster 的状态,所以客户端不需要连接集群所有节点,只需要连接任何一个可用节点即可。
1. 部署简单 2. 可扩展性高 3. 自动故障转移
1、不支持多个 key 的操作(除非在同一个节点上)。 例如keys 命令也只会显示连接的当前节点的 key 数 2、不支持多数据库键空间,只支持 0 库 3、客户端实现复杂,客户端必须以 Cluster 的协议与 Redis 进行通讯,目前仅 JedisCluster 相对成熟
集群方案的选择:
  • Cluster 模式主要是针对海量数据 + 高并发 + 高可用的场景,海量数据,如果你的数据量很大,那么建议就用 Cluster 模式,性能和高可用性均优于 Sentinel 模式。
  • 如果数据量不是很大时,读写压力适中,使用 Sentinel 模式就够了。
  • 主从复制模式不够稳定,一般只在测试环境使用。
  • 需要数据库平滑迁移的场景,可以考虑使用 Codis。

2、主从复制模式

主从服务器之间采用的是「读写分离」的方式:
  • 主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,
  • 从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。
也就是说,所有的数据修改只在主服务器上进行,然后将最新的数据同步给从服务器,这样就使得主从服务器的数据是一致的。
notion image
配置主从复制:
  • 主节点通常无需特殊配置,但可以设置:
    • 从节点配置:修改从节点的 redis.conf 文件:
      • 或运行时使用命令:

    2.1 主从复制过程

    主从复制过程分为两部分:
    • 同步(全量同步):从服务器和主服务器的第一次同步数据,主服务器会把全量数据发送给从服务器,从服务器接收全量数据,把数据状态恢复成和主服务器一致。
    • 传播(增量同步):后续主服务器发生写操作命令时,会把这些写操作命令作为增量数据发送给从服务器,从服务器重新执行这些写操作命令,通过这种方式保持和主服务器数据状态一致。

    2.1.1 同步过程

    我们可以使用 replicaof(Redis 5.0 之前使用 slaveof)命令形成主服务器和从服务器的关系。
    比如,现在有服务器 A 和 服务器 B,我们在服务器 B 上执行下面这条命令:
    接着,服务器 B 就会变成服务器 A 的「从服务器」,然后与主服务器进行第一次同步。
    notion image
    主从服务器间的第一次全量同步的过程可分为三个阶段:
    1. 建立连接、协商同步。
      1. 从节点发送 PSYNC 命令:执行了 replicaof 命令后,从服务器就会给主服务器发送 PSYNC 命令,表示要进行数据同步。PSYNC 命令包含两个参数:
          • runID:主服务器的 ID。每个 Redis 服务器在启动时都会自动生产一个随机的 ID 来唯一标识自己。当从服务器和主服务器第一次同步时,因为不知道主服务器的 run ID,所以将其设置为 "?"。
          • offset:复制进度,第一次同步时,其值为 -1。
      2. 主节点响应 FULLRESYNC 命令。主服务器收到 PSYNC 命令后,会用 FULLRESYNC 作为响应命令返回给对方。FULLRESYNC 响应命令的意图是采用全量复制的方式,也就是主服务器会把所有的数据都同步给从服务器。这个响应命令会带上两个参数:主服务器的 runID 和主服务器目前的复制进度 offset。
      3. 从服务器收到响应后,会记录这两个值。
    1. 主服务器同步数据给从服务器。
      1. 主节点执行 BGSAVE 命令:主服务器会执行 BGSAVE 命令来生成 RDB 文件,然后把文件发送给从服务器。注意,主服务器生成 RDB 这个过程是不会阻塞主线程的,因为 bgsave 命令是产生了一个子进程来做生成 RDB 文件的工作,是异步工作的,这样 Redis 依然可以正常处理命令。
      2. 从节点清空数据并加载 RDB:从服务器收到 RDB 文件后,会先清空当前的数据,然后载入 RDB 文件,将 RDB 数据载入到内存,最后回复一个确认消息给主服务器。
      3. 主节点创建复制缓冲区(replication buffer):那么为了保证主从服务器的数据一致性。主服务器在下面这三个时间节点将新写入命令存入复制缓冲区
          • 主服务器生成 RDB 文件期间。
          • 主服务器发送 RDB 文件给从服务器期间。
          • 从服务器加载 RDB 文件期间。
    1. 主服务器发送新写操作命令给从服务器。等待从服务器加载完 RDB 文件之后,主服务器将 replication buffer 缓冲区里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区里发来的命令,这时主从服务器的数据就一致了。
    至此,主从服务器的第一次全量同步的工作就完成了。

    2.1.2 传播过程

    • 持续同步:主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 长连接。后续主服务器可以通过这个连接继续将写操作命令传播给从服务器,然后从服务器执行该命令,使得与主服务器的数据库状态相同。TCP 使用长连接的目的是避免频繁的 TCP 连接和断开带来的性能开销。
    • 心跳检测
      • 从节点默认每秒向主节点发送 REPLCONF ACK <offset> 报告复制偏移量
      • 主节点通过心跳检测从节点是否在线

    2.1.3 复制缓冲区replication buffer

    复制缓冲区是主节点上一个专门用于“写操作”的内存缓冲区。当主节点接收到任何会改变数据集(即写入,如 SETLPUSHDEL 等)的命令时,它除了执行命令本身,还会将这个命令以特定的协议格式写入到复制缓冲区中。然后,所有连接到主节点的从节点(Replica)都会持续地从这个缓冲区中读取数据,从而在自己的数据库上执行相同的命令,最终达到数据一致的目的。
    为什么需要复制缓冲区?
    1. 从节点短暂阻塞:如果从节点因为执行复杂度高的命令(如 KEYS *)、持久化(fork)或者重新加载 RDB 文件时会短暂无法处理新命令,也会导致复制中断。
    1. 网络短暂波动:如果某个从节点网络连接变慢,它可能无法即时读取主节点刚产生的命令,导致命令丢失,复制中断。
    复制缓冲区的行为通过以下两个参数控制(在 redis.conf 中配置):
    1. client-output-buffer-limit replica
        • 作用:这是最重要的配置。它限制了用于复制客户端的输出缓冲区大小,以防止主节点因复制过慢而消耗过多内存。
        • 格式:client-output-buffer-limit replica <hard-limit> <soft-limit> <soft-seconds>
          • <hard-limit>:缓冲区大小的绝对硬限制。一旦缓冲区大小超过此值,主节点会立即断开与从节点的连接。这会导致从节点重连并可能触发全量同步,这是一种保护机制。
          • <soft-limit> 和 <soft-seconds>:软限制。如果缓冲区大小持续 <soft-seconds> 秒超过了 <soft-limit>,主节点也会断开从节点的连接。
        • 示例:client-output-buffer-limit replica 256mb 64mb 60
          • 硬限制为 256MB。
          • 如果缓冲区超过 64MB 并持续 60 秒,也会断开连接。
    1. repl-backlog-size
        • 作用:设置复制缓冲区本身的大小。这个缓冲区是一个环形缓冲区(Circular Buffer),当写满后,新数据会覆盖旧数据。
        • 建议:这个值需要根据你的应用场景来设置。设置得太小,很容易因为主从网络中断一段时间后,偏移量被覆盖,从而导致全量同步。设置得太大,会浪费内存。
        • 如何设定?一个粗略的估算公式:buffer_size = (平均写速率 bytes_per_second) * (最大预期断线时间 second) * 2
          • 例如,你的主节点平均每秒产生 1MB 的写数据,你希望即使从节点断开 1 分钟(60秒)也能进行部分同步,那么可以设置为 1MB * 60 * 2 = 120MB。乘以 2 是为了留足安全余量。通常建议设置为百MB级别(如 256mb512mb)。
    注意:repl-backlog-size 是缓冲区的大小,而 client-output-buffer-limit 是触发断开连接的保护阈值。通常,你会将硬限制设置得比 repl-backlog-size 稍大一些。

    2.2 部分重同步过程

    如果命令传播期间网络连接短暂中断后又恢复,从服务器为了恢复和主服务器的数据一致,有两种方式:
    • 和主服务器重新进行一次全量复制。在 Redis 2.8 版本以前确实是这么做,缺点就是资料开销太大了。
    • 采用部分重同步的方式继续同步,也就是只会把网络断开期间主服务器接收到的写操作命令,同步给从服务器。
    notion image
    网络恢复后的增量复制过程:
    • 从服务器在恢复网络后,会发送 PSYNC 命令给主服务器,此时的 PSYNC 命令里的 offset 参数不是 -1,而是自己当前的复制偏移量。
    • 主服务器收到该命令后,然后用 CONTINUE 响应命令告诉从服务器接下来采用增量复制的方式同步数据。
    • 然后主服务将主从服务器断线期间,所执行的写命令发送给从服务器。具体做法是先检查这个偏移量之后的数据是否还在自己的复制积压缓冲区repl_backlog_buffer)中。
      • 如果在:主节点只会将从节点缺失的那部分命令(偏移量之后的数据)发送给它。
      • 如果不在:说明中断时间太长,积压缓冲区里的旧数据已经被新数据覆盖了,主节点只能再次触发全量复制

    2.2.1 复制积压缓冲区(repl_backlog_buffer)

    主从服务器断线重连之后,主服务器怎么知道要将哪些增量数据发送给从服务器呢?主要依赖以下两个数据结构:
    • repl_backlog_buffer:复制挤压缓冲区,一个环形缓冲区(固定大小的先进先出队列),当从节点短暂断开后重新连接时,可以从中获取断开期间丢失的命令。
    • replication offset:标记 repl_backlog_buffer 的同步进度,主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 来记录自己「写」到的位置,从服务器使用 slave_repl_offset 来记录自己「读」到的位置。
    notion image
    1. 在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到 repl_backlog_buffer 缓冲区里,因此这个缓冲区里会保存着最近的写命令。
    1. 网络断开后,当从服务器重新连上主服务器时,从服务器会通过 PSYNC 命令将自己的复制偏移量 slave_repl_offset 发送给主服务器,主服务器根据自己的 master_repl_offset 和 slave_repl_offset 之间的差距,然后来决定对从服务器执行哪种同步操作:
        • 如果判断出从服务器要读取的数据还在 repl_backlog_buffer 缓冲区里,那么主服务器将采用增量同步的方式。主服务器将增量的数据写入到 replication buffer 缓冲区,这个缓冲区缓存将要传播给从服务器的命令。从服务器只需要接收 replication buffer 缓冲区里面的少量增量数据,即可恢复成和主服务器数据一致的状态,这就是增量复制的过程。
        • 相反,如果判断出从服务器要读取的数据已经不存在 repl_backlog_buffer 缓冲区里,那么主服务器将采用全量同步的方式。
    replication buffer 和 repl_backlog_buffer 的区别
    notion image
    • replication buffer(复制缓冲区)线性缓冲区,每个从库对应一个,用于支持主从命令传播,存储的是未发送或已发送但未确认的命令数据。当主节点向从节点发送 RDB 文件时,如果又接收到了写命令操作,就会把它们暂存在 replication buffer 缓冲区。等 RDB 文件传输完成,并且在从节点加载完成后,主节点再把复制缓冲区中的写命令发给从节点,进行同步。
    • repl_backlog_buffer(复制积压缓冲区)环形缓冲区,整个主节点只存在一个(所有从库共享),用于支持部分重同步,存储最近传播的写命令。主节点和从节点进行常规同步时,在把写命令写入 replication buffer 缓冲区后,会把写命令写入 repl_backlog_buffer 缓冲区。如果从节点和主节点间发生了网络断连,等从节点再次连接后,可以从 repl_backlog_buffer 缓冲区中同步尚未复制的命令操作。repl_backlog_buffer 缓冲区是一个环形队列,如果从节点和主节点间的网络断连时间过⻓,环形队列的旧数据可能被新写入的命令覆盖。此时,从节点就没有办法和主节点进行增量复制了,而是只能进行全量复制。
    特性
    replication buffer
    repl_backlog_buffer
    数量
    每个从节点一个
    整个主节点共享一个
    用途
    主从节点间命令传播
    支持部分重同步(PSYNC)
    数据结构
    线性缓冲区
    环形缓冲区
    数据保留
    命令被从节点确认后释放
    新数据覆盖旧数据
    断开连接时的处理
    缓冲区数据丢失
    保留部分数据用于重同步

    2.3 手动切换主库

    在主从复制模式下,当主数据库崩溃时,需要手动将从库切换成主库。
    1. 在从库中使用 REPLICAOF NO ONE 命令提升成主库。
      1. 把旧的主库和其他从库使用 REPLICAOF 命令指向新的主库,即可同步数据。

        3、哨兵(Sentinel)模式

        哨兵(Sentinel)机制用于管理 Redis 主从复制集群,实现监控和自动故障转移。哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。从“哨兵”这个名字也可以看得出来,它相当于是“观察者节点”,观察的对象是主从节点。
        哨兵负责的功能有:
        • 监控:哨兵不断检查主从节点是否运行正常。
        • 主动故障转移:当主节点被判断为不可用时,哨兵会启动自动故障转移过程,将一个从节点升级为新的主节点,并让其他从节点开始复制新的主节点。
        • 通知:选主结束后,把新主节点的相关信息通知给客户端。
        一个典型的哨兵模式架构如下所示:
        • Redis 主从集群:一个主节点和多个从节点,负责数据存储和读写。
        • Sentinel 集群:由多个哨兵进程组成的分布式系统。强烈建议至少部署 3 个或以上的哨兵节点(且为奇数个,如 3, 5)。
          • 为什么需要多个哨兵? 这是为了哨兵自身的高可用。单个哨兵节点可能因为网络问题或自身故障而误判主节点下线(这称为“误报”)。多个哨兵可以协同工作,通过投票机制来决定主节点是否真的不可用,从而避免脑裂误判
        notion image
        Sentinel 的工作流程包括:
        1. 监控:每个哨兵进程会以每秒一次的频率向所有主从节点以及其他哨兵节点发送 PING 命令。
        1. 主观下线(SDOWN):如果某个实例在配置的 down-after-milliseconds 时间内连续无效回复,则该实例会被该哨兵主观下线。
        1. 客观下线(ODOWN):当足够数量的哨兵(达到 quorum 数量,通常需要超过哨兵集群一半的节点,例如 3 个哨兵需要 2 个同意)都认为主节点主观下线时,主节点就会被标记为客观下线。这是触发自动故障转移的条件。
        1. 哨兵集群选举:一旦主节点被判定为客观下线,哨兵节点会通过 Raft 选举算法选出一个领导者哨兵。由这个领导者哨兵来负责执行本次的故障转移操作。这样可以确保只有一个哨兵来执行故障转移,避免混乱。
        1. 故障转移:领导者哨兵执行以下步骤:
          1. 筛选新主节点:它会根据一些规则(如网络连接状态、数据新旧程度、优先级等)从剩余的从节点中筛选出一个最合适的。
          2. 提升新主:向选中的从节点发送 SLAVEOF NO ONE 命令,使其成为主节点。
          3. 切换从节点:向其他从节点发送 SLAVEOF 命令,让它们开始复制新的主节点。
          4. 更新配置:将旧的主节点更新为新的主节点的从节点。这样当旧主节点恢复后,它会成为新主的从节点。
          5. 通知客户端:哨兵会发布消息,通知客户端配置发生了变化。
          6. 客户端感知:因为客户端直接连接的是哨兵集群而不是 Redis 服务器,客户端通过哨兵集群获取最新的主节点地址。

        3.1 初始化并启动Sentinel

        修改 redis.conf 配置文件(可以重命名为 sentinel.conf),将一个 Redis 服务器被设置为 Sentinel。
        然后启动 Sentinel,Sentinel 内部会执行以下步骤:
        1. 初始化 Sentinel 服务器。将普通 Redis 服务器使用的代码替换成 Sentinel 专用代码,包括载入 sentinel 模式专用的命令,包括: pingsentinelinfosubscribeunsubscribepsubscribepunsubscribe
        1. 初始化 Sentinel 状态。
        1. 根据给定的配置文件,初始化 Sentinel 的监视主服务器列表。
        1. 创建连向主服务器的网络连接。对于每个被 Sentinel 监视的服务器来说,Sentinel会创建两个连向服务器的异步网络连接:
          1. 命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复。
          2. 订阅连接,这个连接专门用于订阅主服务器的 __sentinel__:hello 频道。

        3.2 哨兵集群间的互相发现

        哨兵在部署的时候不会只部署一个节点,通常用多个节点部署成哨兵集群(最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断,就可以就可以避免单个哨兵因为自身网络状况不好,而误判主节点下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
        每个哨兵只连接主库,通过 pub/sub 机制,通过订阅名为 __sentinel__:hello 的channel,来获取其他哨兵的信息。
        notion image

        3.3 监控集群状态

        每个哨兵与 master 和 slave 之间建立命令连接和订阅连接,与其他哨兵之间建立命令连接。哨兵的工作内容有:
        • 哨兵间互相通信:定期向 master 和 slave 的 _sentinel_:hello 频道发送自己的信息。哨兵节点不会直接与其他哨兵节点建立连接,而是通过 pub/sub 机制向 master 的 _sentinel_:hello 频道发送消息来向其他 sentinel 告知自己的存在。其他订阅了该频道的 sentinel 都可以接收到,从而各个 sentinel 互知。
        • 获取 master 和 slave 的相关信息:定期(一般 10s,当 master 被标记为主观下线时,改为 1s)向 master 和 slave 发送 INFO 命令获取节点信息。通过 INFO 命令可以实现新节点的自动发现,哨兵只需要配置 master 数据库信息就可以自动发现其 slave 信息,获取到 slave 信息后,哨兵也会与 slave 建立连接执行监控。
        • 发送心跳判断实例是否在线:定期(1s)向 master、slave 和其他哨兵发送 PING 命令。
        • 通知客户端:客户端通过 pub/sub 机制从哨兵订阅所有事件,可以获取主从库切换过程中发生的各个重要事件和主从切换后得到新主库的连接信息。

        3.3.1 主观下线

        哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。
        如果主节点或者从节点没有在规定的时间内响应哨兵的 PING 命令,哨兵就会将它们标记为「主观下线」。这个「规定的时间」是配置项 down-after-milliseconds 参数设定的,单位是毫秒。

        3.3.2 客观下线

        当一个哨兵判断主节点为「主观下线」后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。
        当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为「客观下线」。
        例如,现在有 3 个哨兵,quorum 配置的是 2,那么一个哨兵需要 2 张赞成票,就可以标记主节点为“客观下线”了。这 2 张赞成票包括哨兵自己的一张赞成票和另外两个哨兵的赞成票。
        PS:quorum 的值一般设置为哨兵个数的二分之一加 1,例如 3 个哨兵就设置 2。
        哨兵判断完主节点客观下线后,哨兵就要开始在多个「从节点」中,选出一个从节点来做新主节点。

        3.4 哨兵集群选主

        判断 master 客观下线后,哨兵集群需要选出一个 leader 负责故障转移的工作。哨兵集群内部会进行一次选举,采用 Raft 算法选出一个哨兵执行主从切换
        1. 发现 master 下线的哨兵节点(我们称他为A)向每个哨兵发送命令,要求对方选自己为领头哨兵
        1. 如果目标哨兵节点没有选过其他人,则会同意选举 A 为领头哨兵
        1. 如果有超过一半的哨兵同意选举 A 为领头,则 A 当选
        1. 如果有多个哨兵节点同时参选领头,此时有可能存在一轮投票无竞选者胜出,此时每个参选的节点等待一个随机时间后再次发起参选请求,进行下一轮投票竞选,直至选举出领头哨兵。
        notion image

        3.5 主从故障转移

        主从故障转移操作包含以下四个步骤:
        • 第一步:在已下线主节点(旧主节点)属下的所有「从节点」里面,挑选出一个从节点,并将其转换为主节点。
        • 第二步:让已下线主节点属下的所有「从节点」修改复制目标,修改为复制「新主节点」;
        • 第三步:将新主节点的 IP 地址和信息,通过「发布者/订阅者机制」通知给客户端;
        • 第四步:继续监视旧主节点,当这个旧主节点重新上线时,将它设置为新主节点的从节点;

        3.5.1 选出新的主节点

        哨兵集群选出的 leader,从出现故障的 master 的 slave 节点中挑选一个来当选新的 master。按照三个规则依次进行三轮打分,这三个规则分别是从库优先级、从库复制进度以及从库ID号。只要在某一轮中,有从库得分最高,那么它就是主库了。
        1. 从库优先级:从库配置 slave-priority,优先级高的当选主库。
        1. 从库复制进度:主从同步有一个 repl_backlog_buffer 缓冲区,主库会用 master_repl_offset 记录当前的最新写操作,从库会用slave_repl_offset这个值记录当前的复制进度。slave_repl_offset 最接近 master_repl_offset 的当选主库。
        1. 从库ID号:从库 ID 小的当选主库。
        notion image

        3.5.2 执行主从切换

        当新主节点出现之后,哨兵 leader 下一步要做的就是,让已下线主节点属下的所有「从节点」指向「新主节点」,这一动作可以通过向「从节点」发送 SLAVEOF 命令来实现。
        notion image
        所有从节点指向新主节点后的拓扑图如下:
        notion image

        3.5.3 通知客户的主节点已更换

        通过 Redis 的发布者/订阅者机制来实现的。每个哨兵节点提供发布者/订阅者机制,客户端可以从哨兵订阅消息。
        哨兵提供的消息订阅频道有很多,不同频道包含了主从节点切换过程中的不同关键事件,几个常见的事件如下:
        notion image
        客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了
        通过发布者/订阅者机制机制,有了这些事件通知,客户端不仅可以在主从切换后得到新主节点的连接信息,还可以监控到主从节点切换过程中发生的各个重要事件。这样,客户端就可以知道主从切换进行到哪一步了,有助于了解切换进度。

        3.5.4 将旧主节点变为从节点

        故障转移操作最后要做的是,继续监视旧主节点,当旧主节点重新上线时,哨兵集群就会向它发送 SLAVEOF 命令,让它成为新主节点的从节点。
        notion image

        4、集群(Cluster)模式

        Redis Cluster 是 Redis 的分布式数据库方案,集群通过分片(sharding)模式来对数据进行管理,并具备分片间数据复制、故障转移和流量调度的能力。
        为什么需要 Cluster 模式?在单机版 Redis 或主从复制 + 哨兵模式下,所有数据都存在于一个实例中,受限于单机内存、 CPU 和网络带宽。当数据量巨大或并发请求过高时,单机模式会成为瓶颈。
        Redis Cluster 使用 16384 个哈希槽(hash slot)进行数据分片,集群中每个主节点负责一部分哈希槽,每个键通过 CRC16 算法计算后取模 16384 确定所属槽位。
        Redis Cluster 的特性:
        1. 自动分片:数据被自动分割到多个节点上
        1. 高可用性:支持主从复制,主节点故障时从节点可提升为主节点
        1. 无中心架构:所有节点平等,没有代理层
        1. 客户端路由:客户端直接连接到正确的节点执行命令

        4.1 创建集群

        Redis Cluster 至少需要 3 个主节点 才能正常工作为实现高可用,建议每个主节点配置至少 1 个从节点(加起来共 6 个节点)。
        集群是由一个个互相独立的节点(readis node)组成的, 所以刚开始的时候,他们都是隔离,毫无联系的。我们需要通过一些操作,把他们聚集在一起,最终才能组成真正的可协调工作的集群。
        各个节点的联通是通过 CLUSTER MEET 命令完成的:
        这样就可以让两个节点进行握手(handshake) ,握手成功之后,节点就会将握手另一侧的节点添加到当前节点所在的集群中。
        notion image

        4.2 集群间通讯

        集群中的各个节点通过发送和接收消息(message)来进行通信,Redis集群中的各个节点通过Gossip协议来交换各自关于不同节点的状态信息。Gossip协议实现了无中心式,基本思想就是一个节点周期性地随机选择网络中的一些节点发送消息
        Gossip 协议本身包含 MEET、PING、PONG 三种消息。节点发送的消息总共有以下五种:
        • MEET:当发送者接到客户端发送的 CLUSTER MEET 命令时,发送者会向接收者发送 MEET 消息,请求接收者加入到发送者当前所处的集群里面。
        • PING:集群里的每个节点默认每隔一秒钟就会从已知节点列表中随机选出五个节点,然后对这五个节点中最长时间没有发送过 PING 消息的节点发送 PING 消息,以此来检测被选中的节点是否在线。除此之外,如果节点 A 最后一次收到节点 B 发送的 PONG 消息的时间,距离当前时间已经超过了节点A的 cluster-node-timeout 选项设置时长的一半,那么节点 A 也会向节点 B 发送 PING 消息,这可以防止节点 A 因为长时间没有随机选中节点 B 作为 PING 消息的发送对象而导致对节点 B 的信息更新滞后。
        • PONG:当接收者收到发送者发来的 MEET 消息或者 PING 消息时,为了向发送者确认这条 MEET 消息或者 PING 消息已到达,接收者会向发送者返回一条 PONG 消息。另外,一个节点也可以通过向集群广播自己的 PONG 消息来让集群中的其他节点立即刷新关于这个节点的认识,例如当一次故障转移操作成功执行之后,新的主节点会向集群广播一条 PONG 消息,以此来让集群中的其他节点立即知道这个节点已经变成了主节点,并且接管了已下线节点负责的槽。
        • FAIL:当一个主节点 A 判断另一个主节点 B 已经进入 FAIL 状态时,节点 A 会向集群广播一条关于节点 B 的 FAIL 消息,所有收到这条消息的节点都会立即将节点 B 标记为已下线。
        • PUBLISH:当节点接收到一个 PUBLISH 命令时,节点会执行这个命令,并向集群广播一条 PUBLISH 消息,所有接收到这条 PUBLISH 消息的节点都会执行相同的 PUBLISH 命令。

        4.3 数据分片(slots

        notion image
        在数据分片的设计上 Redis Cluster 没有使用一致性哈希,而是引入了 哈希槽(Hash Slot) 的概念。
        • 总槽数:整个 Redis 数据库划分为 16384 (2^14) 个哈希槽(slot),
        • 槽分配:集群中的每个节点负责处理一部分哈希槽,16384个槽位分配给集群中的所有节点(但某些节点可以分配 0 个槽)。当 16384 个哈希槽都有节点进行管理的时候,集群出于可用状态,但只要有一个哈希槽没有被管理到,那么集群处于不可用状态。哈希槽和节点的分配关系有两种配置方式:
            1. 均匀分配 。可以使用 CLUSTER CREATE 为多个节点创建集群,也可以 CLUSTER MEET 将新的节点加入已有的集群,两种方式都会将 16384 个slots 平均分配在集群实例上,比如你有 n 个节点,那每个节点的槽位就是 16384 / n 个了 。
            1. 指定哈希槽范围。可以使用 CLUSTER ADDSLOTS 命令来指定节点的哈希槽范围。指定的好处就是性能好的实例节点可以多分担一些压力。
        • 键映射:当我们向 Redis 发送一个 key 值,这个 key 必定会映射到上面 16384 个槽的其中一个。哈希槽与 key 的映射关系计算公式是 CRC16(key) % 16384
          • 使用 CRC16 算法计算 key 值,会得出一个 16 bit 的值。
          • 将 16 bit 的值对 16384 取模,得到的值会在 0 ~ 16383 之间,即对应的哈希槽编号。
        在一些特殊的情况下想把某些 key 固定到某个哈希槽上面,也就是同一个实例节点上。这时候可以用 hash tag 能力,强制 key 所归属的槽位等于 tag 所在的槽位。其实现方式为在 key 中加个 {},例如 test_key{1}。如果没使用 hash tag,客户端会对整个 key 进行 CRC16 计算。使用 hash tag 后客户端在计算 key 的 CRC16 时,只计算 {} 中数据。
        为什么哈希槽的数量设定为 16384?
        主要考虑到两个方面:
        • 考虑到心跳包所占用的网络带宽和内存。这是最直接的原因。Redis Cluster 的每个节点都需要定期(每秒)通过 PING/PONG Gossip 协议 与其他节点通信,交换节点的状态信息,其中就包括自身负责的哈希槽信息。哈希槽信息使用 bitmap(位图)的数据结构:
          • 16384 个槽 需要 16384 / 8 = 2048 字节(即 2KB)的 bitmap 来表示。
          • 如果使用 65536 个槽,则需要 65536 / 8 = 8192 字节(即 8KB)的 bitmap
          • Redis 的作者 antirez 认为,在大多数网络环境中,一个 2KB 的心跳包是“恰到好处”的,而 8KB 则显得有些“笨重”,可能会对网络性能产生影响,尤其是在集群节点众多、网络状况一般的情况下。
        • 满足超大规模集群的需求:官方建议的 Redis Cluster 最大节点数量是 1000 个。即使有 1000 个节点,平均每个节点也只需要管理 16384 / 1000 ≈ 16 个槽。这个数量对于管理和数据迁移来说是完全可控的。

        4.4 故障转移

        Cluster 模式的故障转移过程和 Sentinel 模式有点类似,是通过 ping/pong 消息传播机制实现的。

        4.4.1 主观下线

        集群中每个节点都会定期向其他节点发送 PING 消息,接收节点回复 PONG 消息作为响应。如果在 cluster-node-timeout(默认 15s)时间内没有收到 PONG 消息,则发送节点会认为接收节点存在故障,把该节点标记为主观下线(PFAIL)状态。
        notion image

        4.4.2 客观下线

        当集群中大多数主节点(超过半数)都在自己的主观下线报告中将某个节点标记为 PFAIL 时,该节点会被提升为客观下线(FAIL)状态。客观下线标记一个节点真正的下线,如果是持有槽的主节点故障,需要为该节点进行故障转移。判断客观下线流程:
        1. 节点 A 检测到与节点 B 通信超时,然后节点 A 在本地标记节点 B 为 PFAIL
        1. 节点 A 通过 Gossip 协议将节点 B 的 PFAIL 状态传播给其他节点
        1. 其他节点收到信息后,如果它们也认为节点 B 是 PFAIL,会记录这一信息
        1. 当多数主节点都认为节点 B 是 PFAIL 时,某个节点会发起将节点 B 标记为 FAIL 的提案
        1. 提案获得多数主节点同意后,节点 B 被标记为客观下线(FAIL)
        notion image

        4.4.3 集群选主

        故障节点变为客观下线后,如果下线节点是持有槽的主节点则需要在它的从节点中选出一个替换它,从而保证集群的高可用。从节点经过类 Raft 协议自动提升为新主节点:
        1. 资格检查:每个节点都会检查与主节点断开的时间。如果这个时间超过了 cluster-node-timeout * cluster-slave-validity-factor(从节点有效因子,默认为 10),那么就没有故障转移的资格。也就是说这个从节点和主节点断开的太久了,很久没有同步主节点的数据了,不适合成为新的主节点,因为成为主节点以后其他的从节点回同步自己的数据。
        1. 等待延迟:从节点会等待一个延迟时间,计算公式为:延迟时间 = 500ms + 随机延迟(0-500ms) + SLAVE_RANK * 1000msSLAVE_RANK 是从节点的复制偏移量排名,复制数据越新的从节点 rank 值越小
          1. notion image
        1. 投票选举:
          1. 发起投票:延迟结束后,从节点会尝试发起选举:增加自己的 currentEpoch,并向集群中所有主节点广播 FAILOVER_AUTH_REQUEST 消息。
          2. 主节点投票:主节点收到 FAILOVER_AUTH_REQUEST 后,会检查以下内容,满足条件的主节点会回复 FAILOVER_AUTH_ACK
            1. 检查请求的 currentEpoch 是否比自己的大,否则拒绝
            2. 检查是否已经给同一轮选举投过票
            3. 检查发起请求的从节点的主节点确实被标记为 FAIL
            4. 在 cluster-node-timeout * 2 时间内,每个主节点只能投一次票
          3. 从节点成为主节点:从节点如果获得大多数主节点(超过 N/2 + 1)的投票,就会提升自己为新主节点,并广播 PONG 消息通知集群其他节点,开始处理客户端请求。
          4. notion image
        1. 配置:新主节点触发替换主节点操作,包括接管旧主节点的哈希槽,更新集群配置信息,并通知其他节点更新自己的集群配置。

        4.5 客户端重定向

        客户端连接 Redis Cluster 与连接单机 Redis 有一些不同之处,客户端的连接协议需要实现 Cluster 协议,来实现自动重定向
        重定向是指 Redis Cluster 使用哈希槽分片(16384个槽),每个节点负责一部分槽。当客户端连接的节点时不包含 key 所在的槽时,需要通过重定向机制引导客户端到正确的节点。
        Redis Cluster 主要有两种重定向响应:
        1. MOVED 重定向
            • 格式:MOVED <slot> <ip>:<port>
            • 含义:表示请求的键不属于当前节点,并告知客户端正确的节点地址
            • 客户端行为:客户端应该更新本地 slot 映射表,然后向指定节点重新发送请求
        1. ASK 重定向
            • 格式:ASK <slot> <ip>:<port>
            • 含义:表示请求的键正在进行迁移(resharding),暂时由目标节点负责
            • 客户端行为:客户端应该先向目标节点发送ASKING命令,然后再发送原命令
        客户端重定向流程:
        1. 客户端连接集群中任意节点
        1. 发送命令如 GET mykey
        1. 如果 mykey 的哈希槽不在当前节点:
            • 如果该槽正在迁移中 → 返回 ASK 重定向
            • 如果该槽完全属于其他节点 → 返回 MOVED 重定向
        1. 客户端根据响应连接到正确节点并重新发送命令
        notion image
        例如使用 redis-cli 客户端连接时:
        -c 参数表示以集群模式连接,这样客户端会自动处理重定向。其他大部分 Redis 客户端库都支持集群模式。
        Redis系列:常见面试题Redis系列:事件机制和线程模型
        Loading...