Redis 高可用篇

主从复制是怎么实现的?

  • 如果服务器发生了宕机,由于数据恢复是需要点时间,那么这个期间是无法服务新的请求的;
  • 如果这台服务器的硬盘出现了故障,可能数据就都丢失了。

要避免这种单点故障,最好的办法是将数据备份到其他服务器上,让这些服务器也可以对外提供服务,这样即使有一台服务器出现了故障,其他服务器依然可以继续提供服务。

主机更新后根据配置和策略,自动同步到备机的mater/slaver机制,Master以写为主,Slave以读为主。

多台服务器要保存同一份数据,这里问题就来了。

这些服务器之间的数据如何保持一致性呢?数据的读写操作是否每台服务器都可以处理?

Redis 提供了主从复制模式,来避免上述的问题。

这个模式可以保证多台服务器的数据一致性,且主从服务器之间采用的是「读写分离」的方式。

主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。

也就是说,所有的数据修改只在主服务器上进行,然后将最新的数据同步给从服务器,这样就使得主从服务器的数据是一致的。

同步这两个字说的简单,但是这个同步过程并没有想象中那么简单,要考虑的事情不是一两个。

我们先来看看,主从服务器间的第一次同步是如何工作的?

第一次同步

多台服务器之间要通过什么方式来确定谁是主服务器,或者谁是从服务器呢?

我们可以使用 replicaof(Redis 5.0 之前使用 slaveof)命令形成主服务器和从服务器的关系。

比如,现在有服务器 A 和 服务器 B,我们在服务器 B 上执行下面这条命令:

powershell 复制代码
# 服务器 B 执行这条命令
replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>

接着,服务器 B 就会变成服务器 A 的「从服务器」,然后与主服务器进行第一次同步。

主从服务器间的第一次同步的过程可分为三个阶段:

  • 第一阶段是建立连接、协商同步;
  • 第二阶段是主服务器同步数据给从服务器;
  • 第三阶段是主服务器发送新写操作命令给从服务器。

为了让你更清楚了解这三个阶段,我画了一张图。

接下来,我在具体介绍每一个阶段都做了什么。

:::info
第一阶段:建立链接、协商同步

:::

执行了 replicaof 命令后,从服务器就会给主服务器发送 psync 命令,表示要进行数据同步。

psync 命令包含两个参数,分别是主服务器的 runID复制进度 offset

  • runID,每个 Redis 服务器在启动时都会自动生产一个随机的 ID 来唯一标识自己。当从服务器和主服务器第一次同步时,因为不知道主服务器的 run ID,所以将其设置为 "?"。
  • offset,表示复制的进度,第一次同步时,其值为 -1。

主服务器收到 psync 命令后,会用 FULLRESYNC 作为响应命令返回给对方。

并且这个响应命令会带上两个参数:主服务器的 runID 和主服务器目前的复制进度 offset。从服务器收到响应后,会记录这两个值。

FULLRESYNC 响应命令的意图是采用全量复制的方式,也就是主服务器会把所有的数据都同步给从服务器。

所以,第一阶段的工作时为了全量复制做准备

那具体怎么全量同步呀呢?我们可以往下看第二阶段。

:::info
第二阶段:主服务器同步数据给从服务器

:::

接着,主服务器会执行 bgsave 命令来生成 RDB 文件,然后把文件发送给从服务器。

从服务器收到 RDB 文件后,会先清空当前的数据,然后载入 RDB 文件。

这里有一点要注意,主服务器生成 RDB 这个过程是不会阻塞主线程的,因为 bgsave 命令是产生了一个子进程来做生成 RDB 文件的工作,是异步工作的,这样 Redis 依然可以正常处理命令。

但是,这期间的写操作命令并没有记录到刚刚生成的 RDB 文件中,这时主从服务器间的数据就不一致了。

那么为了保证主从服务器的数据一致性,主服务器在下面这三个时间间隙中将收到的写操作命令,写入到 replication buffer 缓冲区里

  • 主服务器生成 RDB 文件期间;
  • 主服务器发送 RDB 文件给从服务器期间;
  • 「从服务器」加载 RDB 文件期间;

:::info
第三阶段:主服务器发送新写操作命令给从服务器

:::

在主服务器生成的 RDB 文件发送完,从服务器收到 RDB 文件后,丢弃所有旧数据,将 RDB 数据载入到内存。完成 RDB 的载入后,会回复一个确认消息给主服务器。

接着,主服务器将 replication buffer 缓冲区里所记录的写操作命令发送给从服务器,从服务器执行来自主服务器 replication buffer 缓冲区里发来的命令,这时主从服务器的数据就一致了。

至此,主从服务器的第一次同步的工作就完成了。

命令传播

主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 连接。

后续主服务器可以通过这个连接继续将写操作命令传播给从服务器,然后从服务器执行该命令,使得与主服务器的数据库状态相同。

而且这个连接是长连接的,目的是避免频繁的 TCP 连接和断开带来的性能开销。

上面的这个过程被称为基于长连接的命令传播,通过这种方式来保证第一次同步后的主从服务器的数据一致性。

分摊主服务器的压力

在前面的分析中,我们可以知道主从服务器在第一次数据同步的过程中,主服务器会做两件耗时的操作:生成 RDB 文件和传输 RDB 文件。

主服务器是可以有多个从服务器的,如果从服务器数量非常多,而且都与主服务器进行全量同步的话,就会带来两个问题:

  • 由于是通过 bgsave 命令来生成 RDB 文件的,那么主服务器就会忙于使用 fork() 创建子进程,如果主服务器的内存数据非大,在执行 fork() 函数时是会阻塞主线程的,从而使得 Redis 无法正常处理请求;
  • 传输 RDB 文件会占用主服务器的网络带宽,会对主服务器响应命令请求产生影响。

这种情况就好像,刚创业的公司,由于人不多,所以员工都归老板一个人管,但是随着公司的发展,人员的扩充,老板慢慢就无法承担全部员工的管理工作了。

要解决这个问题,老板就需要设立经理职位,由经理管理多名普通员工,然后老板只需要管理经理就好。

Redis 也是一样的,从服务器可以有自己的从服务器,我们可以把拥有从服务器的从服务器当作经理角色,它不仅可以接收主服务器的同步数据,自己也可以同时作为主服务器的形式将数据同步给从服务器,组织形式如下图:

通过这种方式,主服务器生成 RDB 和传输 RDB 的压力可以分摊到充当经理角色的从服务器

那具体怎么做到的呢?

其实很简单,我们在「从服务器」上执行下面这条命令,使其作为目标服务器的从服务器:

powershell 复制代码
replicaof <目标服务器的IP> 6379

