AOF 和 RDB 持久化技术保证了即使在服务器重启的情况下也不会丢失数据(或少量损失)
不过,可能出现单点故障:
- 如果服务器宕机,数据恢复需要时间,这个期间无法服务新的请求
- 如果这台服务器硬盘出现故障,数据可能都丢失
避免单点故障的方法就是集群,集群需要解决的问题:
- 多台服务器要保存同一份数据,服务器之间的数据如何保持一致性?
- 数据的读写操作是否每台服务器都可以处理?
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 满了,会导致连接断开,删除缓存,从节点重新连接,重新开始全量复制
如何应对主从数据不一致?
为什么会出现主从数据不一致?
因为主从节点间的命令复制是异步进行的,所以无法保证强一致性
如何应对主从数据不一致?
- 尽量保证主从节点间的网络良好(如:避免主从节点在不同的机房)
- 可以开发一个外部程序来监控主从节点间的复制进度。具体做法:
- Redis 的 INFO replication 命令可以查看主节点接收写命令的进度信息(
master_repl_offset
)和从节点复制写命令的进度信息(slave_repl_offset
) - 开发一个监控程序,先查主、从节点的进度,通过
master_repl_offset - slave_repl_offset
得到复制进度差值 - 如果某个从节点的进度差值大于阈值,可以让客户端不再和这个节点连接进行数据读取,这样可以减少读到不一致数据的情况
- 不过,为了避免出现客户端和所有从节点都不能连接的情况,需要把阈值设置得合理一些
- Redis 的 INFO replication 命令可以查看主节点接收写命令的进度信息(