Redis主從復制與一致性

復制簡介

數(shù)據(jù)的同步過程一般都涉及到全量數(shù)據(jù)的遷移以及后續(xù)增量數(shù)據(jù)的同步。

  • 對于Mysql數(shù)據(jù)庫可以通過mysqldump+binlog的方式獲取全量+增量數(shù)據(jù);
  • 對于MongoDB數(shù)據(jù)庫可以通過dump+oplog的方式獲取全量+增量數(shù)據(jù);
  • 對于PostgreSQL數(shù)據(jù)庫,可以通過 dump+WAL日志的方式獲取全量+增量數(shù)據(jù)。
  • 同樣,對于Redis數(shù)據(jù)庫,可以通過RDB文件存儲全量數(shù)據(jù),并采用復制偏移量(+復制積壓緩沖區(qū))的方式實現(xiàn)了增量數(shù)據(jù)的同步。其中RDB文件是將全量數(shù)據(jù)以序列化的方式持久化到.rdb文件中,存放的是二進制數(shù)據(jù)(序列化后的數(shù)據(jù)),并在從庫請求復制后將其發(fā)送給從庫;復制偏移量分別在主庫和從庫記錄了已經(jīng)應用的數(shù)據(jù),用以判斷主庫和從庫數(shù)據(jù)是否一致以及數(shù)據(jù)相差了多少(在Redis2.8版本之后,主庫在將數(shù)據(jù)發(fā)送給從庫的同時,還會將復制偏移量寫入到復制積壓緩沖區(qū))。

SYNC同步

在主Master接收到SYNC命令之后,它會執(zhí)行bgsave在后臺生成一個RDB文件,并且使用一個緩沖區(qū)記錄從現(xiàn)在開始執(zhí)行所有寫命令。當bgsave生成的RDB文件完成了之后,它就發(fā)送給從服務器去進行載入。在更新狀態(tài)完成之后,Master再將記錄在緩沖區(qū)里面的新命令發(fā)送給從服務器,這樣從服務器進行執(zhí)行,主從服務器就保持了一致狀態(tài)。
從服務器到主服務器的復制可以分為兩種情況:

  1. 初次復制
  2. 斷鏈后的重復制
    初次復制就是進行建立連接,然后進行全量和增量的同步,它的SYNC命令可以很好地完成任務。 但對于斷線后的重復制,處于命令傳播階段的主從服務器因為網(wǎng)絡原因而中斷又重連,會再次發(fā)送SYNC命令做全量+增量同步效率較低。 SYNC命令是一個非常耗費資源的操作,資源包括CPU、內(nèi)存、磁盤、帶寬、流量等。

PSYNC同步

為了解決SYNC在處理斷線重復制時候的低效問題,Redis從2.8版本之后開始使用PSYNC命 令,它支持完整重同步和部分重同步。 完整重同步和SYNC一樣,部分重同步就是在處理斷 線重新連接之后,主節(jié)點只向從節(jié)點發(fā)送鏈接斷開期間的寫命令,它的實現(xiàn)基于以下三部分:

  1. 復制偏移量(replication offset)
    Master:每次向Slave發(fā)送n個字節(jié)數(shù)據(jù)時,就會將自己的offset+n;
    Slave:每次收到Master發(fā)送來的n個字節(jié)數(shù)據(jù)時,就會將自己的offset+n;
    如果主從服務器處于一致狀態(tài),那么Master和Slave的Offset總是相同的。
  2. 復制積壓緩沖區(qū)(replication backlog)
    Master上維護的每一個固定大小(fixed-size)的FIFO隊列,保存著一部分最近傳播的寫命令。
    Master進行命令傳播時,不僅會將命令發(fā)送給所有Slave,還會將寫命令入隊到復制積壓緩 沖區(qū)。
    復制積壓緩沖區(qū)會為隊列中的每個字節(jié)記錄相應的復制偏移量,Slave重連Master時,會通過PSYNC命令將自己的Offset發(fā)送給Master。它是一個環(huán)形緩沖區(qū),大小是固定的,可存儲的命令有限,超出部分將會被刪除。
  3. Replication ID(復制ID)
    每個Redis的主節(jié)點都用一個隨機生成的字符串來表示在某一時刻其內(nèi)部存儲數(shù)據(jù)的狀態(tài), “某一時刻”可以理解為其成為master角色的那一刻,在第一個從節(jié)點加入時,Redis初始化了復制ID。
  4. 如果Slave的Offset與Master的Offset不相等,并且Slave的Offset偏移量之后的數(shù)據(jù)仍存在于replication backlog中,那么Master將對Slave執(zhí)行部分重同步操作;
  5. 否則,需要執(zhí)行完整重同步操作。