此时如果目标服务器本身也是「从服务器」,那么该目标服务器就会成为「经理」的角色,不仅可以接受主服务器同步的数据,也会把数据同步给自己旗下的从服务器,从而减轻主服务器的负担。

增量复制

主从服务器在完成第一次同步后,就会基于长连接进行命令传播。

可是,网络总是不按套路出牌的嘛,说延迟就延迟,说断开就断开。

如果主从服务器间的网络连接断开了,那么就无法进行命令传播了,这时从服务器的数据就没办法和主服务器保持一致了,客户端就可能从「从服务器」读到旧的数据。

那么问题来了,如果此时断开的网络,又恢复正常了,要怎么继续保证主从服务器的数据一致性呢?

在 Redis 2.8 之前,如果主从服务器在命令同步时出现了网络断开又恢复的情况,从服务器就会和主服务器重新进行一次全量复制,很明显这样的开销太大了,必须要改进一波。

所以,从 Redis 2.8 开始,网络断开又恢复后,从主从服务器会采用增量复制的方式继续同步,也就是只会把网络断开期间主服务器接收到的写操作命令,同步给从服务器。

网络恢复后的增量复制过程如下图:

主要有三个步骤:

  • 从服务器在恢复网络后,会发送 psync 命令给主服务器,此时的 psync 命令里的 offset 参数不是 -1;
  • 主服务器收到该命令后,然后用 CONTINUE 响应命令告诉从服务器接下来采用增量复制的方式同步数据;
  • 然后主服务将主从服务器断线期间,所执行的写命令发送给从服务器,然后从服务器执行这些命令。

那么关键的问题来了,主服务器怎么知道要将哪些增量数据发送给从服务器呢?

答案藏在这两个东西里:

  • repl_backlog_buffer ,是一个「环形」缓冲区,用于主从服务器断连后,从中找到差异的数据;
  • replication offset ,标记上面那个缓冲区的同步进度,主从服务器都有各自的偏移量,主服务器使用 master_repl_offset 来记录自己「 」到的位置,从服务器使用 slave_repl_offset 来记录自己「」到的位置。

那 repl_backlog_buffer 缓冲区是什么时候写入的呢?

在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到 repl_backlog_buffer 缓冲区里,因此 这个缓冲区里会保存着最近传播的写命令。

网络断开后,当从服务器重新连上主服务器时,从服务器会通过 psync 命令将自己的复制偏移量 slave_repl_offset 发送给主服务器,主服务器根据自己的 master_repl_offset 和 slave_repl_offset 之间的差距,然后来决定对从服务器执行哪种同步操作:

  • 如果判断出从服务器要读取的数据还在 repl_backlog_buffer 缓冲区里,那么主服务器将采用增量同步的方式;
  • 相反,如果判断出从服务器要读取的数据已经不存在 repl_backlog_buffer 缓冲区里,那么主服务器将采用全量同步的方式。

当主服务器在 repl_backlog_buffer 中找到主从服务器差异(增量)的数据后,就会将增量的数据写入到 replication buffer 缓冲区,这个缓冲区我们前面也提到过,它是缓存将要传播给从服务器的命令。

repl_backlog_buffer 缓行缓冲区的默认大小是 1M,并且由于它是一个环形缓冲区,所以当缓冲区写满后,主服务器继续写入的话,就会覆盖之前的数据。因此,当主服务器的写入速度远超于从服务器的读取速度,缓冲区的数据一下就会被覆盖。

那么在网络恢复时,如果从服务器想读的数据已经被覆盖了,主服务器就会采用全量同步,这个方式比增量同步的性能损耗要大很多。

因此,为了避免在网络恢复时,主服务器频繁地使用全量同步的方式,我们应该调整下 repl_backlog_buffer 缓冲区大小,尽可能的大一些,减少出现从服务器要读取的数据被覆盖的概率,从而使得主服务器采用增量同步的方式。

那 repl_backlog_buffer 缓冲区具体要调整到多大呢?

repl_backlog_buffer 最小的大小可以根据这面这个公式估算。

  • second 为从服务器断线后重新连接上主服务器所需的平均 时间(以秒计算)。
  • write_size_per_second 则是主服务器平均每秒产生的写命令数据量大小。

举个例子,如果主服务器平均每秒产生 1 MB 的写命令,而从服务器断线之后平均要 5 秒才能重新连接主服务器。

那么 repl_backlog_buffer 大小就不能低于 5 MB,否则新写地命令就会覆盖旧数据了。

当然,为了应对一些突发的情况,可以将 repl_backlog_buffer 的大小设置为此基础上的 2 倍,也就是 10 MB。

关于 repl_backlog_buffer 大小修改的方法,只需要修改配置文件里下面这个参数项的值就可以。

powershell 复制代码
repl-backlog-size 1mb

Redis 主从复制的面试题

Redis 主从节点是长连接还是短连接?

长连接

怎么判断 Redis 某个节点是否正常工作?

Redis 判断节点是否正常工作,基本都是通过互相的 ping-pong 心跳检测机制,如果有一半以上的节点去 ping 一个节点的时候没有 pong 回应,集群就会认为这个节点挂掉了,会断开与这个节点的连接。

Redis 主从节点发送的心态间隔是不一样的,而且作用也有一点区别:

  • Redis 主节点默认每隔 10 秒对从节点发送 ping 命令,判断从节点的存活性和连接状态,可通过参数repl-ping-slave-period控制发送频率。
  • Redis 从节点每隔 1 秒发送 replconf ack{offset} 命令,给主节点上报自身当前的复制偏移量,目的是为了:
    • 实时监测主从节点网络状态;
    • 上报自身复制偏移量, 检查复制数据是否丢失, 如果从节点数据丢失, 再从主节点的复制缓冲区中拉取丢失数据。

主从复制架构中,过期key如何处理?

主节点处理了一个key或者通过淘汰算法淘汰了一个key,这个时间主节点模拟一条del命令发送给从节点,从节点收到该命令后,就进行删除key的操作。

Redis 是同步复制还是异步复制?

Redis 主节点每次收到写命令之后,先写到内部的缓冲区,然后异步发送给从节点。

主从复制中两个Buffer(replication buffer,repl backlog buffer)有什么区别?

replication buffer 、repl backlog buffer 区别如下:

  • 出现的阶段不一样:
    • repl backlog buffer 是在增量复制阶段出现,一个主节点只分配一个 repl backlog buffer
    • replication buffer 是在全量复制阶段和增量复制阶段都会出现,主节点会给每个新连接的从节点,分配一个 replication buffer
  • 这两个 Buffer 都有大小限制的,当缓冲区满了之后,发生的事情不一样:
    • 当 repl backlog buffer 满了,因为是环形结构,会直接覆盖起始位置数据;
    • 当 replication buffer 满了,会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制

