Redis主从复制

Redis在持久化方面采用RDB和AOF两个技术保证了即使在服务器重启的情况下也不会丢失数据。

但由于数据都是存储在一台服务器上,也有可能产生如下问题:

  • 如果服务器宕机,在数据恢复的这段时间内是无法提供服务的
  • 如果服务器的硬盘出现故障,数据就可能全部丢失

因此,可以将数据备份到其他服务器上,让这些服务器也对外提供服务,这样即使有一台出现了故障,其他服务器仍可以继续提供服务。

redisDuplicate

为了保证这些服务器之间的数据一致性,Redis提供了主从复制机制。主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。所有的数据修改只在主服务器上进行,然后将最新的数据同步给从服务器,这样就使得主从服务器的数据是一致的。

第一次同步

多台服务器如何确定主服务器和从服务器?

可以使用replicaof命令形成主从关系。例如,现有服务器A和服务器B,在服务器B上执行命令:

1
2
# on Server B:
replicaof <IP addr of Server A> <Redis Port of Server A>

这样,服务器B就会成为服务器A的从服务器,并与主服务器进行第一次同步。

主从之间第一次同步分为三个阶段:

  • 第一阶段,建立链接、协商同步
  • 第二阶段,主服务器同步数据给从服务器
  • 第三阶段,主服务器发送新的写命令给从服务器

redisSync

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

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

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

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

主服务器收到psync命令后,会用FULLRESYNC命令作为响应返回给从服务器。同样地,也会带有上述的两个参数,从服务器收到响应后,会记录下这两个值。

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

全量同步见第二阶段:

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

主服务器执行bgsave命令生产RDB文件,然后将RDB文件发送给从服务器。

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

但是,这期间若有新的写命令在主服务器执行,则不会写入RDB文件,因此主从服务器之间的数据就产生了不一致。

为了保证一致性,主服务器会在三个时间间隙中将收到的写命令,写入到replication buffer中:

  • 主服务器生成RDB期间
  • 主服务器发送RDB给从服务器期间
  • 从服务器加载RDB期间

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

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

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

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

命令传播

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

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

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

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

分摊主服务器的压力

在第一次同步的过程中,主服务器会做两件耗时的操作:生成RDB和传输RDB。

如果从服务器数量非常多,而且都与主服务器进行全量同步的话,就会产生两个问题:

  • 主服务器会忙于使用fork()创建用于bgsave的子进程,大量的fork()函数调用会阻塞主进程
  • 传输RDB会占用大量的主服务器带宽

redisSlave

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

增量复制

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

但如果主从服务器间的网络断开了,从服务器的数据就无法与主服务器继续保持一致,客户端就可能从从服务器处读到旧数据。

那么当网络恢复时,应如何继续保证主从服务器之间的数据一致性呢?

在Redis 2.8之前,会做一次主从之间的全量复制,但这样存在很多重复的不必要的开销;Redis 2.8之后,只做增量复制,即传播断开网络之后的写操作命令。

具体步骤为:

  • 网络恢复后,从服务器发送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_offsetslave_repl_offset之间的差距,来决定对从服务器执行哪种同步操作:

  • 如果判断出从服务器要读取的数据还在repl_backlog_buffer里,那么主服务器将采用增量同步的方式
  • 如果判断出从服务器要读取的数据不在repl_backlog_buffer里,那么主服务器将采用全量同步的方式

增量数据会被写入到之前提到的replication buffer中,然后发送给从服务器。

IncrementalRepl

repl_backlog_buffer缓冲区的默认大小是1M,并且是环形的,因此当缓冲区写满后,继续写入的话会覆盖之前的数据。因此,如果从服务器要读的数据已经被覆盖了,那么主服务器就会采用全量复制的方式进行同步。

因此,为了避免在网络恢复时,主服务器频繁使用全量同步的方式,应该调整repl_backlog_buffer的大小:
$$
size(repl_backlog_buffer) \geq second\ *\ write_size_per_sercond
$$
其中,second指的是断网后从服务器重新连接上主服务器所需的平均时间;write_size_per_second指的是主服务器每秒产生的平均写命令数据量大小。例如,如果主服务器平均每秒产生1MB的写命令,从服务器重连主服务器平均需要5秒,则repl_backlog_buffer最小大小为5MB。最后,考虑到保险起见,一般将其设置为此基础上的两倍。

一些问题

如何判断某个Redis节点是否正常工作?

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

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

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

主从复制模式中,过期的key如何处理?

主节点处理了一个key或者通过淘汰算法淘汰了一个key,则模拟一条del命令发送给从节点,从节点据此进行删除key的操作。

主从复制模式中,两个Buffer区(replication bufferrepl_backlog_buffer)有什么区别?

  • 出现阶段不同:
    • replication buffer在全量复制阶段和增量复制阶段均会出现,主节点会给每个新连接的从节点分配一个replication buffer
    • repl_backlog_buffer只在增量复制阶段出现,一个主节点只分配一个repl_backlog_buffer
  • 缓冲区满了之后不同:
    • replication buffer满了之后会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制
    • repl_backlog_buffer满了之后会覆盖起始位置的数据(环形结构)

如何解决主从之间数据不一致?

主从数据不一致的原因:

主从节点间的命令复制是异步进行的,所以无法实现强一致性保证(时时刻刻保持一致)

解决方法:

  • 尽量保证主从节点间的网络状况良好
  • 通过一个外部程序监控主从节点间的复制进度,具体为:
    • 监控程序通过Redis的INFO replication命令查到主、从节点的进度信息,然后用master_repl_offset减去slave_repl_offset,得到主从节点之间的复制进度差值
    • 如果某个从节点与主节点的进度差值超出了预设的阈值,则可以让客户端不再和该从节点进行数据读取