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 连接和断开带来的性能开销。
    • 心跳检测
      • 从节点默认每秒向主节点发送 REPLCONF ACK <offset> 报告复制偏移量
      • 主节点通过心跳检测从节点是否在线

    2.2 增量复制

    主从服务器在完成第一次同步后,就会基于长连接进行命令传播。如果主从服务器间的网络连接断开了,那么就无法进行命令传播了。当主从服务器重新连接的时候,从服务器为了恢复和主服务器的数据一致,有两种方式:
    • 和主服务器重新进行一次全量复制。在 Redis 2.8 版本以前确实是这么做,缺点就是资料开销太大了。
    • 采用增量复制的方式继续同步,也就是只会把网络断开期间主服务器接收到的写操作命令,同步给从服务器。
    notion image
    网络恢复后的增量复制过程:
    • 从服务器在恢复网络后,会发送 PSYNC 命令给主服务器,此时的 PSYNC 命令里的 offset 参数不是 -1;
    • 主服务器收到该命令后,然后用 CONTINUE 响应命令告诉从服务器接下来采用增量复制的方式同步数据;
    • 然后主服务将主从服务器断线期间,所执行的写命令发送给从服务器,然后从服务器执行这些命令。
    那么关键的问题来了,主服务器怎么知道要将哪些增量数据发送给从服务器呢?
    答案藏在这两个东西里:
    • repl_backlog_buffer:是一个环形缓冲区,当从节点短暂断开后重新连接时,可以从中获取断开期间丢失的命令。
    • replication offset:标记上面那个缓冲区的同步进度,主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 来记录自己「」到的位置,从服务器使用 slave_repl_offset 来记录自己「」到的位置。
    notion image
    在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到 repl_backlog_buffer 缓冲区里,因此这个缓冲区里会保存着最近的写命令。
    网络断开后,当从服务器重新连上主服务器时,从服务器会通过 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)
    数据结构
    线性缓冲区
    环形缓冲区
    数据保留
    命令被从节点确认后释放
    新数据覆盖旧数据
    大小配置
    client-output-buffer-limit slave
    repl-backlog-size
    断开连接时的处理
    缓冲区数据丢失
    保留部分数据用于重同步

    2.3 手动切换主库

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

        3、Sentinel模式

        哨兵(Sentinel)机制用于管理 Redis 主从复制集群,实现自动故障转移和监控。哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。从“哨兵”这个名字也可以看得出来,它相当于是“观察者节点”,观察的对象是主从节点。
        Sentinel 负责的功能:
        • 监控:监测主从节点是否存活。
        • 故障转移:当主服务器故障时,Sentinel 会启动自动故障转移过程,将一个从节点升级为主节点,并让其他从节点改为复制新的主节点。
        • 通知:选主结束后,把新主节点的相关信息通知给客户端。
        典型的 Sentinel 部署包含:
        • 一到多个 Redis 主节点(通常只需 1 个主节点)
        • 一到多个 Redis 从节点(建议最少 2 个)
        • 多个 Sentinel 进程(奇数,通常至少 3 个,避免脑裂问题)
        notion image
        Sentinel 实现故障转移的工作流程包括:
        1. 主观下线(SDOWN):单个 Sentinel 检测到节点无响应,标记节点为"主观不可用”。
        1. 客观下线(ODOWN):多个 Sentinel(达到 quorum 数量)确认节点不可用,标记节点为"客观不可用”。
        1. 哨兵集群选举:Sentinel 节点间使用 Raft 算法选举一个 Sentinel 领导者,由这个 Sentinel 领导者执行故障转移。
        1. 故障转移
          1. 选择最合适的从节点(考虑复制偏移量、运行时间等)
          2. 将其提升为新主节点
          3. 配置其他从节点复制新主节点
          4. 更新客户端配置
        客户端连接 Sentinel 集群而不是直接连接 Redis 服务器,Sentinel 会提供当前可用的主服务器地址。

        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)模式来对数据进行管理,并具备分片间数据复制、故障转移和流量调度的能力。
        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 数据库划分为 16384 个哈希槽(slot),集群中的每个节点可以处理 0 个到至多 16384 个槽点,16384个槽位分配到集群中的所有节点(但某些节点可以分配 0 个槽)。当 16384 个哈希槽都有节点进行管理的时候,集群出于可用状态,但只要有一个哈希槽没有被管理到,那么集群处于不可用状态。
        哈希槽和节点的分配关系有两种配置方式:
        1. 均匀分配 。可以使用 CLUSTER CREATE 为多个节点创建集群,也可以 CLUSTER MEET 将新的节点加入已有的集群,两种方式都会将 16384 个slots 平均分配在集群实例上,比如你有 n 个节点,那每个节点的槽位就是 16384 / n 个了 。
        1. 指定哈希槽范围。可以使用 CLUSTER ADDSLOTS 命令来指定节点的哈希槽范围。指定的好处就是性能好的实例节点可以多分担一些压力。
          当我们向 Redis 发送一个 key 值,这个 key 必定会映射到上面 16384 个槽的其中一个。哈希槽与 key 的映射关系计算:
          • 使用 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 时,只计算 {} 中数据。

          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...