为什么出现主从数据不一致?

主从数据不一致,就是指客户端从从节点中读取到的值和主节点中的最新值并不一致。

之所以会出现主从数据不一致的现象,是因为主从节点间的命令复制是异步进行的,所以无法实现强一致性保证(主从数据时时刻刻保持一致)。

具体来说,在主从节点命令传播阶段,主节点收到新的写命令后,会发送给从节点。但是,主节点并不会等到从节点实际执行完命令后,再把结果返回给客户端,而是主节点自己在本地执行完命令后,就会向客户端返回结果了。如果从节点还没有执行主节点同步过来的命令,主从节点间的数据就不一致了。

如何应对主从数据不一致?

第一种方法,尽量保证主从节点间的网络连接状况良好,避免主从节点在不同的机房。

第二种方法,可以开发一个外部程序来监控主从节点间的复制进度。具体做法:

  • Redis 的 INFO replication 命令可以查看主节点接收写命令的进度信息(master_repl_offset)和从节点复制写命令的进度信息(slave_repl_offset),所以,我们就可以开发一个监控程序,先用 INFO replication 命令查到主、从节点的进度,然后,我们用 master_repl_offset 减去 slave_repl_offset,这样就能得到从节点和主节点间的复制进度差值了。
  • 如果某个从节点的进度差值大于我们预设的阈值,我们可以让客户端不再和这个从节点连接进行数据读取,这样就可以减少读到不一致数据的情况。不过,为了避免出现客户端和所有从节点都不能连接的情况,我们需要把复制进度差值的阈值设置得大一些。

主从切换如何减少数据丢失?

主从切换过程中,产生数据丢失的情况有两种:

  • 异步复制同步丢失
  • 集群产生脑裂数据丢失

我们不可能保证数据完全不丢失,只能做到使得尽量少的数据丢失。

异步复制同步丢失

对于 Redis 主节点与从节点之间的数据复制,是异步复制的,当客户端发送写请求给主节点的时候,客户端会返回 ok,接着主节点将写请求异步同步给各个从节点,但是如果此时主节点还没来得及同步给从节点时发生了断电,那么主节点内存中的数据会丢失。

:::info
减少异步复制的数据丢失的方案

:::

Redis 配置里有一个参数 min-slaves-max-lag,表示一旦所有的从节点数据复制和同步的延迟都超过了 min-slaves-max-lag 定义的值,那么主节点就会拒绝接收任何请求。

假设将 min-slaves-max-lag 配置为 10s 后,根据目前 master->slave 的复制速度,如果数据同步完成所需要时间超过10s,就会认为 master 未来宕机后损失的数据会很多,master 就拒绝写入新请求。这样就能将 master 和 slave 数据差控制在10s内,即使 master 宕机也只是这未复制的 10s 数据。

那么对于客户端,当客户端发现 master 不可写后,我们可以采取降级措施,将数据暂时写入本地缓存和磁盘中,在一段时间(等 master 恢复正常)后重新写入 master 来保证数据不丢失,也可以将数据写入 kafka 消息队列,等 master 恢复正常,再隔一段时间去消费 kafka 中的数据,让将数据重新写入 master

集群产生脑裂数据丢失

先来理解集群的脑裂现象,这就好比一个人有两个大脑,那么到底受谁控制呢?

那么在 Redis 中,集群脑裂产生数据丢失的现象是怎样的呢?

在 Redis 主从架构中,部署方式一般是「一主多从」,主节点提供写操作,从节点提供读操作。

如果主节点的网络突然发生了问题,它与所有的从节点都失联了,但是此时的主节点和客户端的网络是正常的,这个客户端并不知道 Redis 内部已经出现了问题,还在照样的向这个失联的主节点写数据(过程A),此时这些数据被主节点缓存到了缓冲区里,因为主从节点之间的网络问题,这些数据都是无法同步给从节点的。

这时,哨兵也发现主节点失联了,它就认为主节点挂了(但实际上主节点正常运行,只是网络出问题了),于是哨兵就会在从节点中选举出一个 leeder 作为主节点,这时集群就有两个主节点了 ------ 脑裂出现了

这时候网络突然好了,哨兵因为之前已经选举出一个新主节点了,它就会把旧主节点降级为从节点(A),然后从节点(A)会向新主节点请求数据同步,因为第一次同步是全量同步的方式,此时的从节点(A)会清空掉自己本地的数据,然后再做全量同步。所以,之前客户端在过程 A 写入的数据就会丢失了,也就是集群产生脑裂数据丢失的问题

总结一句话就是:由于网络问题,集群节点之间失去联系。主从数据不同步;重新平衡选举,产生两个主服务。等网络恢复,旧主节点会降级为从节点,再与新主节点进行同步复制的时候,由于会从节点会清空自己的缓冲区,所以导致之前客户端写入的数据丢失了。

:::info
减少脑裂的数据丢失的方案

:::

TODO ?? 这里的复制和同步延迟如何实现???

当主节点发现「从节点下线的数量太多」,或者「网络延迟太大」的时候,那么主节点会禁止写操作,直接把错误返回给客户端。

在 Redis 的配置文件中有两个参数我们可以设置:

  • min-slaves-to-write x,主节点必须要有至少 x 个从节点连接,如果小于这个数,主节点会禁止写数据。
  • min-slaves-max-lag x,主从数据复制和同步的延迟不能超过 x 秒,如果主从同步的延迟超过 x 秒,主节点会禁止写数据。

我们可以把 min-slaves-to-write 和 min-slaves-max-lag 这两个配置项搭配起来使用,分别给它们设置一定的阈值,假设为 N 和 T。

这两个配置项组合后的要求是,主节点连接的从节点中至少有 N 个从节点,「并且」主节点进行数据复制时的 ACK 消息延迟不能超过 T 秒,否则,主节点就不会再接收客户端的写请求了。

即使原主节点是假故障,它在假故障期间也无法响应哨兵心跳,也不能和从节点进行同步,自然也就无法和从节点进行 ACK 确认了。这样一来,min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,原主节点就会被限制接收客户端写请求,客户端也就不能在原主节点中写入新数据了

等到新主节点上线时,就只有新主节点能接收和处理客户端请求,此时,新写的数据会被直接写到新主节点中。而原主节点会被哨兵降为从节点,即使它的数据被清空了,也不会有新数据丢失。我再来给你举个例子。

