vlambda博客
学习文章列表

Redis学习(五)主从复制

之前的文章中学习了redis提供的数据类型、请求流程、键空间、数据的持久化,也是redis单服务的整个流程。这篇开始来学习redis的部署方式主从、哨兵、集群模模式以及其实现思路。

        其实现在很多中间件都提供了主从复制功,主从复制功能带来的好处一是为了数据备份、二是为了分担master服务压力如实现读写分离,三是为后续集群的高可用做了基础铺垫。


一、MySQL主从复制


1、MySQL的主从复制逻辑

(1)、配置MySQL主服务器信息

(2)、MySQL从库启动一个I/O线程一个SQL线程

(3)、MySQL从库I/O线程发送连接请求给MySQL主库

(4)、主库进行权限校验

(5)、主库判断从库是否已建立binlog事件dump线程,如果发现已经有从库对应的binlog dump线程则会把之前binlog dump线程杀死,避免一个从库出现多个binlog dump 线程。

(6)、主库返回连接成功给存库,存库接收到主库连接信息,这样主从socket连接成功建立

(7)、从库发送数据同步请求给主库,包括binlog日志复制偏移量offset

(8)、主控发送binlong日志事件给从库,主库从库之间的数据同步是通过主库的dump线程和从库的I/O线程完成

(8)、当从库接收到主库同步的binlog事件后通过从库的I/O线程会将binlog 二进制日志写到从库的中继日志文件中,并改变主从复制偏移量offset

(9)、从库的SQL线程就是解析中继日志并执行,这样从库就有和主库相同的数据了,每当解析执行之后都会记录中继日志偏移量offset


2、MySQL复制方式

(1)、通过上面复制过程逻辑可以发现MySQL的主从复制是增量复制。因为每当MySQL执行一个事务只有当事务提交时才会将binlog日志写入binlog日志缓冲区所以从库的数据同步是通过主库增量实现的。

(2)、主从复制之前主库积累的数据也会通过dump线程、I/O线程和SQL线程来实现数据复制。

(3)、主从复制延迟出现的原因,因为主库这边是多线程执行而从库的I/O线程、SQL解析线程都是单线程、还有一个就是网络问题。


在Redis的主从复制中你会发现和MySQL有很多相似之处也有很多不同之处,因为MySQL中的数据是静态的不会过期而Redis中的数据是动态的他有过期问题所以还是有很多不同之处。


二、Redis主从复制


        相比上面的MySQL复制流程感觉Redis中多了很多复制方式、比如RDB生成文件并全量复制、不生成RDB文件全量复制、部分复制(复制积压缓冲区复制)、AOF时时增量复制,那这些复制方式都有什么区别、在什么情况下使用他们,各自都有什么特点带着这些疑问开始下面内容学习!


Redis主从复制方式

1、RDB全量复制

什么是全量复制?

(1)、全量复制是在从节点发送复制请求时,不满足部分复制的情况下Master进行一次RDB快照生成并将该快照数据发送给Slave达到Master-Slave数据一致。

什么情况下会进行全量复制?

(1)、当Slave节点第一次向Master发送复制请求时

(2)、在集群模式下Master节点出现crash情况,当新生成Master节点后其他所有Slave节点都要重新进行全量复制,因为他们记录的master的runId 和新的Master节点是不一致的。

(3)、当网络抖动出现Master-Slave 断开,而Slave进行再次重连,但是发现在这期间的增量大于AOF复制积压缓冲区,这样Slave没法同步在AOF复制积压缓冲区中被覆盖的部分所以只能进行全量复制。


2、AOF复制积压缓冲区数据复制(部分复制)

什么是部分复制?

(1)、在2.8之前的版本中只要Master-Slave 连接断开再次连接后就要进行上面说的全量复制。但是从实际场景分析在这期间可能都没有请求发生过、也有可能都是查询命令、也有可能是少量的几个命令当然也有可能有大量的写入和删除。如果上面所有场景都进行全量复制其实很浪费资源所以在Redis的2.8version中添加了部分复制功能。

(2)、Slave再次连接进行主从复制时会带上Mster.runId以及复制偏移量offset,如果offset还在复制积压缓冲区则Master将复制积压缓冲区中的增量发送给slave,这样就不用进行全量复制了。

(3)、关键点复制积压缓冲区大小默认是1M,但是这个我们一定要按照具体使用业务场景写并发量来设置合适大小以便有更好的部分复制。

什么情况下会进行部分复制、进行部分复制的影响因素有哪些?

(1)、部分复制这种情况只有在主从复制链接断开之后再次连接后,断开期间增量能在复制积压缓冲区中保存,这样只需要复制复制缓冲区中的数据而不用全量复制。不满足这个条件只能进行全量复制。


3、AOF时时增量复制

    不管是全量复制还是部分复制,只要slave和master数据一致之后后续的数据是通过命令方式时时进行同步


4、小结

    在主从复制中有全量复制生成RDB文件、全量复制不生成RDB文件(eof)、复制缓冲区内容、时时复制这几种情况,下面来看看什么情况下使用哪种方式……

1、如果slave_server 是第一次和master_server 主从同步则肯定是全量复制。

2、如果主从复制支持“eof”则全量复制时不会生成RDB文件而是直接按照redis字符格式发送。这个貌似是4.0新增特性。

3、如果不是第一次主从同步,但是slave_server 中保存的master_server_runid和master_server 当前runid不一致则说明不是同一台机器会发生全量复制。

4、如果master_server 和slave_server 之间主从复制应为网络原因断开连接,当slave_server 再次和master_server之间建立连接后进行同步时slave_server中同步的offset已经不再master_server的复制缓冲区中时只能进行全量复制。这个也是在2.8版本新增的功能。

