Redis4.0 主从复制(PSYN2.0)

Redis4.0版本相比原来3.x版本,增长了不少新特性,如模块化、PSYN2.0、非阻塞DEL和FLUSHALL/FLUSHDB、RDB-AOF混合持久化等功能。尤为是模块化功能,做者从七年前的redis1.0版本就开始谋划,终于在4.0版本发布了,因此版本号也就从3.x直接迭代到了4.0以表示版本变化之大。简单看了一下新版的PSYN2.0,虽然不少细节没搞清楚,可是大概流程却是搞明白了。redis

1、主要流程数据库

在新版的PSYN2.0中,相比原来的PSYN功能,最大的变化支持两种场景下的部分重同步,一个场景是slave提高为master后,其余slave能够重新提高的master进行部分重同步。另一个场景就是slave重启后,能够进行部分重同步。缓存

在具体实现上redis服务器在生成RDB文件的时候会把当前的复制ID和复制偏移量一块儿保存到RDB文件中,后面服务器重启的时候,就能够从RDB文件中读取复制ID和复制偏移量,从而能够进行部分重同步功能。另外当服务器从slave提高为master后,会保存两个复制ID,其余slave复制的时候能够根据第二个复制ID来进行部分重同步。服务器

假设两台Redis服务器A和B启动后,对B执行slaveof命令,使得B变为A的从服务器,总体流程以下网络

一、B在启动的时候,会尝试从RDB文件中加载复制ID和复制偏移量(loadDataFromDisk),若是没有配置RDB文件或者RDB文件中不包含主从复制相关信息,那么使用随机生成的复制ID模块化

二、B接收到slaveof命令时候,设置复制状态为REPL_STATE_CONNECT(replicationSetmaster)测试

三、周期调度的时间事件中,若是检测到REPL_STATE_CONNECT状态,则会初始化连向master的套接字(serverCron->replicationCron->connectWithmaster),套接字关联事件对应的事件处理程序(syncWithmaster)spa

四、链接创建后,对应的事件处理程序,则会依据相关设置等,依次发送PING、AUTH、PORT、IP、CAPA、PSYN等信息(syncWithmaster)rest

  • 其中CAPA表示发送端在主从复制方面支持的能力,目前Redis4.0版本支持两种能力,一个是EOF、另一个是PSYNC2。code

    EOF表示slave能够接收直接由套接字发送过来的RDB流。传统的全量复制方式是master首先生成一个RDB文件,而后以$<count> bulk format发送到slave。而EOF格式下,master并不会在硬盘上生成RDB文件,而是先经过$EOF:<40 bytes delimiter>通知slave一个随机生成的40byte结束符,master边生成RDB文件,边发往slave,在slave以接收到的delimiter来判断接收过程结束。

    PSYNC2则表示支持Redis4.0最新的PSYN复制操做。

  • PSYN信息中,slave会把本身的复制ID和复制偏移量发给master

五、master根接收到PSYN消息后,根据复制ID若是复制ID与本身的复制ID相同且复制偏移量仍然存在于复制缓存区中(server.repl_backlog),那么执行部分重同步,回复CONTINUE消息,并从复制缓存区中复制相关的数据到slave。不然执行全量重同步,回复FULLRESYNC消息,生成RDB传输到slave。(实际上master会维护两个复制id,若是PSYN复制ID与第二个复制ID相同且PSYN的复制偏移量没有超过第二个复制ID的偏移量,也会执行部分重同步,参考后面的slave提高为master场景)

七、CONTINUE消息和FULLRESYNC消息中都会带有master的复制ID,slave须要将本身的复制ID更新为master的复制ID(若是二者不一样)。

六、同步成功之后,后续master接收到修改数据库的新命令,则会把新命令传输到slave执行。


2、网络闪断超时

master在与slave同步后,slave每隔1s发送一个REPLCONF ACK消息给master,master每隔10s给slave发送一个PING消息。若是master在必定的时间内(server.repl_timeout)没有收到消息,则会释放slave链接。一样若是slave在必定的时间(server.repl_timeout)内没有收到master的PING消息,也会释放套接字链接(replicationCron)。可是slave会设置复制状态为REPL_STATE_CONNECT(replicationHandlemasterDisconnection),后续slave端定时事件调度的时候,则会根据复制状态尝试从新链接。

测试脚本以下

 
 
 
 
 
#!/bin/shTIMEOUT=15M_IP=127.0.0.1M_PORT=6379M_OUT=master.outS1_IP=127.0.0.2S1_PORT=6380S1_OUT=slave_1.outS1_RDB_NAME=slave_1.rdbnohup ../src/redis-server --port $M_PORT --bind $M_IP --repl-timeout $TIMEOUT > $M_OUT &nohup ../src/redis-server --port $S1_PORT --bind $S1_IP --dbfilename $S1_RDB_NAME --repl-timeout $TIMEOUT > $S1_OUT&echo set redis hello | nc $M_IP $M_PORTecho lpush num 1 2 3 | nc $M_IP $M_PORTsleep 1echo slaveof 127.0.0.1 6379 | nc $S1_IP $S1_PORTsleep 1echo set redis world | nc $M_IP $M_PORTecho lpush num 4 | nc $M_IP $M_PORTecho save | nc $S1_IP $S1_PORTsleep 5echo "modify iptables"sudo iptables -I INPUT -s 127.0.0.1 -d 127.0.0.2 -j DROPsudo iptables -I OUTPUT -s 127.0.0.2 -d 127.0.0.1 -j DROPecho set redis helloworld | nc $M_IP $M_PORTecho lpush num 5 | nc $M_IP $M_PORTsleep 25echo "restore iptables"sudo iptables -D INPUT -s 127.0.0.1 -d 127.0.0.2 -j DROPsudo iptables -D OUTPUT -s 127.0.0.2 -d 127.0.0.1 -j DROP