假设我们将 min-slaves-to-write 设置为 1,把 min-slaves-max-lag 设置为 12s,把哨兵的 down-after-milliseconds 设置为 10s,主节点因为某些原因卡住了 15s,导致哨兵判断主节点客观下线,开始进行主从切换。同时,因为原主节点卡住了 15s,没有一个从节点能和原主节点在 12s 内进行数据复制,原主节点也无法接收客户端请求了。这样一来,主从切换完成后,也只有新主节点能接收请求,不会发生脑裂,也就不会发生数据丢失的问题了。

主从如何做到故障自动切换?

主节点挂了 ,从节点是无法自动升级为主节点的,这个过程需要人工处理,在此期间 Redis 无法对外提供写操作。

此时,Redis 哨兵机制就登场了,哨兵在发现主节点出现故障时,由哨兵自动完成故障发现和故障转移,并通知给应用方,从而实现高可用性。

如何实现 "断开重连后接收历史消息"?

在 Redis 中实现 "断开重连后接收历史消息",核心是解决消息持久化消费位置记录 两个问题。Redis 的原生数据结构中,Stream是最适合此场景的方案(Redis 5.0 + 引入),它专为消息队列设计,天然支持消息持久化、消费偏移量记录和重连后历史消息回溯。

  • 消息持久化:消息一旦写入 Stream,会被持久化(可配合 RDB/AOF 确保重启不丢失)。
  • 消费偏移量 :支持消费者组(Consumer Group),自动记录每个消费者的最后消费位置(<font style="color:#000000;">last_delivered_id</font>)。
  • 历史消息回溯:消费者重连后,可从上次记录的偏移量继续读取未消费的历史消息。

关键配置:确保消息不丢失

  1. 持久化配置 :开启 Redis 的 AOF(Append Only File)持久化,确保消息写入磁盘:conf
plain 复制代码
appendonly yes          # 开启AOF
appendfsync everysec    # 每秒同步一次(平衡性能和安全性)
  1. 消费者组偏移量持久化:消费者组的偏移量会被 Redis 自动持久化,重启后依然有效。

相比 Redis 的其他结构(如 List 的 BRPOP),Stream 无需手动维护偏移量,且提供更完善的消息确认、回溯和持久化机制,是最优解。

Redis Cluster

Redis Cluster 的 "ASK 错误" 和 "MOVED 错误" 有什么区别?客户端如何处理?

在 Redis Cluster 中,<font style="color:#000000;">ASK</font> 错误和 <font style="color:#000000;">MOVED</font> 错误都与槽位(slot)的重定位有关,但它们的触发场景、含义和处理方式有本质区别。

区别
维度 **<font style="color:rgb(0, 0, 0);">MOVED</font>**错误 **<font style="color:rgb(0, 0, 0);">ASK</font>**错误
触发场景 槽位已永久迁移到新节点 槽位正在迁移过程中(临时状态)
含义 告知客户端:该槽位的新主节点是 X 告知客户端:该槽位正在迁移,临时向 X 节点查询
槽位归属 槽位已完全属于新节点 槽位仍属于原节点(迁移未完成)
持久化 集群会更新槽位映射表(配置纪元递增) 不更新集群映射表(临时状态)
两者介绍
<font style="color:rgb(0, 0, 0);">MOVED</font> 错误

当 Redis Cluster 中的槽位完成永久迁移 后(如集群扩容 / 缩容时,槽位从节点 A 迁移到节点 B 并完成),若客户端仍向原节点 A 发送该槽位的请求,节点 A 会返回 <font style="color:rgba(0, 0, 0, 0.85) !important;">MOVED</font> 错误,格式如下:

plain 复制代码
MOVED <slot> <new_node_ip>:<new_node_port>

示例

客户端向节点 127.0.0.1:6379 发送属于槽位 10086 的命令,但该槽位已迁移到 127.0.0.1:6380,节点 6379 会返回:

plain 复制代码
MOVED 10086 127.0.0.1:6380
<font style="color:rgb(0, 0, 0);">ASK</font> 错误

当槽位正在迁移过程中(部分数据已从原节点 A 迁移到新节点 B,但未完全迁移完毕),若客户端向原节点 A 发送该槽位的请求:

  • 若数据仍在 A 中,A 直接返回结果;
  • 若数据已迁移到 B 中,A 会返回 <font style="color:rgb(0, 0, 0);">ASK</font> 错误,格式如下:
plain 复制代码
ASK <slot> <new_node_ip>:<new_node_port>

示例

槽位 10086 正在从 6379 迁移到 6380,客户端向 6379 查询已迁移到 6380 的 key,6379 会返回:

plain 复制代码
ASK 10086 127.0.0.1:6380
客户端处理方式
处理 <font style="color:rgb(0, 0, 0);">MOVED</font> 错误

<font style="color:rgba(0, 0, 0, 0.85) !important;">MOVED</font> 表示槽位已永久迁移,客户端需要更新本地的槽位映射表,并将后续请求直接发送到新节点:

  1. 解析 <font style="color:rgb(0, 0, 0);">MOVED</font> 错误中的新节点地址和槽位;
  2. 更新客户端本地缓存的「槽位 - 节点」映射关系(将该槽位绑定到新节点);
  3. 重新向新节点发送原命令。

注意 :客户端无需每次查询集群状态,只需通过 <font style="color:rgba(0, 0, 0, 0.85) !important;">MOVED</font> 错误逐步更新本地映射,最终与集群一致。

处理 <font style="color:rgb(0, 0, 0);">ASK</font> 错误

<font style="color:rgba(0, 0, 0, 0.85) !important;">ASK</font> 表示槽位临时迁移,客户端无需更新本地映射,只需临时向新节点查询

  1. 解析 <font style="color:rgb(0, 0, 0);">ASK</font> 错误中的临时节点地址;
  2. 先向该临时节点发送 <font style="color:rgb(0, 0, 0);">ASKING</font> 命令(告知节点允许处理该槽位的临时请求);
  3. 再向该临时节点重新发送原命令;
  4. 后续请求仍按本地原映射发送(因为槽位最终归属未变)。

示例流程

plain 复制代码
客户端 → 原节点(6379)请求 key → 收到 ASK 10086 6380  
客户端 → 向 6380 发送 ASKING 命令  
客户端 → 向 6380 重新发送原命令 → 得到结果
总结
  • <font style="color:rgb(0, 0, 0);">MOVED</font>永久重定向,客户端需更新本地槽位映射;
  • <font style="color:rgb(0, 0, 0);">ASK</font>临时重定向,客户端仅临时向新节点查询,不更新映射。

Redis Cluster 集群中,每个节点至少需要配置几个从节点才能保证高可用?

Redis Cluster 要求至少有 3 个主节点才能正常工作。为每个主节点配置 1 个从节点,即形成 3 主 3 从的结构,这样当某个主节点发生故障时,其对应的从节点可以自动升级为主节点,继续提供服务,从而保证集群的高可用性。