5、如果主从之间断开时间不长或断开期间master_server 接收的写命令不多,也就是slave_server 中记录的offset还在master_server的复制积压缓冲区中则优先使用部分复制(2.8版本及以上)。

6、当master和slave之间数据达到一致时会进行时时复制也叫命令方式复制。


Redis主从复制流程

1、slave_client 发送slave of master_ip、master_port 请求

2、slave_server 接收client请求并将master_ip、master_port 记录在slave_server 端的redisServer->masterhost和redisServer->masterport

3、设置slave_server->repl_state= REPL_STATE_CONNECT即slave_server 和master_server之间等待socket连接

4、返回ok给slave_client ,为何整个过程没有和master_server 进行socket连接,因为是在时间事件中进行的。

5、replicationCron函数是一秒中执行一次,该函数中会判断slave_server 服务端的repl_state字段状态,上面看到会被设置为REPL_STATE_CONNECT,当replicationCron函数中执行判断状态为REPL_STATE_CONNECT 则slave_server 会创建和master_server之间的socket连接,如果socket连接返回ok则更新repl_state = REPL_STATE_CONNECTING,表示socket连接成功。

6、slave_server 和master_server 之间socket创建成功之后,还会创建一个文件事件来处理slave、master之间的通信。

7、socket建立之后为了确保网络正常则slave_server发送一个ping请求给master_server 并修改repl_state = REPL_STATE_RECEIVE_PONG

8、当master_server 接收到slave_server 请求之后返回消息,因为已经建立了文件事件所以会解析返回值并将slave_server 的repl_state状态修改为repl_state=REPL_STATE_SEND_PORT

9、slave_server 接着发送slave的port、ip这样最终slave_server的状态为 repl_state=REPL_STATE_RECEIVE_IP,到这里master_server 知道了slave_server 的主从复制的ip+port并会把这个信息保存在master_slave 的redisClient->slave_listening_port 中。

10、检测到当前repl_state=REPL_STATE_RECEIVE_IP时会从socket中读取主服务器回复结果并修改状态为REPL_STATE_SEND_CAPA。函数没有返回还是会继续执行if语句。

11、检测到repl_state=REPL_STATE_SEND_CAPA 也就是告诉master_server slave_server 支持哪些主从复制方式。

12、发送“REPLICATION ”,”capa”,”eof”,”capa”,”psync2” 给master_server 告诉具体支持的主从复制功能。

13、发送成功之后repl_state=REPL_STATE_RECEIVE_CAPA,函数还没结束继续执行,解析master_server 返回值并更新repl_state= REPL_STATE_SEND_PSYNC

14、还是在syncWithMaster方法中判断如果repl_state=REPL_STATE_SEND_PSYNC 则调用slaveTryPartialResynchronization函数尝试部分同步,通过返回值获取master_server 的runId以及offset。

15、修改slave_server 的repl_state = REPL_STATE_RECEIVE_PSYNC。

16、还是在syncWithMaster 函数中判断当前状态为REPL_STATE_RECEIVE_PSYNC 则再次调用slaveTryPartialResynchronization函数进行

并且向master_server发送psync同步命令。解析psync返回值并判断是全部复制还是部分复制。

17、如果master_server 返回 FULLRESYNC则会进行全量复制,如果返回CONTINUE则说明满足部分复制。

18、如果返回FULLRESYNC 则创建一个文件事件并调用readSyncBulkPayload函数来接收master_server 发送过来的RDB文件如果支持eof则发送的是REDIS字符格式命令

19、当readSyncBulkPayload函数同步完master_server 的RDB数据之后修改slave_server的repl_state状态为REPL_STATE_CONNECTED 状态。

20、当slave_server 的repl_state =REPL_STATE_CONNECTED时 则说明从服务器已经成功与主服务器建立连接,从服务器只需要接收并执行主服务器同步过来的命令请求即可,与执行普通客户端命令请求差不多。

OK 主从复制的大体流程了解了,具体执行后面我们在看源码的时候进行详细学习。整个代码在replication.c文件中。


主从复制的配置属性-策略

1、复制积压缓冲区大小

(1)、复制积压缓冲区大小是redisServer->repl_backlog_size 来设置,如果设置过小则slave_server和master_server 出连接断开再连时很有可能发生全量复制所以要合理设置复制积压缓冲区大小。复制积压缓冲区默认大小为1M。

(2)、还有一种情况可能导致全量复制进入死循环,当master生成RDB文件之后同步给slave,slave清空之前数据库中的数据并重新加载RDB文件同步数据,同步完RDB文件之后在发送master_runid、offset进行复制积压缓冲区数据同步的时候发现,复制积压缓冲区中的数据已经出现了覆盖的现象所以不满足部分复制,只能进行全量复制。这种情况会周而复始的进行最后导致master cpu飙升。


2、有效从节点

(1)、redisServer->slaves是一个列表,其中保存了所有有效的从节点客户端。有效从节点个数是redisServer->repl_good_slaves_count 来记录的。

(2)、从节点的有效性是通过redisServer->repl_ack_time 和redisServer->min_slaves_max_lag 来确定,repl_ack_time 记录了master_server 和slave_server 最近一次的通信时间,如果系统当前时间-repl_ack_time > min_slaves_max_lag 值则标记这个slave_server 为失效从节点redisServer->repl_good_slaves_count 的值减一。


3、主节点停止写服务

(1)redisServer->repl_main_slaves_to_write 该属性配置了当有效从节点数小于该值时主节点停止写服务。


4、主从复制-异步

(1)、Redis的主从复制是异步复制,异步分为两个方面,一个是master服务器在将数据同步到slave时是异步的,因此master服务器在这里仍然可以接收其他请求。

(2)、一个是slave在接收同步数据也是异步的。