为保证redis高可用, 一般公司都会部署一个集群。为保证整个集群的数据一致性,主从复制就尤为重要。Redis的主从复制经历了两大版本:在Redis2.8之前的旧版复制SYNC,和2.8之后的新版复制PSYNC,旧版复制和新版复制也叫同步复制和异步复制。
旧版复制
从服务器收到来自客户端的SLAVEOF命令,向主服务器发送SYNC命令。主服务器收到之后执行BGSAVE命令,生成一个RDB文件,并且用一个缓冲区记录从当前开始的写命令。随后主服务器将RDB文件发送给从服务器,并且将缓冲区里的写命令同步给从服务器。
同步操作执行完之后,主从服务器之间依然存在可能不一致的情况。主服务器在自身命令执行完之后还会进行命令传播操作,将执行的写或删除命令同步给从服务器。
旧版复制主要的缺陷在于主从断开重连之后的同步过程效率很低。因为旧版复制都是全量的同步,不支持部分同步。正是由于这个缺陷,2.8之后引入了新版的复制。
新版复制
为了解决旧版复制断线重连之后复制低效的问题,Redis2.8以后用PSYNC命令代替了SYNC命令。PSYNC命令有完整重同步和部分重同步两个模式。完整重同步与旧版没有太大的区别。而部分重同步则支持主服务器在从服务器重新连接之后,将断线期间的写命令同步给从服务器。
部分重同步的实现主要是依赖主从服务器所记录的复制偏移量。主从双方都会维护一个复制偏移量 ,主服务器向从服务器传播数据时,在自己的复制偏移量上加上N个字节,从服务器收到同步的数据之后也在自己的复制偏移量上加上N个字节。如果从服务器集群中有一个断线了,那么他的复制偏移量将会和主服务器不一致。
复制偏移量不一致时,主从的数据同步依赖于一个复制积压缓冲区实现。缓冲区是一个固定大小为1M的FIFO队列。主服务器每次将要同步的写命令写入缓冲区。从服务器断线重连后,主服务器会判断从服务器的偏移量是否还在缓冲区内,如果还在那么执行部分同步,如果已经不在了那就只能全同步了。
除此之外,由于Redis的主服务器也有可能出现不可用,且Redis存在自动选举机制。所以主从服务器还会分别保存一个自己的服务器ID。从服务器断线重连后会判断连接的主服务器ID是不是断线之前的ID,如果不是,说明断线期间主服务器发生了变更,那么就只能进行全同步了。
复制的具体实现
从服务器向主服务器发送SLAVEOF命令,但是在发送之前,从服务器需要设置masterhost和masterport两个值,然后向客户端返回OK,代表从服务器已经开始接受并处理复制工作。随后从服务器与主服务器建立socket套接字,如果成功连接,那么从服务器将为这个套接字关联一个专门用于处理事件的文件事件处理器,而主服务器在成功接收命令之后,为套接字设置客户端状态。此时从服务器既是服务器,也是客户端。
随后从服务器将以客户端的身份向主服务器发送一个PING命令来检测套接字的读写是否正常。必须要保证从服务器接收到来自主服务器的PONG返回,才会进行下一步的操作。
如果从服务器开启了masterauth设置,则需要对主服务器进行身份验证,发送AUTH xxx选项值,并且主服务器需要开启requirepass选项,并返回验证成功。如果任何一个环节出了问题,例如主服务器没有开启这个选项,或者返回了错误的结果,那么复制工作就会停止。
如果验证顺利通过,从服务器发送REPLCONF listening-port <port-number>,向主服务器发送从服务器的监听端口号。主服务器接收到这个命令后,会将端口记录在从服务器所属的客户端状态的slave_listening_port中,这个属性的唯一作用就是在主服务器执行INFO replication命令时打印从服务器的端口号。。。
在执行PSYNC命令之后,主从服务器都是对方的客户端,这就保证了双方可以发送命令给对方执行,互相回复。随后主服务器就可以愉快的进行命令传播了,作为从服务器的客户端,给从服务器发送写命令。
心跳检测
在命令传播阶段,从服务器每隔1s会给主服务器发送 一个心跳命令:REPLCONF ACK <replication_offset>,在心跳里带上复制偏移量。主从服务器互相发送ACK来确认网络状态是否正常。例如写命令在网络传递中丢失,那么主服务器就会在心跳包中发现从服务器的复制偏移量小于自己的偏移量,那么主服务器就会根据从服务器当前的偏移量,从自己的缓冲区里找到对应的命令重新发送。
网友评论