Redis Cluster 的 "槽位迁移" 是什么场景下触发的?迁移过程中数据是否可用?

在 Redis Cluster(Redis 集群)中,槽位(Slot)迁移是核心的动态扩容 / 缩容机制,用于在集群节点间重新分配数据存储的 "责任范围"。

Redis Cluster 将所有数据映射到 16384 个槽位(Slot 0 ~ 16383),每个主节点负责一部分槽位的读写。当集群的 "槽位 - 节点" 对应关系需要调整时,就会触发槽位迁移,具体场景包括:

  1. 集群扩容(新增主节点)
    1. 当原集群性能不足,需要新增主节点分担负载时,需将已有主节点的部分槽位迁移到新主节点,确保槽位均匀分配。
  2. 集群缩容(下线主节点)
    1. 当集群资源过剩或某个主节点需下线(如硬件故障、节点退役)时,需将该主节点负责的所有槽位迁移到其他正常主节点,再移除下线节点。
  3. 槽位均衡(手动触发)
    1. 若集群因异常(如节点故障后恢复、手动调整失误)导致槽位分配不均(如某主节点负责 10000 个槽位,其他节点仅负责 2000 个),可通过手动命令触发槽位迁移,实现负载均衡。
  4. 主节点故障后的简介间接迁移
    1. 当主节点故障且无可用从节点时(极端情况),若通过 <font style="color:rgba(0, 0, 0, 0.85);">CLUSTER FAILOVER FORCE</font> 等命令手动指定新主节点,需先将故障主节点的槽位迁移到新主节点,再完成故障转移。

槽位迁移过程中数据是否可用?

结论:槽位迁移过程中,数据完全可用,读写操作不会中断

Redis 采用 "增量迁移 + 原子化槽位切换" 的设计,确保迁移期间的可用性,具体流程如下:

:::info
移前的准备:标记 "迁移中槽位"

:::

发起迁移的节点(通常是 <font style="color:rgba(0, 0, 0, 0.85) !important;">redis-cli --cluster</font> 工具或管理节点)会先向源主节点 (待迁出槽位的主节点)发送 <font style="color:rgba(0, 0, 0, 0.85) !important;">CLUSTER SETSLOT <slot> MIGRATING <target-node-id></font> 命令,标记该槽位进入 "迁移中" 状态;

同时向目标主节点 (接收槽位的主节点)发送 <font style="color:rgba(0, 0, 0, 0.85) !important;">CLUSTER SETSLOT <slot> IMPORTING <source-node-id></font> 命令,标记该槽位进入 "接收中" 状态。

:::info
迁移过程:增量复制 + 分批迁移

:::

源主节点会将 "迁移中槽位" 的所有键值对分批(默认每批 16 个键)迁移到目标主节点,流程如下:

  1. 源主节点扫描该槽位的键,生成键列表;
  2. 向目标主节点发送 <font style="color:rgb(0, 0, 0);">MIGRATE</font> 命令,批量传输键值对(传输期间源节点仍可处理该槽位的读写);
  3. 目标主节点接收并存储键值对,完成后向源节点返回确认;
  4. 重复步骤 1~3,直到该槽位的所有键迁移完成。

Redis Cluster 支持跨节点的事务操作吗?为什么?

Redis Cluster(Redis 集群)不支持跨节点的事务操作,即无法在一个事务中操作位于不同节点的键。

核心原因:数据分片与事务原子性的冲突。

Redis Cluster 采用哈希槽(Hash Slot) 机制将数据分片存储:所有键通过哈希计算分配到 16384 个槽位,每个槽位由特定主节点负责,不同槽位可能分布在不同节点上。而 Redis 的事务(<font style="color:rgba(0, 0, 0, 0.85);">MULTI</font>/<font style="color:rgba(0, 0, 0, 0.85);">EXEC</font>)本质是 "单个节点上的原子操作序列",无法跨节点协调。

  1. 事务的单节点原子性限制(不同节点的命令队列相互独立),无法保证原子性
  2. 跨节点事务的难点(跨节点事务需要引入分布式事务 2PC两阶段提交)
  3. Cluster协议的路由限制

为什么要有哨兵?

为什么要有哨兵机制?

在 Redis 的主从架构中,由于主从模式是读写分离的,如果主节点(master)挂了,那么将没有主节点来服务客户端的写操作请求,也没有主节点给从节点(slave)进行数据同步了。

这时如果要恢复服务的话,需要人工介入,选择一个「从节点」切换为「主节点」,然后让其他从节点指向新的主节点,同时还需要通知上游那些连接 Redis 主节点的客户端,将其配置中的主节点 IP 地址更新为「新主节点」的 IP 地址。

这样也不太"智能"了,要是有一个节点能监控「主节点」的状态,当发现主节点挂了 ,它自动将一个「从节点」切换为「主节点」的话,那么可以节省我们很多事情啊!

Redis 在 2.8 版本以后提供的哨兵( Sentinel )机制 ,它的作用是实现主从节点故障转移。它会监测主节点是否存活,如果发现主节点挂了,它就会选举一个从节点切换为主节点,并且把新主节点的相关信息通知给从节点和客户端。

哨兵机制是如何工作的?

哨兵其实是一个运行在特殊模式下的 Redis 进程,所以它也是一个节点。从"哨兵"这个名字也可以看得出来,它相当于是"观察者节点",观察的对象是主从节点。

当然,它不仅仅是观察那么简单,在它观察到有异常的状况下,会做出一些"动作",来修复异常状态。

哨兵节点主要负责三件事情:监控、选主、通知

所以,我们重点要学习这三件事情:

  • 哨兵节点是如何监控节点的?又是如何判断主节点是否真的故障了?
  • 根据什么规则选择一个从节点切换为主节点?
  • 怎么把新主节点的相关信息通知给从节点和客户端呢?

如何判断主节点真的故障了?

哨兵会每隔 1 秒给所有主从节点发送 PING 命令,当主从节点收到 PING 命令后,会发送一个响应命令给哨兵,这样就可以判断它们是否在正常运行。

如果主节点或者从节点没有在规定的时间内响应哨兵的 PING 命令,哨兵就会将它们标记为「主观下线」。这个「规定的时间」是配置项 down-after-milliseconds 参数设定的,单位是毫秒。

:::info
主观下线?难道还有客观下线?

:::

是的没错,客观下线只适用于主节点。

之所以针对「主节点」设计「主观下线」和「客观下线」两个状态,是因为有可能「主节点」其实并没有故障,可能只是因为主节点的系统压力比较大或者网络发送了拥塞,导致主节点没有在规定时间内响应哨兵的 PING 命令。

