主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。(侧面说明了只有主机能写,而从机只能读)
主从复制的作用
- 备份数据:主从复制能够提供数据的热备份,是持久化操作之外的一个数据冗余方式
- 故障恢复:当主节点出现故障后,可以由从节点提供服务,实现快速的故障恢复;
- 负载均衡:写少读多的场景下,将读写分离到不同的服务器上,主机只负责写操作,降低服务器压力
- 高可用:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
主从复制机制工作流程
主从复制机制共有三个步骤
- 建立连接
- 数据同步
- 命令传播
建立连接
数据同步
- 在从机初次连接主机后,复制主机中的所有数据到从机
- 将从机的数据库状态更新成主机当前的数据库状态
第一次连接使用的全量复制
如何判断是第一次连接?
psync 命令包含了主库的 runID 和复制进度 offset 两个参数。runID,是每个 Redis 实例启动时都会自动生成的一个随机 ID,用来唯一标记这个实例。当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。offset,此时设为 -1,表示第一次复制。
命令传播
当数据同步完成后,主机收到写操作请求状态被修改后,主从状态不一致,这时需要让主从数据同步到一致,同步的动作就是命令传播
主机将接收到的命令传播数据发送给主机,主机接收到命令后执行
命令传播
当命令传播阶段网络断开
就可能会触发增量复制机制
增量复制机制触发条件
主从机不是第一次连接,环形缓存未被写满
实现原理
主从复制共有两种实现方式,redis目前是将两种方式混合使用
全量复制:将所有数据快照全部都同步给从库,使用RDB(数据快照)方式
增量复制:只会把增量命令同步给从库
全量复制
主从建立连接后,从机会先将自己本地的数据进行删除(从机之前可能有数据),之后接收主机的RDB文件进行数据加载。
同步过程中(RDB文件写入与读取过程中)若又有行数据写入主机,则主机会将这些命令写入repl buffer 缓存中
等同步完成后在将这些命令发送给从机
增量复制
主从断开后,主机的命令会写入环形缓存repl backlog中,当连接恢复,这些命令就会发送给从机用于同步数据
- 怎么知道从机是之前连接过的?
还是runID与offset参数,之前连接过的从机在psync命令时会带有主机的runID与自己的offset(同步进度)
主机接收到后就会将offset之后的命令发送给从机
- 如果在网络断开期间,repl_backlog_size环形缓冲区写满之后,从库是会丢失掉那部分被覆盖掉的数据,还是直接进行全量复制呢?
- 一个从库如果和主库断连时间过长,造成它在主库repl_backlog_buffer的slave_repl_offset位置上的数据已经被覆盖掉了(从机的进度位置被覆盖了),此时从库和主库间将进行全量复制。
- 每个从库会记录自己的slave_repl_offset,每个从库的复制进度也不一定相同。在和主库重连进行恢复时,从库会通过psync命令把自己记录的slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。
增量复制的三个核心要素
- 服务器的运行id(run id)
- 主服务器的复制积压缓冲区
- 主从服务器的复制偏移量
为什么使用RDB而不是AOF做全盘复制
- 文件大小:RDB是压缩过的二进制数据,文件很小,而AOF文件记录的是命令,写操作越多,文件越大。
- 恢复时间:对于RDB文件数据,从库只需要按照协议解析还原数据即可,速度较快,而AOF则是需要重放每个命令,过程很长
- 场景:RDB是默认开启的们AOF需要根据情况打开,在某些不适用AOF的场景,RDB模式依然能支持主从复制。
为什么还有从库的从库的设计
在一次全盘复制中,对于主库来说,耗时的有两个操作**,生成RDB文件与传输RDB文件。**
而如果从库数量很多,都要与主库进行全盘复制的话,主线程就会忙于fork子线程生成RDB文件,而fork会阻塞主线程。同时,大量的RDB文件传输也会占用主库网络带宽
于是就有了主-从-从的设计模式
主从从模式就可以将生成RDB文件与传输RDB文件的压力级联的分散到从库中去(主库只需要同步从库,而从从库靠的是从库的数据进行复制)
上图共有了四个从库,但是主库只需要负担两个从库的同步压力
数据过期问题
在单机版Redis中,存在两种删除策略:
- 惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
- 定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。
在主从复制场景下,为了主从节点的数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。由于主节点的惰性删除和定期删除策略,都不能保证主节点及时对过期数据执行删除操作,因此,当客户端通过Redis从节点读取数据时,很容易读取到已经过期的数据。
Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。
故障切换问题
在没有使用哨兵的读写分离场景下,应用针对读和写分别连接不同的Redis节点;
当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接;
连接的切换可以手动进行,或者自己写监控程序进行切换,但前者响应慢、容易出错,后者实现复杂,成本都不算低。
总结
在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:
如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;
使用Redis集群同时提高读负载能力和写负载能力等。如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。