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





相關文章
相關標籤/搜索