所以,为了减少误判的情况,哨兵在部署的时候不会只部署一个节点,而是用多个节点部署成哨兵集群 (最少需要三台机器来部署哨兵集群),通过多个哨兵节点一起判断,就可以就可以避免单个哨兵因为自身网络状况不好,而误判主节点下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。

:::info
具体是怎么判定主节点为「客观下线」的呢?

:::

当一个哨兵判断主节点为「主观下线」后,就会向其他哨兵发起命令,其他哨兵收到这个命令后,就会根据自身和主节点的网络状况,做出赞成投票或者拒绝投票的响应。

当这个哨兵的赞同票数达到哨兵配置文件中的 quorum 配置项设定的值后,这时主节点就会被该哨兵标记为「客观下线」。

例如,现在有 3 个哨兵,quorum 配置的是 2,那么一个哨兵需要 2 张赞成票,就可以标记主节点为"客观下线"了。这 2 张赞成票包括哨兵自己的一张赞成票和另外两个哨兵的赞成票。

PS:quorum 的值一般设置为哨兵个数的二分之一加1,例如 3 个哨兵就设置 2。

哨兵判断完主节点客观下线后,哨兵就要开始在多个「从节点」中,选出一个从节点来做新主节点。

由哪个哨兵进行主从故障转移?

前面说过,为了更加"客观"的判断主节点故障了,一般不会只由单个哨兵的检测结果来判断,而是多个哨兵一起判断,这样可以减少误判概率,所以哨兵是以哨兵集群的方式存在的

问题来了,由哨兵集群中的哪个节点进行主从故障转移呢?

所以这时候,还需要在哨兵集群中选出一个 leader,让 leader 来执行主从切换。

选举 leader 的过程其实是一个投票的过程,在投票开始前,肯定得有个「候选者」。

那谁来作为候选者呢?

哪个哨兵节点判断主节点为「客观下线」,这个哨兵节点就是候选者,所谓的候选者就是想当 Leader 的哨兵。

举个例子,假设有三个哨兵。当哨兵 B 先判断到主节点「主观下线后」,就会给其他实例发送 is-master-down-by-addr 命令。接着,其他哨兵会根据自己和主节点的网络连接情况,做出赞成投票或者拒绝投票的响应。

当哨兵 B 收到赞成票数达到哨兵配置文件中的 quorum 配置项设定的值后,就会将主节点标记为「客观下线」,此时的哨兵 B 就是一个Leader 候选者。

候选者如何选举成为Leader?

候选者会向其他哨兵发送命令,表明希望成为 Leader 来执行主从切换,并让所有其他哨兵对它进行投票。

每个哨兵只有一次投票机会,如果用完后就不能参与投票了,可以投给自己或投给别人,但是只有候选者才能把票投给自己。

那么在投票过程中,任何一个「候选者」,要满足两个条件:

  • 第一,拿到半数以上的赞成票;
  • 第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值。

举个例子,假设哨兵节点有 3 个,quorum 设置为 2,那么任何一个想成为 Leader 的哨兵只要拿到 2 张赞成票,就可以选举成功了。如果没有满足条件,就需要重新进行选举。

这时候有的同学就会问了,如果某个时间点,刚好有两个哨兵节点判断到主节点为客观下线,那这时不就有两个候选者了?这时该如何决定谁是 Leader 呢?

每位候选者都会先给自己投一票,然后向其他哨兵发起投票请求。如果投票者先收到「候选者 A」的投票请求,就会先投票给它,如果投票者用完投票机会后,收到「候选者 B」的投票请求后,就会拒绝投票。这时,候选者 A 先满足了上面的那两个条件,所以「候选者 A」就会被选举为 Leader。

为什么哨兵节点至少要3个?

如果哨兵集群中只有 2 个哨兵节点,此时如果一个哨兵想要成功成为 Leader,必须获得 2 票,而不是 1 票。

所以,如果哨兵集群中有个哨兵挂掉了,那么就只剩一个哨兵了,如果这个哨兵想要成为 Leader,这时票数就没办法达到 2 票,就无法成功成为 Leader,这时是无法进行主从节点切换的。

因此,通常我们至少会配置 3 个哨兵节点。这时,如果哨兵集群中有个哨兵挂掉了,那么还剩下两个个哨兵,如果这个哨兵想要成为 Leader,这时还是有机会达到 2 票的,所以还是可以选举成功的,不会导致无法进行主从节点切换。

当然,你要问,如果 3 个哨兵节点,挂了 2 个怎么办?这个时候得人为介入了,或者增加多一点哨兵节点。

再说一个问题,Redis 1 主 4 从,5 个哨兵 ,quorum 设置为 3,如果 2 个哨兵故障,当主节点宕机时,哨兵能否判断主节点"客观下线"?主从能否自动切换?

  • 哨兵集群可以判定主节点"客观下线"。哨兵集群还剩下 3 个哨兵,当一个哨兵判断主节点"主观下线"后,询问另外 2 个哨兵后,有可能能拿到 3 张赞同票,这时就达到了 quorum 的值,因此,哨兵集群可以判定主节点为"客观下线"。
  • 哨兵集群可以完成主从切换。当有个哨兵标记主节点为「客观下线」后,就会进行选举 Leader 的过程,因为此时哨兵集群还剩下 3 个哨兵,那么还是可以拿到半数以上(5/2+1=3)的票,而且也达到了 quorum 值,满足了选举 Leader 的两个条件, 所以就能选举成功,因此哨兵集群可以完成主从切换。

如果 quorum 设置为 2 ,并且如果有 3 个哨兵故障的话。此时哨兵集群还是可以判定主节点为"客观下线",但是哨兵不能完成主从切换了,大家可以自己推演下。

如果 quorum 设置为 3,并且如果有 3 个哨兵故障的话,哨兵集群即不能判定主节点为"客观下线",也不能完成主从切换了。

可以看到,quorum 为 2 的时候,并且如果有 3 个哨兵故障的话,虽然可以判定主节点为"客观下线",但是不能完成主从切换,这样感觉「判定主节点为客观下线」这件事情白做了一样,既然这样,还不如不要做,quorum 为 3 的时候,就可以避免这种无用功。

所以,quorum 的值建议设置为哨兵个数的二分之一加1 ,例如 3 个哨兵就设置 2,5 个哨兵设置为 3,而且哨兵节点的数量应该是奇数

主从故障转移的过程是怎么的?

在哨兵集群中通过投票的方式,选举出了哨兵 leader 后,就可以进行主从故障转移的过程了,如下图:

主从故障转移操作包含以下四个步骤:

  • 第一步:选出新主节点
  • 第二步:让已下线主节点属下的所有「从节点」修改复制目标,修改为复制「新主节点」;
  • 第三步:将新主节点的 IP 地址和信息,通过「发布者/订阅者机制」通知给客户端;
  • 第四步:继续监视旧主节点,当这个旧主节点重新上线时,将它设置为新主节点的从节点;
步骤一:选出新主节点

故障转移操作第一步要做的就是在已下线主节点属下的所有「从节点」中,挑选出一个状态良好、数据完整的从节点,然后向这个「从节点」发送 SLAVEOF no one 命令,将这个「从节点」转换为「主节点」。

那么多「从节点」,到底选择哪个从节点作为新主节点的?

随机的方式好吗?随机的方式,实现起来很简单,但是如果选到一个网络状态不好的从节点作为新主节点,那么可能在将来不久又要做一次主从故障迁移。

所以,我们首先要把网络状态不好的从节点给过滤掉。首先把已经下线的从节点过滤掉,然后把以往网络连接状态不好的从节点也给过滤掉。

怎么判断从节点之前的网络连接状态不好呢?

Redis 有个叫 down-after-milliseconds * 10 配置项,其down-after-milliseconds 是主从节点断连的最大连接超时时间。如果在 down-after-milliseconds 毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连了。如果发生断连的次数超过了 10 次,就说明这个从节点的网络状况不好,不适合作为新主节点。

至此,我们就把网络状态不好的从节点过滤掉了,接下来要对所有从节点进行三轮考察:优先级、复制进度、ID 号。在进行每一轮考察的时候,哪个从节点优先胜出,就选择其作为新主节点。

  • 第一轮考察:哨兵首先会根据从节点的优先级来进行排序,优先级越小排名越靠前,
  • 第二轮考察:如果优先级相同,则查看复制的下标,哪个从「主节点」接收的复制数据多,哪个就靠前。
  • 第三轮考察:如果优先级和下标都相同,就选择从节点 ID 较小的那个。

:::info
第一轮考察:优先级最高的从节点胜出

:::

Redis 有个叫 slave-priority 配置项,可以给从节点设置优先级。

每一台从节点的服务器配置不一定是相同的,我们可以根据服务器性能配置来设置从节点的优先级。

比如,如果 「 A 从节点」的物理内存是所有从节点中最大的, 那么我们可以把「 A 从节点」的优先级设置成最高。这样当哨兵进行第一轮考虑的时候,优先级最高的 A 从节点就会优先胜出,于是就会成为新主节点。

:::info

第二轮考察:复制进度最靠前的从节点胜出

:::

如果在第一轮考察中,发现优先级最高的从节点有两个,那么就会进行第二轮考察,比较两个从节点哪个复制进度。

什么是复制进度?主从架构中,主节点会将写操作同步给从节点,在这个过程中,主节点会用 master_repl_offset 记录当前的最新写操作在 repl_backlog_buffer 中的位置(如下图中的「主服务器已经写入的数据」的位置),而从节点会用 slave_repl_offset 这个值记录当前的复制进度(如下图中的「从服务器要读的位置」的位置)。

如果某个从节点的 slave_repl_offset 最接近 master_repl_offset,说明它的复制进度是最靠前的,于是就可以将它选为新主节点。

:::info

第三轮考察:ID 号小的从节点胜出

:::

如果在第二轮考察中,发现有两个从节点优先级和复制进度都是一样的,那么就会进行第三轮考察,比较两个从节点的 ID 号,ID 号小的从节点胜出。

什么是 ID 号?每个从节点都有一个编号,这个编号就是 ID 号,是用来唯一标识从节点的。

到这里,选主的事情终于结束了。简单给大家总结下:

在选举出从节点后,哨兵 leader 向被选中的从节点发送 SLAVEOF no one 命令,让这个从节点解除从节点的身份,将其变为新主节点。

如下图,哨兵 leader 向被选中的从节点 server2 发送 SLAVEOF no one 命令,将该从节点升级为新主节点。

在发送 SLAVEOF no one 命令之后,哨兵 leader 会以每秒一次的频率向被升级的从节点发送 INFO 命令(没进行故障转移之前,INFO 命令的频率是每十秒一次),并观察命令回复中的角色信息,当被升级节点的角色信息从原来的 slave 变为 master 时,哨兵 leader 就知道被选中的从节点已经顺利升级为主节点了。

如下图,选中的从节点 server2 升级成了新主节点:

步骤二:将从节点指向新主节点

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

如下图,哨兵 leader 向所有从节点(server3和server4)发送 SLAVEOF ,让它们成为新主节点的从节点。

所有从节点指向新主节点后的拓扑图如下:

步骤三:通知客户的主节点已更换

经过前面一系列的操作后,哨兵集群终于完成主从切换的工作,那么新主节点的信息要如何通知给客户端呢?

这主要通过 Redis 的发布者/订阅者机制来实现的。每个哨兵节点提供发布者/订阅者机制,客户端可以从哨兵订阅消息。

哨兵提供的消息订阅频道有很多,不同频道包含了主从节点切换过程中的不同关键事件,几个常见的事件如下:

客户端和哨兵建立连接后,客户端会订阅哨兵提供的频道。主从切换完成后,哨兵就会向 +switch-master 频道发布新主节点的 IP 地址和端口的消息,这个时候客户端就可以收到这条信息,然后用这里面的新主节点的 IP 地址和端口进行通信了

通过发布者/订阅者机制机制,有了这些事件通知,客户端不仅可以在主从切换后得到新主节点的连接信息,还可以监控到主从节点切换过程中发生的各个重要事件。这样,客户端就可以知道主从切换进行到哪一步了,有助于了解切换进度。

将旧主节点切换为从节点

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

哨兵集群是如何组成的?

在我第一次搭建哨兵集群的时候,当时觉得很诧异。因为在配置哨兵的信息时,竟然只需要填下面这几个参数,设置主节点名字、主节点的 IP 地址和端口号以及 quorum 值。

powershell 复制代码
sentinel monitor <master-name> <ip> <redis-port> <quorum> 

不需要填其他哨兵节点的信息,我就好奇它们是如何感知对方的,又是如何组成哨兵集群的?

后面才了解到,哨兵节点之间是通过 Redis 的发布者/订阅者机制来相互发现的

在主从集群中,主节点上有一个名为sentinel:hello的频道,不同哨兵就是通过它来相互发现,实现互相通信的。

在下图中,哨兵 A 把自己的 IP 地址和端口的信息发布到__sentinel__:hello 频道上,哨兵 B 和 C 订阅了该频道。那么此时,哨兵 B 和 C 就可以从这个频道直接获取哨兵 A 的 IP 地址和端口号。然后,哨兵 B、C 可以和哨兵 A 建立网络连接。