缺點:

  1. 在鏈式的一主兩從的結構中,M -> S1-> S2,如果S1下線了,那么S2在成為M的從庫后,會進行完全重同步;
  2. 在樹狀的一主兩從的結構中,S1 -> M <- S2,如果M不可用下線,S1提升為主,那 么S2在成為S1的從庫后,會進行完全重同步;
  3. 在一主一從的結構中,M -> S, 發(fā)生主從切換,需要進行完全重同步;
  4. 在從實例發(fā)生重啟,及時不變更主從關系,由于丟失了所有的復制信息,還是會進行完全重同步;

PYSNC2

  • 支持實例重啟后的部分重同步
  • 支持主從切換后的部分重同步
    在建立主從復制關系時,master會將自己的replid傳遞給slave,slave會把自己的replid更新為master的,這樣逐級傳遞下去。最終master、slave1、slave2的server.replid全部一樣,都是master的replid。主從復制建立完成之后,這條復制鏈上所有的數(shù)據(jù)都由master產(chǎn)生,也就是說master、 slave1、slave2的offset也全都匹配得上。
    優(yōu)化解決:
  • 對于場景1: M -> S1-> S2
    可以看出,M和S2具有相同的replid,并且offset也可以匹配上,此時就可以直接進行部分重同步,避免了完全同步的開銷;
  • 對于場景2: S1 -> M <- S2
    當S1變更為主時,M的replid和offset信息并不會被丟去,而是被保存在了S1的replid2和 second_repl_offset,參與同步;這樣在S2在成為S1的從庫之后,依然能夠查到同步之前的同步信息,從而可以進行部分重同步,不需要進行完全重同步。
  • 對于場景3: M -> S
    當S變更為主時,原來master的replid,offset不會丟棄,而是會保存在replid2和 second_repl_offset中,參與匹配。那么M的replid,offset都能從新主中找到replid2,并且和新主的second_repl_offset也匹配的上,就可以直接進行增量同步,避免全量同步的開銷。
  • 對于場景4: 從實例重啟
    psync2在意外關閉前會調(diào)用 rdbSaveInfoAuxFields 函數(shù)把當前的復制 ID(即關閉前正在復制的 master 的 replid,因為 slave 中的 replid 字段保存的是 master 的復制 ID) 和復制偏移量一起保存到 RDB 文件中,后面該 slave 重啟的時候,就可以從 RDB 文件中讀取復制 ID 和復制偏移量,然后重連上 master 后 slave 將這兩個值發(fā)送給master。

注:上述所有場景的前提是數(shù)據(jù)依然保存在backlog中,否則還是會進行完全重同步。

Redis命令傳播原理

  • 當完成同步操作之后,master-slave便會進入命令傳播階段,此時master-slave的數(shù)據(jù)是一致的。
  • 當maste執(zhí)行完新的寫命令后,會通過傳播程序把該命令追加至復制積壓緩沖區(qū),然后異步地發(fā)送給slave。
  • slave接收命令并執(zhí)行,同時更新slave維護的復制偏移量offset。


    repl.png

主從數(shù)據(jù)一致性

