單機redis不能滿足分區(qū)容錯,當(dāng)主機發(fā)生單點故障的時候矫夷,redis服務(wù)就無法訪問;如果主機的磁盤設(shè)備損壞憋槐,甚至有丟失數(shù)據(jù)的風(fēng)險双藕。可以通過主從結(jié)構(gòu)來避免單點故障的問題秦陋,當(dāng)主機不可用的時候蔓彩,從機仍然保持著數(shù)據(jù)的備份。必要時驳概,可以將從機升級為主機來對外提供服務(wù)赤嚼。
主從結(jié)構(gòu)的需要關(guān)注兩個重要問題分別是:主從數(shù)據(jù)的一致性和主從機器的存活狀態(tài)。本文主要關(guān)注主從數(shù)據(jù)的一致性顺又,即主從復(fù)制更卒。
redis主從結(jié)構(gòu)的建立是通過在從機服務(wù)器執(zhí)行SLAVEOF命令實現(xiàn)的,該命令讓從服務(wù)器連接主服務(wù)器稚照,并去復(fù)制主服務(wù)器的數(shù)據(jù)蹂空。下文皆以master和slave分別指代主俯萌、從服務(wù)器。
1.復(fù)制功能的實現(xiàn) (redis2.8以前)
復(fù)制功能包含兩個階段: 同步和命令傳播
- 同步的作用:將slave的數(shù)據(jù)庫狀態(tài)更新至master的當(dāng)前狀態(tài)
- 命令傳播的作用: slave更新狀態(tài)更新后上枕,master接收到新的客戶端請求導(dǎo)致master狀態(tài)改變時咐熙,重新使slave更新到與master一致的狀態(tài)。
1.1 同步的過程
- slave 向 master 發(fā)送 SYNC 命令
- master收到slave的SYNC命令辨萍, 執(zhí)行BGSAVE操作棋恼,在后臺生成數(shù)據(jù)快照RDB文件, 同時使用一個復(fù)制積壓緩沖區(qū)來記錄從生成快照開始锈玉,執(zhí)行的來自于客戶端的寫命令爪飘。
- BGSAVE完成,master 將生成的RDB文件發(fā)給slave拉背。slave接收到該文件之后載入文件师崎,更新自己的數(shù)據(jù)庫,使數(shù)據(jù)同步到與 master 在 BGSAVE 開始時相同的狀態(tài)椅棺。
-
master將緩沖區(qū)內(nèi)的所有寫命令發(fā)送給 slave犁罩, slave執(zhí)行這些命令,更新自己的數(shù)據(jù)庫两疚,讓自己更新至當(dāng)前master所處的狀態(tài)
1.2 命令傳播
同步過程完成后昼汗,master收到來自客戶端的寫請求,執(zhí)行后會出現(xiàn)主從狀態(tài)不一致的情況鬼雀。為了使二者一致,master需要將這些命令發(fā)送到slave蛙吏,slave執(zhí)行完這些命令源哩,兩者重新回到一致的狀態(tài)。
1.3 舊版本復(fù)制的缺點
每當(dāng)slave連接master的時候鸦做,都需要完成同步励烦,同步通過BGSAVE生成master中全量數(shù)據(jù)的快照,然后將快照發(fā)送給slave泼诱。這在slave初次連接到master的時候是有必要的坛掠,但是重連情況下,真的有必要全量復(fù)制嗎治筒?
slave與master斷開連接期間屉栓,如果master執(zhí)行的寫入命令很少,為了同步這少量的數(shù)據(jù)耸袜,做全量同步是不劃算的一件事友多。因為BGSAVE需要在后臺耗費大量的CPU、內(nèi)存和磁盤IO資源堤框,同時將全部數(shù)據(jù)發(fā)送給slave需要占用較大的網(wǎng)絡(luò)帶寬和流量域滥,影響master對客戶端請求的響應(yīng)纵柿。
那么能不呢在開銷較小的情況下將這些增量數(shù)據(jù)同步至重連的客戶端呢?有启绰,新版本的復(fù)制支持部分重同步就是做這件事的昂儒。
2.新版本復(fù)制功能
新版本復(fù)制功能是通過PSYNC實現(xiàn)的,它具有完整重同步和部分重同步兩種模式委可。
- 完整重同步和初次同步SYNC功能相同渊跋,slave發(fā)送命令給master,由master生成RDB文件撤缴,同時在緩沖區(qū)內(nèi)保存寫命令刹枉,將RDB文件和寫命令發(fā)送給slave。主要用于處理初次連接時數(shù)據(jù)同步屈呕。
- 部分重同步用于處理重連時連接斷開期間master執(zhí)行寫入命令的同步微宝。當(dāng)然,如果產(chǎn)生數(shù)據(jù)量過大虎眨,導(dǎo)致master內(nèi)存不下這么多寫入命令蟋软,那么仍然要通過全量同步。
思考一下為了同步在連接丟失期間內(nèi)master的寫入數(shù)據(jù)嗽桩,我們需要那些信息岳守?
- 首先需要一個容器,記錄master的寫入命令碌冶。 對這個容器有以下要求:第一湿痢,不能無限大,否則寫入命令就能把內(nèi)存給占滿了扑庞;第二譬重,既然容量有限,那么應(yīng)當(dāng)優(yōu)先存放最近寫入的命令罐氨⊥喂妫基于這兩個要求,可以使用有限隊列栅隐,由于FIFO的特性塔嬉,當(dāng)隊列滿了之后,將頭部節(jié)點給remove掉租悄,然后在尾部添加新的數(shù)據(jù)谨究。redis由復(fù)制積壓緩沖區(qū)來承擔(dān)該角色。它是一個默認(rèn)大小1MB的FIFO隊列恰矩。
- 需要master和slave在恢復(fù)連接時记盒,二者分別執(zhí)行到的最后最后一條命令在隊列中的位置。redis通過復(fù)制偏移量(offset)來完成外傅。
- master每次向slave發(fā)送N byte的數(shù)據(jù)時纪吮,將自己的offset 加 N
- slave每次收到master發(fā)送來的N byte數(shù)據(jù)時俩檬,將自己的offset 加 N
- slave向master請求重連時,master如何能保證該slave之前就是從自己這里同步的數(shù)據(jù)呢碾盟?顯然需要一個身份憑證棚辽,redis通過運行Id來完成校驗。即slave內(nèi)需要保存master的運行Id冰肴,重連時屈藐,帶上該Id,master校驗這是自己的id熙尉,然后才能向slave發(fā)送增量數(shù)據(jù)联逻,否則將其視為需要全量同步。
2.1 復(fù)制積壓緩沖區(qū)
redis內(nèi)復(fù)制積壓緩沖區(qū)結(jié)構(gòu)如圖:它包含了每個字節(jié)的值和其對應(yīng)的偏移量检痰。
2.2服務(wù)器offset
服務(wù)器內(nèi)offset如下圖: master與slave最近一次命令傳播之后包归,雙方offset都在10086. 之后,slave A與master斷開連接铅歼。在連接斷開期間公壤,master收到了新的命令, 之后又向slave傳播了33字節(jié)數(shù)據(jù)椎椰。此時各服務(wù)器的offset狀態(tài)如圖厦幅。
假設(shè)此時slave A請求重連, 那么他需要向master發(fā)送重連請求慨飘,并帶上自己的offset和內(nèi)部保存的master運行Id确憨。master收到請求后,校驗運行id瓤的,然后從buffer內(nèi)查找slave offset + 1是否在buffer內(nèi)缚态,如果不在,說明這段時間內(nèi)請求較多堤瘤,已經(jīng)將這個數(shù)據(jù)給擠出了緩沖區(qū),那么此時就不能進行部分同步浆熔,因為這樣必然要丟失數(shù)據(jù)本辐。如果在,那么將slave offset + 1 到 master offset的數(shù)據(jù)發(fā)送給slave即可医增。
2.3 PSYNC命令(partial sync)
PSYNC有兩種調(diào)用方式:
- 如果服務(wù)器執(zhí)行過 SLAVEOF no one 或沒有復(fù)制過任何服務(wù)器, 那么他向master 發(fā)送PSYNC ? -1 命令慎皱,請求master進行完整重同步。
- 如果該服務(wù)器復(fù)制過某個master叶骨, 則他向master發(fā)送 PSYNC <runid> <offset>命令請求部分重同步茫多, runid為上次連接master的運行ID, offset為slave的當(dāng)前偏移量忽刽。
主服務(wù)器收到PSYNC后天揖,有兩種正常返回值和異常返回:
-
+FULLRESYNC <runid> <offset> (完整重同步)和 +CONTINUE (部分重同步)夺欲。 完整同步的話,slave需要保存runid和offset今膊,在RDB數(shù)據(jù)載入數(shù)據(jù)庫之后需要將自己的初始化偏移量更新為該offset些阅;部分同步則只需要等待master將自己缺失的部分?jǐn)?shù)據(jù)發(fā)送過來,然后將offset + N斑唬。
如果為-ERR回復(fù)市埋,表示master版本低于Redis2.8,無法識別PSYNC請求
同步之后,命令傳播就和舊版本如出一轍了恕刘,在此不作贅述缤谎。
總結(jié)
- 主從主要解決的問題: 單點故障容錯,當(dāng)然也可以做讀寫分離褐着,不過似乎沒什么必要坷澡,因為可以做集群,分散每臺master的讀寫壓力献起。
-
- 主從結(jié)構(gòu)如何實現(xiàn)數(shù)據(jù)復(fù)制
- 舊版本: SYNC + 命令傳播洋访。缺點在于每次重連都需要完整同步,而完整同步需要較大的硬件資源開銷谴餐。
- 新版本: PSYNC + 命令傳播姻政。重連時通常不希望完整同步,只需要同步在連接斷開期間master的寫入命令即可岂嗓。此時通過復(fù)制緩沖區(qū)和主從雙方的offset即可判斷是否可以從buffer中取出斷開期間的全部寫入命令汁展,可以的話則只需要將buffer中[slave offset + 1 , master offset]區(qū)間內(nèi)的數(shù)據(jù)發(fā)送到slave即可。