通过这个方式,哨兵 B 和 C 也可以建立网络连接,这样一来,哨兵集群就形成了。

哨兵集群会对「从节点」的运行状态进行监控,那哨兵集群如何知道「从节点」的信息?

主节点知道所有「从节点」的信息,所以哨兵会每 10 秒一次的频率向主节点发送 INFO 命令来获取所有「从节点」的信息。

如下图所示,哨兵 B 给主节点发送 INFO 命令,主节点接受到这个命令后,就会把从节点列表返回给哨兵。接着,哨兵就可以根据从节点列表中的连接信息,和每个从节点建立连接,并在这个连接上持续地对从节点进行监控。哨兵 A 和 C 可以通过相同的方法和从节点建立连接。

正式通过 Redis 的发布者/订阅者机制,哨兵之间可以相互感知,然后组成集群,同时,哨兵又通过 INFO 命令,在主节点里获得了所有从节点连接信息,于是就能和从节点建立连接,并进行监控了。

哨兵集群中,为什么建议部署奇数个哨兵节点?

  1. 避免投票平局
    1. 哨兵集群在进行主节点故障检测以及新主节点选举等操作时,需要通过投票来决定。若节点数量为偶数,可能会出现投票平局的情况,导致无法达成共识,从而影响故障转移的进行。
  2. 提高故障转移效率
    1. 奇数个哨兵节点有助于更快地确定多数派,减少决策时间
  3. 资源利用更合理
    1. 在保证相同容错能力的情况下,奇数个节点比偶数个节点更节省资源。

哨兵的 "quorum" 参数有什么作用?如何设置合理的值?

<font style="color:#000000;">quorum</font> 定义了判定主节点 "客观下线(ODOWN)" 所需的最少哨兵节点数量<font style="color:#000000;">quorum</font> 是哨兵集群对 "主节点是否真的故障" 达成共识的 "最低票数门槛",目的是避免因单个哨兵的误判(如网络抖动)触发不必要的故障转移

:::info

如何设置合理的quorum值?

:::

<font style="color:#000000;">quorum = (哨兵总数 // 2) + 1</font>(多数派原则),

主从复制、哨兵、Cluster 三种架构分别适用于什么业务规模?

在 Redis 的三种核心架构(主从复制、哨兵、Cluster)中,其适用的业务规模差异主要由数据量、并发量、可用性要求、扩容需求四大核心因素决定。

主从复制

主从复制是 Redis 最基础的高可用架构,核心逻辑是1 个 Master 节点写入数据,N 个 Slave 节点同步 Master 数据并提供读服务,本身不具备 "自动故障转移" 能力(需手动切换 Master)。

  • 读写分离:Master 负责写操作,Slave 分担读操作,解决单节点 "读多写少" 场景下的性能瓶颈。
  • 数据冗余:Slave 节点是 Master 的副本,避免单点数据丢失风险(如 Master 宕机后可从 Slave 恢复数据)。
  • 无自动故障转移:Master 宕机后,需手动将某个 Slave 升级为新 Master,期间写服务不可用。
  • 横向扩展有限:Slave 节点数量建议不超过 5 个(过多 Slave 会增加 Master 的同步压力,导致延迟),且所有节点存储全量数据(无法突破单节点存储上限)。

适用于中小规模,允许短时间(分钟级)写服务不可用(如手动切换 Master 的时间),或可接受 "读服务正常、写服务暂停" 的临时状态。

哨兵模式(Sentinel)

哨兵架构是在 "主从复制" 基础上增加了哨兵节点(Sentinel Node) ,核心能力是监控主从节点状态、自动故障转移、通知客户端,解决了主从复制 "手动切换 Master" 的痛点。

  • 自动故障转移:当哨兵集群检测到 Master 宕机后,会自动选举 1 个 Slave 升级为新 Master,并更新其他 Slave 的同步目标,整个过程无需人工干预(通常秒级完成)。
  • 高可用保障:哨兵节点本身需部署 3 个及以上(避免哨兵单点故障),确保 "监控服务" 不中断。
  • 读写分离延续:保留主从复制的 "读多写少" 优化能力,Slave 仍可分担读压力。
  • 存储瓶颈未解决:所有节点(Master+Slave)仍存储全量数据,无法突破单节点的存储上限(如数据量达到 50GB,单节点内存无法承载)。

适用于中大规模业务,数据量20GB、并发量读1w~2w,可用性高。

应用场景:

  • 电商的用户购物车、订单列表(数据量中等,读并发高,写服务中断会直接影响用户体验)。
  • 在线教育的课程报名、学习进度存储(用户数据重要,需高可用,且读多写少)。
  • 支付系统的非核心记账数据(如交易流水查询,读并发高,需避免写服务中断导致数据丢失)。

Cluster(集群)

Cluster 是 Redis 官方推出的分布式集群架构 ,核心逻辑是将数据分片(Sharding)到多个 "主从节点组" ,每个主节点(Master)负责一部分哈希槽(共 16384 个槽),Slave 节点仅作为对应 Master 的副本,同时支持 "自动故障转移"。

  • 数据分片:突破单节点存储上限 ------ 数据按 "哈希槽" 分配到不同 Master,每个 Master 仅存储部分数据(如 3 个 Master 节点,每个负责~5461 个槽,总存储量可线性扩展)。
  • 并发扩展:写并发可线性提升 ------ 多个 Master 节点同时处理写请求(如 5 个 Master,写并发可提升至单节点的 5 倍左右)。
  • 自动故障转移:每个 Master 对应 1~N 个 Slave,当 Master 宕机时,其 Slave 会自动升级为新 Master,保障分片数据的可用性。
  • 复杂度较高:需管理哈希槽分配、跨节点数据迁移(如扩容时添加 Master 需重新分配槽)、客户端路由(客户端需支持 Cluster 协议,自动定位数据所在的 Master)。

适用于大规模/超大规模业务,数据量支持20GB以上,并发读支持5w以上,可用性极高。

总结

架构类型 适用数据量 适用并发量(读 / 写) 可用性(写服务) 核心痛点 典型业务规模
主从复制 ≤10GB(单节点) 读≤1 万 QPS / 写≤2000 QPS 低(手动切换,分钟级) 无自动故障转移 中小规模(初创公司、内部系统)
哨兵(Sentinel) ≤20GB(单节点) 读≤5 万 QPS / 写≤3000 QPS 高(自动切换,秒级) 无数据分片(存储瓶颈) 中大规模(成长型企业、核心业务)
Cluster(集群) ≥20GB(分布式) 读≥5 万 QPS / 写≥3000 QPS 极高(分片故障自动恢复) 架构复杂(槽管理、路由) 大规模 / 超大规模(头部企业、核心交易)