Redis主从复制
Redis主从复制
Redis在持久化方面采用RDB和AOF两个技术保证了即使在服务器重启的情况下也不会丢失数据。
但由于数据都是存储在一台服务器上,也有可能产生如下问题:
- 如果服务器宕机,在数据恢复的这段时间内是无法提供服务的
- 如果服务器的硬盘出现故障,数据就可能全部丢失
因此,可以将数据备份到其他服务器上,让这些服务器也对外提供服务,这样即使有一台出现了故障,其他服务器仍可以继续提供服务。
为了保证这些服务器之间的数据一致性,Redis提供了主从复制机制。主服务器可以进行读写操作,当发生写操作时自动将写操作同步给从服务器,而从服务器一般是只读,并接受主服务器同步过来写操作命令,然后执行这条命令。所有的数据修改只在主服务器上进行,然后将最新的数据同步给从服务器,这样就使得主从服务器的数据是一致的。
第一次同步
多台服务器如何确定主服务器和从服务器?
可以使用replicaof
命令形成主从关系。例如,现有服务器A和服务器B,在服务器B上执行命令:
1 | # on Server B: |
这样,服务器B就会成为服务器A的从服务器,并与主服务器进行第一次同步。
主从之间第一次同步分为三个阶段:
- 第一阶段,建立链接、协商同步
- 第二阶段,主服务器同步数据给从服务器
- 第三阶段,主服务器发送新的写命令给从服务器
第一阶段:建立链接,协商同步
执行了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会占用大量的主服务器带宽
通过上图这种分级的方式,主服务器生成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_offset
和slave_repl_offset
之间的差距,来决定对从服务器执行哪种同步操作:
- 如果判断出从服务器要读取的数据还在
repl_backlog_buffer
里,那么主服务器将采用增量同步的方式 - 如果判断出从服务器要读取的数据不在
repl_backlog_buffer
里,那么主服务器将采用全量同步的方式
增量数据会被写入到之前提到的replication buffer
中,然后发送给从服务器。
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 buffer
和repl_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
,得到主从节点之间的复制进度差值 - 如果某个从节点与主节点的进度差值超出了预设的阈值,则可以让客户端不再和该从节点进行数据读取
- 监控程序通过Redis的