Redis主从复制
简介
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);
数据的复制是单向的,只能由主节点到从节点。
- 为了要避免单点故障后,数据库依旧能正常使用,即保证高可用,便需要多结点方式提供集群服务。
- 而Redis 提供了主从库模式,以保证数据副本的一致,主从库之间采用的是读写分离的方式。
作用
主从复制的作用主要包括:
- 数据备份:主从复制实现了数据的热备份,是持久化之外的一种数据备份方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
读写分离
主从库之间采用的是读写分离的方式。
- 读操作:主库、从库都可以接收;
建立主从关系
例如,现在有实例 1(ip:172.16.19.3)和实例 2(ip:172.16.19.5),我们在实例 2 上执行以下这个命令后,实例 2 就变成了实例 1 的从库,并从实例 1 上复制数据:
replicaof 172.16.19.3 6379
传播命令
区分repl__buffer 和 repl_backlog_buffer
当主服务器进行命令传播的时候,maser不仅将所有的数据更新命令发送到所有slave的replication buffer,还会写入replication backlog
- 一个
replication buffer
对应于一个slave replication backlog buffer
是一个环形缓冲区,整个master进程中只会存在一个,所有的slave公用。
主从复制原理
注意:在2.8版本之前只有全量复制,而2.8版本后有全量和增量复制:
全量(同步)复制
:用于初次复制或其它无法进行部分复制的情况,将主节点中的所有数据都发送给从节点。当数据量过大的时候,会造成很大的网络开销。增量(同步)复制
:**只会把主从库网络断连期间主库收到写的命令,同步给从库。**补发的数据远远小于全量数据,可以有效避免全量复制的过高开销。
主节点运行ID(runId)
是每个 Redis 节点启动时都会自动生成的一个随机 ID
-
需要注意的是redis关闭再启动,运行的id会随之变化
-
当从库和主库第一次复制时,因为不知道主库的 runID,所以将 runID 设为“?”。offset,此时设为 -1,表示第一次复制
复制偏移量(offset)
主节点和从节点都各自维护自己的主从复制偏移量offset,当主节点有写入命令时,offset=offset+命令的字节长度。从节点在收到主节点发送的命令后,也会增加自己的offset,并把自己的offset发送给主节点。这样,主节点同时保存自己的offset和从节点的offset,通过对比offset来判断主从节点数据是否一致。
复制积压缓冲区(repl_backlog_buf)
在2.8版本,redis使用了新的复制方式,引入了复制积压缓冲(replication backlog)
- 复制积压缓冲区是保存在主节点上的一个固定长度的队列,默认大小为1MB,当主节点有连接的从节点时被创建,这时主节点响应写命令时,不但会把命令发给从节点,还会写入复制积压缓冲区。
- 除了存储写命令,还存储了写命令的每个字节对应的复制偏移量(offset) 。由于复制积压缓冲区定长且先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。
全量复制
形成主库和从库的关系,之后会按照三个阶段完成数据的第一次同步
全量复制的三个阶段
第一阶段是主从库间建立连接、协商同步的过程,
主要是为全量复制做准备。在这一步,从库和主库建立起连接,并告诉主库即将进行同步,主库确认回复后,主从库间就可以开始同步了。
具体来说,从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID 和复制进度 offset 两个参数。主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。这里有个地方需要注意,FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库
第二阶段,主库将所有数据同步给从库。
从库收到数据后,在本地完成数据加载。这个过程**依赖于内存快照生成的 RDB 文件。**
- 主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库
- 从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。
- 在主库将数据同步给从库的过程中,主库不会被阻塞,仍然可以正常接收请求。否则,Redis 的服务就被中断了。
- 为了保证主从库的数据一致性,主库会在内存中用专门的 replication
buffer
,记录 RDB 文件生成后收到的所有写操作。
第三个阶段,主库会把第二阶段执行过程中新收到的写命令,再发送给从库。
具体的操作是,当主库完成 RDB 文件发送后,就会把此时 replication buffer
中的修改操作发给从库,从库再重新执行,最终实现主从库同步
增量复制
在 Redis 2.8 版本引入了增量复制,从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。
❓为什么会设计增量复制
如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。
增量复制的流程
你可以先看一下下面这张图,有个整体感知,接下来我再具体介绍。
-
如果网络抖动(连接断开 connection lost)
-
主机master 还是会写 replbackbuffer(复制缓冲区)
-
从机slave 会继续尝试连接主机
-
从机slave 会把自己当前 runid 和偏移量传输给主机 master,并且执行 pysnc 命令同步
-
如果 master 发现你的偏移量是在缓冲区的范围内,就会返回 continue 命令,表示可以增量复制
-
根据offset把复制积压缓冲区里的数据发送给从节点,所以部分复制的基础就是偏移量 offset
❓如果在网络断开后,判断进行全量复制还是增量复制?
在和主库重连进行恢复时,从库会通过psync命令把自己记录的slave_repl_offset发给主库,从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制
如果主库repl_backlog_buffer
的从节点偏移量offset位置上的数据已经被覆盖掉了,就需要进行全量复制
深入理解
为什么主从全量复制使用RDB而不使用AOF?
- 内存占用小:RDB是经过压缩的二进制数据,而AOF文件记录的是每一次写操作的命令,写操作越多文件会变得很大,其中还包括很多对同一个key的多次冗余操作。
- 速度快:从库直接按照RDB协议解析还原数据即可,速度会非常快,而AOF需要依次执行每个写命令,恢复速度相比RDB会慢得多,
为什么还会有从库的从库的设计?
通过分析主从库间第一次数据同步的过程,你可以看到,一次全量复制中,对于主库来说,需要完成两个耗时的操作:生成 RDB 文件和传输 RDB 文件。
- 从库数量多,主库会fork操作频繁,阻塞主线程
- 传输 RDB 文件也会占用主库的网络带宽
从库数量多,主库的压力会很大
可以使用**“主 - 从 - 从”模式** 将主库生成 RDB 和传输 RDB 的压力,以级联的方式分散到从库上。
简单来说,我们在部署主从集群的时候,可以手动选择一个从库(比如选择内存资源配置较高的从库),用于级联其他的从库。然后再选择一些从库(例如三分之一的从库),让它们和刚才所选的从库,建立起主从关系。
replicaof 所选从库的IP 6379
这三分之一的从库就不用再和主库进行交互了,只要和级联的从库进行写操作同步就行了,这就可以减轻主库上的压力,如下图所示:
读写分离及其中的问题
在主从复制基础上实现的读写分离,可以实现Redis的读负载均衡:由主节点提供写服务,由一个或多个从节点提供读服务;在读负载较大的应用场景下,可以大大提高Redis服务器的并发量。
下面介绍在使用Redis读写分离时,需要注意的问题。
延迟与不一致问题
由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。
- 优化主从节点之间的网络环境(如在同机房部署);
- 监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;
- 使用集群同时扩展写负载和读负载等。
数据过期问题
在主从复制场景下,为了数据一致性,从节点不会主动删除数据,而是由主节点控制从节点中过期数据的删除。但是由于主节点的删除策略,对过期数据执行删除操作,从节点很容易读取到已经过期的数据。
在单机版Redis中,存在两种删除策略:
惰性删除
:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。(和超市一样,顾客买到过期的才换)定期删除
:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。
Redis 3.2中,从节点在读取数据时,增加了对数据是否过期的判断:如果该数据已过期,则不返回给客户端;将Redis升级到3.2可以解决数据过期问题。
故障切换问题
在没有使用哨兵的读写分离场景下,当主节点或从节点出现问题而发生更改时,需要及时修改应用程序读写Redis数据的连接,成本都不算低
- 连接的切换可以手动进行,响应慢、容易出错,或者
- 自己写监控程序进行切换,实现复杂
总结
在使用读写分离之前,可以考虑其他方法增加Redis的读负载能力:
- 如尽量优化主节点(减少慢查询、减少持久化等其他情况带来的阻塞等)提高负载能力;
- 使用Redis集群同时提高读负载能力和写负载能力等。
- 如果使用读写分离,可以使用哨兵,使主从节点的故障切换尽可能自动化,并减少对应用程序的侵入。