如果slave可以收到每條傳播指令,并執(zhí)行成功,便可以保持與master的數(shù)據(jù)一致狀態(tài)。但是master并不等待slave節(jié)點的返回,master與slave是通過網(wǎng)絡通信,由于網(wǎng)絡抖動等因 素,命令傳播過程不保證slave真正接收到,那如何在傳播階段確保主從數(shù)據(jù)一致呢?
在命令傳播階段,每隔一秒slave節(jié)點向master節(jié)點發(fā)送一次心跳信息,命令格式為 REPLCONF ACK <offset>。其中offset指從節(jié)點保存的復制偏移量。REPLCONF ACK命令的作用包括:

  1. 實時監(jiān)測主從節(jié)點網(wǎng)絡狀態(tài)
    該命令會被主節(jié)點用于復制超時的判斷。此外在主節(jié)點中使用info Replication,可以看到其從節(jié)點的狀態(tài)中的lag值,代表的是主節(jié)點上次收到該 REPLCONF ACK命令的時間間隔,在正常情況下,該值應該是0或1。
  2. 檢測命令丟失
    從節(jié)點發(fā)送了自身的offset,主節(jié)點會與自己的offset對比,如果從節(jié)點數(shù)據(jù)缺失(如網(wǎng)絡丟包),主節(jié)點會推送缺失的數(shù)據(jù)(這里也會利用復制積壓緩沖區(qū))。 注意,offset和復制積壓緩沖區(qū),不僅可以用于部分復制,也可以用于處理命令丟失等情 形;區(qū)別在于前者是在斷線重連后進行的,而后者是在主從節(jié)點沒有斷線的情況下進行的。
  3. 輔助保證從節(jié)點的數(shù)量和延遲
    Redis主節(jié)點中使用min-slaves-to-write和min-slaves-max-lag參數(shù),來保證主節(jié)點在不安全的情況下不會執(zhí)行寫命令;所謂不安全,是指從節(jié)點數(shù)量太少,或延遲過高。例如min-slaves-to-write和min-slaves-max-lag分別是3和10,含義是如果從節(jié)點數(shù)量小于3個,或所有從節(jié)點的延遲值都大于10s,則主節(jié)點拒絕執(zhí)行寫命令。 而這里從節(jié)點延遲值的獲取,就是通過主節(jié)點接收到REPLCONF ACK命令的時間來判斷的,即前面所說的info Replication中的lag值。

全量同步和增量同步

在全量復制階段,主節(jié)點會將執(zhí)行的寫命令放到復制緩沖區(qū)中,該緩沖區(qū)存放的數(shù)據(jù)包括了以下幾個時間段內(nèi)主節(jié)點執(zhí)行的寫命令:bgsave生成RDB文件、RDB文件由主節(jié)點發(fā)往從 節(jié)點、從節(jié)點清空老數(shù)據(jù)并載入RDB文件中的數(shù)據(jù)。當主節(jié)點數(shù)據(jù)量較大,或者主從節(jié)點之間網(wǎng)絡延遲較大時,可能導致該緩沖區(qū)的大小超過了限制,此時主節(jié)點會斷開與從節(jié)點之間的連接;這種情況可能引起全量復制→復制緩沖區(qū)溢出導致連接中斷→重連→全量復制→復制緩沖區(qū)溢出導致連接中斷......的循環(huán)。
復制緩沖區(qū)的大小由client-output-buffer-limit slave{hard limit}{soft limit}{soft seconds}配 置,默認值為client-output-buffer-limit slave 256MB 64MB 60,其含義是:如果buffer大于 256MB,或者連續(xù)60s大于64MB,則主節(jié)點會斷開與該從節(jié)點的連接。該參數(shù)是可以通過 config set命令動態(tài)配置的(即不重啟Redis也可以生效)。

backlog

Redis為復制積壓緩沖區(qū)設置的默認大小為1MB,如果主服務器需要執(zhí)行大量寫命令,又或者主從服務器斷線后重連接所需的時間比較?,那么這個大小也許并不合適。如果復制積壓 緩沖區(qū)的大小設置得不恰當,那么PSYNC命令的復制重同步模式就不能正常發(fā)揮作用,正確估算和設置復制積壓緩沖區(qū)的大小非常重要。
復制積壓緩沖區(qū)的最小大小可以根據(jù)公式second*write_size_per_second 來估算:

  • 其中second為從服務器斷線后重新連接上主服務器所需的平均時間(以秒計算)。
  • 而write_size_per_second則是主服務器平均每秒產(chǎn)生的寫命令數(shù)據(jù)量(協(xié)議格式的寫命令的?度總和)。
    例如,如果主服務器平均每秒產(chǎn)生1 MB的寫數(shù)據(jù),而從服務器斷線之后平均要5秒才能重新 連接上主服務器,那么復制積壓緩沖區(qū)的大小就不能低于5MB。
    為了安全起?可以將復制積壓緩沖區(qū)的大小設為2secondwrite_size_per_second,這樣可以保證絕大部分斷線情況都能用部分重同步來處理。 至于復制積壓緩沖區(qū)大小的修改方法,可以參考配置文件中關于repl-backlog-size選項的說明。
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容