最终运行如上脚本,获得以下结果,能够看到在slave初始启动的时候,NO20处slave经过REPLCONF CAPA信息通知master,本身支持EOF格式的RDB传输同时支持PSYNC2.0协议,接着经过PSYNC发送了一个随机生成的复制ID,master收到这个ID后发现与本身的复制ID不相符,须要全量复制,所以回复FULLRESYNC消息,slave收到FULLRESYNC消息后,会把本身的复制ID更新为f8e28****,后续slave生成RDB文件的时候,就会把f8e28****这个复制ID和复制偏移量保存进去。


网络闪断超时后,master和slave都会释放链接,当网络恢复后,slave会从新创建链接,同时经过PSYNC消息把对应的复制ID和复制偏移量发给master,master收到PSYNC消息后,发现复制ID与本身相同,说明这个slave以前是本身的slave,同时复制偏移量位于缓存区范围内,所以知足进行部分重同步的条件,回复CONTINUE消息,指示slave进行部分重同步


3、slave提高为master以及slave重启场景

这两种场景是PSYN2.0新增支持的场景,原理也很容易理解,就是经过复制ID来进行匹配的。直接看示例吧,经过一个综合示例来看一下这两种场景

示例场景:首先有一个master和两个从服务器slave1和slave2,同步过程当中首先把slave2进程kill掉,而后在master上执行两个命令同步到slave1后,把slave1手动提高为master,并把slave2重启指向以前的slave1

测试脚本

 
 
 
 
 
#!/bin/shTIMEOUT=15M_IP=127.0.0.1M_PORT=6379M_OUT=master.outS1_IP=127.0.0.2S1_PORT=6380S1_OUT=slave_1.outS1_RDB_NAME=slave_1.rdbS2_IP=127.0.0.3S2_PORT=6381S2_OUT=slave_2.outS2_RDB_NAME=slave_2.rdbnohup ../src/redis-server --port $M_PORT --bind $M_IP --repl-timeout $TIMEOUT > $M_OUT &nohup ../src/redis-server --port $S1_PORT --bind $S1_IP --dbfilename $S1_RDB_NAME --repl-timeout $TIMEOUT > $S1_OUT&nohup ../src/redis-server --port $S2_PORT --bind $S2_IP --dbfilename $S2_RDB_NAME --repl-timeout $TIMEOUT > $S2_OUT&sleep 1s1_pid=`cat  $S1_OUT|grep PID`s1_pid=`echo ${s1_pid#*PID:}`echo s1_pid:$s1_pids2_pid=`cat  $S2_OUT|grep PID`s2_pid=`echo ${s2_pid#*PID:}`echo s1_pid:$s2_pidecho set redis hello | nc $M_IP $M_PORTecho lpush num 1 2 3 | nc $M_IP $M_PORTsleep 1echo slaveof $M_IP $M_PORT | nc $S1_IP $S1_PORTecho slaveof $M_IP $M_PORT | nc $S2_IP $S2_PORTsleep 1echo set redis world | nc $M_IP $M_PORTecho lpush num 4 | nc $M_IP $M_PORTsleep 1echo save | nc $S2_IP $S2_PORTkill -9 $s2_pidsleep 3echo set redis helloworld | nc $M_IP $M_PORTecho lpush num 5 | nc $M_IP $M_PORTecho "slave1提高为master"echo slaveof no one | nc $S1_IP $S1_PORTecho "重启slave2 并设置 slaveof $S1_IP $S1_PORT"nohup ../src/redis-server --port $S2_PORT --bind $S2_IP --dbfilename $S2_RDB_NAME --repl-timeout $TIMEOUT > $S2_OUT&sleep 1echo slaveof $S1_IP $S1_PORT | nc $S2_IP $S2_PORT

须要注意的是在No68和No69处,slave2重启并设置slaveof 127.0.0.2 6380的时候,127.0.0.2:6380虽然回复了CONTINUE消息,可是复制ID却发生了变化。缘由是在127.0.0.2 6380执行slaveof no one的时候,Redis服务器会把当前的复制ID和复制偏移量保存起来,并从新生成一个新的复制ID(shiftReplicationId)。若是后续PSYN收到的复制ID与先前保存的复制ID相同,且复制偏移量小于先前保存的复制偏移量那么就能够进行部分重同步(当前若是与新生成的复制ID相同,且偏移量在缓存区内,一样能够进行部分重同步)。这里须要从新生成一个复制ID的缘由就是旧有的复制ID的复制偏移量在执行完slaveof no one后不能在继续增长,不然会致使数据混淆。(masterTryPartialResynchronization)



补充说明

一、Redis的RESP协议https://redis.io/topics/protocol

二、Redis4.0 http://antirez.com/news/110





相关文章
相关标签/搜索