AOFRDB 持久化技术保证了即使在服务器重启的情况下也不会丢失数据(或少量损失)

不过,可能出现单点故障:

  • 如果服务器宕机,数据恢复需要时间,这个期间无法服务新的请求
  • 如果这台服务器硬盘出现故障,数据可能都丢失

避免单点故障的方法就是集群,集群需要解决的问题:

  • 多台服务器要保存同一份数据,服务器之间的数据如何保持一致性?
  • 数据的读写操作是否每台服务器都可以处理?

Redis 提供了主从复制模式

  • 可以保证多台服务器的数据一致性,且主从服务器之间采用「读写分离」的方式
  • 主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器;而从服务器一般是只读,并接受主服务器同步过来写操作命令并执行

第一次同步

使用 replicaof(Redis 5.0 之前使用 slaveof)命令形成主服务器和从服务器的关系:

# 从服务器执行
replicaof <主服务器的 IP > <主服务器的 Redis 端口>

这样就建立了主从关系,然后从服务器与主服务器进行第一次同步

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

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

psync <主服务器的 runID> <复制进度 offset>

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

主服务器收到 psync 命令后,会用 FULLRESYNC(表示全量复制)作为响应命令返回给对方:

FULLRESYNC <主服务器的 runID> <复制进度 offset>

从服务器收到响应后,会记录这两个值,并准备接收主服务器的全量数据

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

  • 接着,主服务器会执行 bgsave 命令(子进程工作)来生成 RDB 文件,然后把文件发送给从服务器
  • 从服务器收到 RDB 文件后,会先清空当前的数据,然后载入 RDB 文件

主服务器生成 RDB 文件是子进程完成的,不会阻塞主线程

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

为了保证主从服务器的数据一致性,主服务器将新收到的写操作命令,写入到 replication buffer(复制缓存区)中

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

从服务器完成 RDB 载入后,会回复一个确认消息给主服务器

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

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

命令传播

主从服务器在完成第一次同步后,双方之间就会维护一个 TCP 连接(长连接,目的是避免频繁的 TCP 连接和断开带来的性能开销)

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

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

增量复制

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

可是,网络可能出现波动,导致主从服务器间无法进行命令传播,导致数据不一致。之后当断开的网络恢复正常,要怎么继续保证主从服务器的数据一致性呢?

  • Redis 2.8 之前:从服务器会和主服务器重新进行一次全量复制(开销太大)
  • Redis 2.8 之后:采用增量复制的方式继续同步,就是只会把网络断开期间主服务器收到的写操作命令,同步给从服务器

主要有三个步骤:

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

主服务器怎么知道要将哪些增量数据发送给从服务器呢?

  • repl_backlog_buffer:一个「环形」缓冲区,保存着最近传播的写命令
  • replication offset:标记 repl_backlog_buffer 缓冲区的同步进度,主从服务器都有各自的偏移量
    • 主服务器使用 master_repl_offset 记录自己「写」到的位置
    • 从服务器使用 slave_repl_offset 记录自己「读」到的位置

在主服务器进行命令传播时,不仅会将写命令发送给从服务器,还会将写命令写入到 repl_backlog_buffer 缓冲区

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

  • 如果从服务器要读取的数据还在 repl_backlog_buffer 缓冲区中,采用增量同步的方式
  • 如果从服务器要读取的数据已经不存在 repl_backlog_buffer 缓冲区中,采用全量同步的方式

增量同步的方式:主服务器在 repl_backlog_buffer 中找到差异(增量)数据后,将增量数据写入到 replication buffer 缓冲区,发给从服务器

repl_backlog_buffer 缓冲区的默认大小是 1M,它是一个环形缓冲区,当写满后,继续写入会覆盖最早的数据

为了避免在网络恢复时,主服务器频繁地使用全量同步的方式,应该调整 repl_backlog_buffer 缓冲区大小

建议不低于:second * write_size_per_second

  • second:从服务器断线后重接主服务器所需的平均秒数
  • write_size_per_second:主服务器平均每秒产生的写命令数据量大小

当然,为了应对一些突发情况,可以尽量设置的更大(如:2 倍)

修改方式:

// redis.conf
repl-backlog-size 1mb

分摊主服务器的压力

主从服务器在第一次数据同步时,主服务器会做两件耗时的操作:生成 RDB 文件和传输 RDB 文件

当从服务器数量非常多,而且都与主服务器进行全量同步的话,主服务器的压力就很大(硬件资源、网络带宽、执行 fork() 阻塞主线程)

解决方法是:不必所有的从服务器都和主服务器全量同步,而且从服务器可以有自己的从服务器,它不仅可以接收主服务器的同步数据,自己也可以同时作为主服务器的形式将数据同步给从服务器

通过这种方式,主服务器压力可以分摊到一些从服务器上

具体做法:从服务器不 replicaof 主服务器,而是 replicaof 另一个充当中转的从服务器:

// 从服务器执行
replicaof <中转的从服务器 IP > <中转的从服务器 Redis 端口>

总结

主从复制共有三种模式:全量复制、基于长连接的命令传播、增量复制

  • 主从服务器第一次同步时,采用全量复制
  • 第一次同步完成后,主从服务器会维护一个长连接,主服务器在接收到写操作命令后,就会通过这个连接将写命令传播给从服务器,来保证主从服务器的数据一致性
  • 如果遇到网络波动导致连接断开,网络恢复后需要保证数据一致性
    • 如果从服务器需要的数据已经被覆盖,采用全量复制
    • 服务器需要的数据还在缓存区,采用增量复制

所有从服务器都与一个主服务器联系的话,主服务器压力较大,可以将部分从服务器当作中转服务器,来分担主服务器的工作

面试题

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

通过互相的 ping-pong 心态检测机制,如果有一半以上的节点去 ping 一个节点都没有 pong 回应,集群就会认为这个节点挂掉了,会断开与这个节点的连接

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

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

主节点通过淘汰算法淘汰了一个 key 时,主节点==模拟一条 del 命令==发送给从节点,从节点收到该命令后,进行删除操作

主从复制是同步复制还是异步复制?

主节点每次收到写命令后,先写到缓冲区,然后异步发送给从节点

主从复制中两个 Buffer 的区别?

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

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

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

因为主从节点间的命令复制是异步进行的,所以无法保证强一致性

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

  1. 尽量保证主从节点间的网络良好(如:避免主从节点在不同的机房)
  2. 可以开发一个外部程序来监控主从节点间的复制进度。具体做法:
    • Redis 的 INFO replication 命令可以查看主节点接收写命令的进度信息(master_repl_offset)和从节点复制写命令的进度信息(slave_repl_offset
    • 开发一个监控程序,先查主、从节点的进度,通过 master_repl_offset - slave_repl_offset 得到复制进度差值
    • 如果某个从节点的进度差值大于阈值,可以让客户端不再和这个节点连接进行数据读取,这样可以减少读到不一致数据的情况
    • 不过,为了避免出现客户端和所有从节点都不能连接的情况,需要把阈值设置得合理一些