主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。(侧面说明了只有主机能写,而从机只能读)

redis的主从复制 redis主从复制和redis集群的区别_database

主从复制的作用

  1. 备份数据:主从复制能够提供数据的热备份,是持久化操作之外的一个数据冗余方式
  2. 故障恢复:当主节点出现故障后,可以由从节点提供服务,实现快速的故障恢复;
  3. 负载均衡:写少读多的场景下,将读写分离到不同的服务器上,主机只负责写操作,降低服务器压力
  4. 高可用:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。


主从复制机制工作流程

主从复制机制共有三个步骤

  1. 建立连接
  2. 数据同步
  3. 命令传播

建立连接

redis的主从复制 redis主从复制和redis集群的区别_redis的主从复制_02

数据同步

  • 在从机初次连接主机后,复制主机中的所有数据到从机
  • 将从机的数据库状态更新成主机当前的数据库状态

第一次连接使用的全量复制

如何判断是第一次连接?

psync 命令包含了主库的 runID 和复制进度 offset 两个参数。runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。offset,此时设为 -1,表示第一次复制。

命令传播

当数据同步完成后,主机收到写操作请求状态被修改后,主从状态不一致,这时需要让主从数据同步到一致,同步的动作就是命令传播
主机将接收到的命令传播数据发送给主机,主机接收到命令后执行
命令传播
当命令传播阶段网络断开
就可能会触发增量复制机制

增量复制机制触发条件

主从机不是第一次连接,环形缓存未被写满

实现原理

主从复制共有两种实现方式,redis目前是将两种方式混合使用
全量复制:将所有数据快照全部都同步给从库,使用RDB(数据快照)方式
增量复制:只会把增量命令同步给从库

全量复制

redis的主从复制 redis主从复制和redis集群的区别_数据库_03


主从建立连接后,从机会先将自己本地的数据进行删除(从机之前可能有数据),之后接收主机的RDB文件进行数据加载。

同步过程中(RDB文件写入与读取过程中)若又有行数据写入主机,则主机会将这些命令写入repl buffer 缓存中

等同步完成后在将这些命令发送给从机

增量复制

redis的主从复制 redis主从复制和redis集群的区别_redis_04

主从断开后,主机的命令会写入环形缓存repl backlog中,当连接恢复,这些命令就会发送给从机用于同步数据

  • 怎么知道从机是之前连接过的?

还是runID与offset参数,之前连接过的从机在psync命令时会带有主机的runID与自己的offset(同步进度)
主机接收到后就会将offset之后的命令发送给从机

  • 如果在网络断开期间,repl_backlog_size环形缓冲区写满之后,从库是会丢失掉那部分被覆盖掉的数据,还是直接进行全量复制呢
  1. 一个从库如果和主库断连时间过长,造成它在主库repl_backlog_buffer的slave_repl_offset位置上的数据已经被覆盖掉了(从机的进度位置被覆盖了),此时从库和主库间将进行全量复制。
  2. 每个从库会记录自己的slave_repl_offset,每个从库的复制进度也不一定相同。在和主库重连进行恢复时,从库会通过psync命令把自己记录的slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。

增量复制的三个核心要素

  1. 服务器的运行id(run id)
  2. 主服务器的复制积压缓冲区
  3. 主从服务器的复制偏移量

为什么使用RDB而不是AOF做全盘复制

  1. 文件大小:RDB是压缩过的二进制数据,文件很小,而AOF文件记录的是命令,写操作越多,文件越大。
  2. 恢复时间:对于RDB文件数据,从库只需要按照协议解析还原数据即可,速度较快,而AOF则是需要重放每个命令,过程很长
  3. 场景:RDB是默认开启的们AOF需要根据情况打开,在某些不适用AOF的场景,RDB模式依然能支持主从复制。

为什么还有从库的从库的设计

在一次全盘复制中,对于主库来说,耗时的有两个操作**,生成RDB文件与传输RDB文件。**

而如果从库数量很多,都要与主库进行全盘复制的话,主线程就会忙于fork子线程生成RDB文件,而fork会阻塞主线程。同时,大量的RDB文件传输也会占用主库网络带宽

于是就有了主-从-从的设计模式

主从从模式就可以将生成RDB文件与传输RDB文件的压力级联的分散到从库中去(主库只需要同步从库,而从从库靠的是从库的数据进行复制)

redis的主从复制 redis主从复制和redis集群的区别_redis的主从复制_05


上图共有了四个从库,但是主库只需要负担两个从库的同步压力

数据过期问题

在单机版Redis中,存在两种删除策略:

  • 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
  • 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。


在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。

Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。

故障切换问题

在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;
当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;
连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。

总结


在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:
如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;
使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。