Redis主從複製原理總結

散盡浮華發表於2016-07-19

和Mysql主從複製的原因一樣,Redis雖然讀取寫入的速度都特別快,但是也會產生讀壓力特別大的情況。為了分擔讀壓力,Redis支援主從複製,Redis的主從結構可以採用一主多從或者級聯結構,Redis主從複製可以根據是否是全量分為全量同步和增量同步。下圖為級聯結構。 

全量同步
Redis全量複製一般發生在Slave初始化階段,這時Slave需要將Master上的所有資料都複製一份。具體步驟如下:
-  從伺服器連線主伺服器,傳送SYNC命令;
-  主伺服器接收到SYNC命名後,開始執行BGSAVE命令生成RDB檔案並使用緩衝區記錄此後執行的所有寫命令;
-  主伺服器BGSAVE執行完後,向所有從伺服器傳送快照檔案,並在傳送期間繼續記錄被執行的寫命令;
-  從伺服器收到快照檔案後丟棄所有舊資料,載入收到的快照;
-  主伺服器快照傳送完畢後開始向從伺服器傳送緩衝區中的寫命令;
-  從伺服器完成對快照的載入,開始接收命令請求,並執行來自主伺服器緩衝區的寫命令;

完成上面幾個步驟後就完成了從伺服器資料初始化的所有操作,從伺服器此時可以接收來自使用者的讀請求。

增量同步
Redis增量複製是指Slave初始化後開始正常工作時主伺服器發生的寫操作同步到從伺服器的過程。
增量複製的過程主要是主伺服器每執行一個寫命令就會向從伺服器傳送相同的寫命令,從伺服器接收並執行收到的寫命令。
 
Redis主從同步策略
主從剛剛連線的時候,進行全量同步;全同步結束後,進行增量同步。當然,如果有需要,slave 在任何時候都可以發起全量同步。redis 策略是,無論如何,首先會嘗試進行增量同步,如不成功,要求從機進行全量同步。
 
注意點
如果多個Slave斷線了,需要重啟的時候,因為只要Slave啟動,就會傳送sync請求和主機全量同步,當多個同時出現的時候,可能會導致Master IO劇增當機。

Redis主從複製的配置十分簡單,它可以使從伺服器是主伺服器的完全拷貝。需要清除Redis主從複製的幾點重要內容:

1)Redis使用非同步複製。但從Redis 2.8開始,從伺服器會週期性的應答從複製流中處理的資料量。
2)一個主伺服器可以有多個從伺服器。
3)從伺服器也可以接受其他從伺服器的連線。除了多個從伺服器連線到一個主伺服器之外,多個從伺服器也可以連線到一個從伺服器上,形成一個
   圖狀結構。
4)Redis主從複製不阻塞主伺服器端。也就是說當若干個從伺服器在進行初始同步時,主伺服器仍然可以處理請求。
5)主從複製也不阻塞從伺服器端。當從伺服器進行初始同步時,它使用舊版本的資料來應對查詢請求,假設你在redis.conf配置檔案是這麼配置的。
   否則的話,你可以配置當複製流關閉時讓從伺服器給客戶端返回一個錯誤。但是,當初始同步完成後,需要刪除舊的資料集和載入新的資料集,在
   這個短暫的時間內,從伺服器會阻塞連線進來的請求。
6)主從複製可以用來增強擴充套件性,使用多個從伺服器來處理只讀的請求(比如,繁重的排序操作可以放到從伺服器去做),也可以簡單的用來做資料冗餘。
7)使用主從複製可以為主伺服器免除把資料寫入磁碟的消耗:在主伺服器的redis.conf檔案中配置“避免儲存”(註釋掉所有“儲存“命令),然後連線一個配
   置為“進行儲存”的從伺服器即可。但是這個配置要確保主伺服器不會自動重啟(要獲得更多資訊請閱讀下一段)

主從複製的一些特點:

1)採用非同步複製;
2)一個主redis可以含有多個從redis;
3)每個從redis可以接收來自其他從redis伺服器的連線;
4)主從複製對於主redis伺服器來說是非阻塞的,這意味著當從伺服器在進行主從複製同步過程中,主redis仍然可以處理外界的訪問請求;
5)主從複製對於從redis伺服器來說也是非阻塞的,這意味著,即使從redis在進行主從複製過程中也可以接受外界的查詢請求,只不過這時候從redis返回的是以前老的資料,
   如果你不想這樣,那麼在啟動redis時,可以在配置檔案中進行設定,那麼從redis在複製同步過程中來自外界的查詢請求都會返回錯誤給客戶端;(雖然說主從複製過程中
   對於從redis是非阻塞的,但是當從redis從主redis同步過來最新的資料後還需要將新資料載入到記憶體中,在載入到記憶體的過程中是阻塞的,在這段時間內的請求將會被阻,
   但是即使對於大資料集,載入到記憶體的時間也是比較多的);
6)主從複製提高了redis服務的擴充套件性,避免單個redis伺服器的讀寫訪問壓力過大的問題,同時也可以給為資料備份及冗餘提供一種解決方案;
7)為了編碼主redis伺服器寫磁碟壓力帶來的開銷,可以配置讓主redis不在將資料持久化到磁碟,而是通過連線讓一個配置的從redis伺服器及時的將相關資料持久化到磁碟,
   不過這樣會存在一個問題,就是主redis伺服器一旦重啟,因為主redis伺服器資料為空,這時候通過主從同步可能導致從redis伺服器上的資料也被清空;

Redis大概主從同步是怎麼實現的?

全量同步:
master伺服器會開啟一個後臺程式用於將redis中的資料生成一個rdb檔案,與此同時,伺服器會快取所有接收到的來自客戶端的寫命令(包含增、刪、改),當後臺儲存程式
處理完畢後,會將該rdb檔案傳遞給slave伺服器,而slave伺服器會將rdb檔案儲存在磁碟並通過讀取該檔案將資料載入到記憶體,在此之後master伺服器會將在此期間快取的
命令通過redis傳輸協議傳送給slave伺服器,然後slave伺服器將這些命令依次作用於自己本地的資料集上最終達到資料的一致性。

部分同步:
從redis 2.8版本以前,並不支援部分同步,當主從伺服器之間的連線斷掉之後,master伺服器和slave伺服器之間都是進行全量資料同步,但是從redis 2.8開
始,即使主從連線中途斷掉,也不需要進行全量同步,因為從這個版本開始融入了部分同步的概念。部分同步的實現依賴於在master伺服器記憶體中給每個slave伺服器維護了
一份同步日誌和同步標識,每個slave伺服器在跟master伺服器進行同步時都會攜帶自己的同步標識和上次同步的最後位置。當主從連線斷掉之後,slave伺服器隔斷時間
(預設1s)主動嘗試和master伺服器進行連線,如果從伺服器攜帶的偏移量標識還在master伺服器上的同步備份日誌中,那麼就從slave傳送的偏移量開始繼續上次的同步
操作,如果slave傳送的偏移量已經不再master的同步備份日誌中(可能由於主從之間斷掉的時間比較長或者在斷掉的短暫時間內master伺服器接收到大量的寫操作),則
必須進行一次全量更新。在部分同步過程中,master會將本地記錄的同步備份日誌中記錄的指令依次傳送給slave伺服器從而達到資料一致。

主從同步中需要注意幾個問題

1)在上面的全量同步過程中,master會將資料儲存在rdb檔案中然後傳送給slave伺服器,但是如果master上的磁碟空間有效怎麼辦呢?那麼此時全部同步對於master來說
將是一份十分有壓力的操作了。此時可以通過無盤複製來達到目的,由master直接開啟一個socket將rdb檔案傳送給slave伺服器。(無盤複製一般應用在磁碟空間有限但是網
絡狀態良好的情況下)

2)主從複製結構,一般slave伺服器不能進行寫操作,但是這不是死的,之所以這樣是為了更容易的保證主和各個從之間資料的一致性,如果slave伺服器上資料進行了修改,
那麼要保證所有主從伺服器都能一致,可能在結構上和處理邏輯上更為負責。不過你也可以通過配置檔案讓從伺服器支援寫操作。(不過所帶來的影響還得自己承擔哦。。。)

3)主從伺服器之間會定期進行通話,但是如果master上設定了密碼,那麼如果不給slave設定密碼就會導致slave不能跟master進行任何操作,所以如果你的master伺服器
上有密碼,那麼也給slave相應的設定一下密碼吧(通過設定配置檔案中的masterauth);

4)關於slave伺服器上過期鍵的處理,由master伺服器負責鍵的過期刪除處理,然後將相關刪除命令已資料同步的方式同步給slave伺服器,slave伺服器根據刪除命令刪除
本地的key。

當主伺服器不進行持久化時複製的安全性

在進行主從複製設定時,強烈建議在主伺服器上開啟持久化,當不能這麼做時,比如考慮到延遲的問題,應該將例項配置為避免自動重啟。

為什麼不持久化的主伺服器自動重啟非常危險呢?
為了更好的理解這個問題,看下面這個失敗的例子,其中主伺服器和從伺服器中資料庫都被刪除了。

設定節點A為主伺服器,關閉持久化,節點B和C從節點A複製資料。
這時出現了一個崩潰,但Redis具有自動重啟系統,重啟了程式,因為關閉了持久化,節點重啟後只有一個空的資料集。
節點B和C從節點A進行復制,現在節點A是空的,所以節點B和C上的複製資料也會被刪除。
當在高可用系統中使用Redis Sentinel,關閉了主伺服器的持久化,並且允許自動重啟,這種情況是很危險的。
比如主伺服器可能在很短的時間就完成了重啟,以至於Sentinel都無法檢測到這次失敗,那麼上面說的這種失敗的情況就發生了。

如果資料比較重要,並且在使用主從複製時關閉了主伺服器持久化功能的場景中,都應該禁止例項自動重啟。

Redis主從複製是如何工作的

如果設定了一個從伺服器,在連線時它傳送了一個SYNC命令,不管它是第一次連線還是再次連線都沒有關係。

然後主伺服器開始後臺儲存,並且開始快取新連線進來的修改資料的命令。當後臺儲存完成後,主伺服器把資料檔案傳送到從伺服器,
從伺服器將其儲存在磁碟上,然後載入到記憶體中。然後主伺服器把剛才快取的命令傳送到從伺服器。這是作為命令流來完成的,並且
和Redis協議本身格式相同。

你可以通過telnet自己嘗試一下。在Redis伺服器工作時連線到Redis埠,傳送SYNC命令,會看到一個批量的傳輸,並且主伺服器接收
的每一個命令都會通過telnet會話重新傳送一遍。

當主從伺服器之間的連線由於某些原因斷開時,從伺服器可以自動進行重連線。當有多個從伺服器同時請求同步時,主伺服器只進行一個後臺儲存。

當連線斷開又重新連上之後,一般都會進行一個完整的重新同步,但是從Redis2.8開始,只重新同步一部分也可以。
 

部分重新同步

從Redis 2.8開始,如果遭遇連線斷開,重新連線之後可以從中斷處繼續進行復制,而不必重新同步。

它的工作原理是這樣:
主伺服器端為複製流維護一個記憶體緩衝區(in-memory backlog)。主從伺服器都維護一個複製偏移量(replication offset)和master run id ,
當連線斷開時,從伺服器會重新連線上主伺服器,然後請求繼續複製,假如主從伺服器的兩個master run id相同,並且指定的偏移量在記憶體緩衝
區中還有效,複製就會從上次中斷的點開始繼續。如果其中一個條件不滿足,就會進行完全重新同步(在2.8版本之前就是直接進行完全重新同步)。
因為主執行id不儲存在磁碟中,如果從伺服器重啟了的話就只能進行完全同步了。

部分重新同步這個新特性內部使用PSYNC命令,舊的實現中使用SYNC命令。Redis2.8版本可以檢測出它所連線的伺服器是否支援PSYNC命令,不支援的
話使用SYNC命令。

無磁碟複製

通常來講,一個完全重新同步需要在磁碟上建立一個RDB檔案,然後載入這個檔案以便為從伺服器傳送資料。

如果使用比較低速的磁碟,這種操作會給主伺服器帶來較大的壓力。Redis從2.8.18版本開始嘗試支援無磁碟的複製。
使用這種設定時,子程式直接將RDB通過網路傳送給從伺服器,不使用磁碟作為中間儲存。

配置

主從複製的配置十分簡單:把下面這行加入到從伺服器的配置檔案中即可。
slaveof 192.168.1.1 6379

當然你需要把其中的192.168.1.1 6379替換為你自己的主伺服器IP(或者主機名hostname)和埠。另外你可以呼叫SLAVEOF命令,
主伺服器就會開始與從伺服器同步。

關於部分重新同步,還有一些針對複製記憶體緩衝區的優化引數。檢視Redis介質中的Redis.conf示例獲得更多資訊。

使用repl-diskless-sync配置引數來啟動無磁碟複製。使用repl-diskless-sync-delay 引數來配置傳輸開始的延遲時間,以便等待
更多的從伺服器連線上來。檢視Redis介質中的Redis.conf示例獲得更多資訊。

只讀從伺服器

從Redis 2.6開始,從伺服器支援只讀模式,並且是預設模式。這個行為是由Redis.conf檔案中的slave-read-only 引數控制的,
可以在執行中通過CONFIG SET來啟用或者禁用。

只讀的從伺服器會拒絕所有寫命令,所以對從伺服器不會有誤寫操作。但這不表示可以把從伺服器例項暴露在危險的網路環境下,
因為像DEBUG或者CONFIG這樣的管理命令還是可以執行的。不過你可以通過使用rename-command命令來為這些命令改名來增加安全性。

你可能想知道為什麼只讀限制還可以被還原,使得從伺服器還可以進行寫操作。雖然當主從伺服器進行重新同步或者從伺服器重啟後,
這些寫操作都會失效,還是有一些使用場景會想從伺服器中寫入臨時資料的,但將來這個特性可能會被去掉。

限制有N個以上從伺服器才允許寫入

從Redis 2.8版本開始,可以配置主伺服器連線N個以上從伺服器才允許對主伺服器進行寫操作。但是,因為Redis使用的是非同步主從複製,
沒辦法確保從伺服器確實收到了要寫入的資料,所以還是有一定的資料丟失的可能性。

這一特性的工作原理如下:
1)從伺服器每秒鐘ping一次主伺服器,確認處理的複製流數量。
2)主伺服器記住每個從伺服器最近一次ping的時間。
3)使用者可以配置最少要有N個伺服器有小於M秒的確認延遲。
4)如果有N個以上從伺服器,並且確認延遲小於M秒,主伺服器接受寫操作。

還可以把這看做是CAP原則(一致性,可用性,分割槽容錯性)不嚴格的一致性實現,雖然不能百分百確保一致性,但至少保證了丟失的資料不會超過M秒內的資料量。

如果條件不滿足,主伺服器會拒絕寫操作並返回一個錯誤。
1)min-slaves-to-write(最小從伺服器數)
2)min-slaves-max-lag(從伺服器最大確認延遲)

======            通過redis實現伺服器崩潰等資料恢復             ======
由於redis儲存在記憶體中且提供一般程式語言常用的資料結構儲存型別,所以經常被用於做伺服器崩潰當機的資料恢復處理。伺服器可以在某些指定過程中將需要儲存的資料以json物件等方式儲存到redis中,也就是我們常說的快照,當伺服器執行時讀取redis來判斷是否有待需要恢復資料繼續處理的業務。當一次業務處理結束後再刪除redis的資料即可。redis提供兩種將記憶體資料匯出到硬碟實現資料備份的方法

1)RDB方式(預設)
RDB方式的持久化是通過快照(snapshotting)完成的,當符合一定條件時Redis會自動將記憶體中的所有資料進行快照並儲存在硬碟上。進行快照的條件可以由使用者在配置檔案中自定義,由兩個引數構成:時間和改動的鍵的個數。當在指定的時間內被更改的鍵的個數大於指定的數值時就會進行快照。RDB是redis預設採用的持久化方式,在配置檔案中已經預置了3個條件:
save 900 1      #900秒內有至少1個鍵被更改則進行快照
save 300 10     #300秒內有至少10個鍵被更改則進行快照
save 60 10000   #60秒內有至少10000個鍵被更改則進行快照
 
可以存在多個條件,條件之間是"或"的關係,只要滿足其中一個條件,就會進行快照。 如果想要禁用自動快照,只需要將所有的save引數刪除即可。
Redis預設會將快照檔案儲存在當前目錄(可CONFIG GET dir來檢視)的dump.rdb檔案中,可以通過配置dir和dbfilename兩個引數分別指定快照檔案的儲存路徑和檔名。
 
Redis實現快照的過程
-  Redis使用fork函式複製一份當前程式(父程式)的副本(子程式);
-  父程式繼續接收並處理客戶端發來的命令,而子程式開始將記憶體中的資料寫入硬碟中的臨時檔案;
-  當子程式寫入完所有資料後會用該臨時檔案替換舊的RDB檔案,至此一次快照操作完成。
-  在執行fork的時候作業系統(類Unix作業系統)會使用寫時複製(copy-on-write)策略,即fork函式發生的一刻父子程式共享同一記憶體資料,當父程式要更改其中某片資料時(如執行一個寫命令 ),作業系統會將該片資料複製一份以保證子程式的資料不受影響,所以新的RDB檔案儲存的是執行fork一刻的記憶體資料。
 
Redis在進行快照的過程中不會修改RDB檔案,只有快照結束後才會將舊的檔案替換成新的,也就是說任何時候RDB檔案都是完整的。這使得我們可以通過定時備份RDB檔案來實 現Redis資料庫備份。RDB檔案是經過壓縮(可以配置rdbcompression引數以禁用壓縮節省CPU佔用)的二進位制格式,所以佔用的空間會小於記憶體中的資料大小,更加利於傳輸。
 
除了自動快照,還可以手動傳送SAVE或BGSAVE命令讓Redis執行快照,兩個命令的區別在於,前者是由主程式進行快照操作,會阻塞住其他請求,後者會通過fork子程式進行快照操作。 Redis啟動後會讀取RDB快照檔案,將資料從硬碟載入到記憶體。根據資料量大小與結構和伺服器效能不同,這個時間也不同。通常將一個記錄一千萬個字串型別鍵、大小為1GB的快照檔案載入到內 存中需要花費20~30秒鐘。 通過RDB方式實現持久化,一旦Redis異常退出,就會丟失最後一次快照以後更改的所有資料。這就需要開發者根據具體的應用場合,通過組合設定自動快照條件的方式來將可能發生的資料損失控制在能夠接受的範圍。如果資料很重要以至於無法承受任何損失,則可以考慮使用AOF方式進行持久化。
 
2)AOF方式
預設情況下Redis沒有開啟AOF(append only file)方式的持久化,可以在redis.conf中通過appendonly引數開啟:
appendonly yes
在啟動時Redis會逐個執行AOF檔案中的命令來將硬碟中的資料載入到記憶體中,載入的速度相較RDB會慢一些
 
開啟AOF持久化後每執行一條會更改Redis中的資料的命令,Redis就會將該命令寫入硬碟中的AOF檔案。AOF檔案的儲存位置和RDB檔案的位置相同,都是通過dir引數設定的,預設的檔名是appendonly.aof,可以通過appendfilename引數修改:
appendfilename appendonly.aof
配置redis自動重寫AOF檔案的條件
 
auto-aof-rewrite-percentage 100  # 當目前的AOF檔案大小超過上一次重寫時的AOF檔案大小的百分之多少時會再次進行重寫,如果之前沒有重寫過,則以啟動時的AOF檔案大小為依據
 
auto-aof-rewrite-min-size 64mb   # 允許重寫的最小AOF檔案大小
配置寫入AOF檔案後,要求系統重新整理硬碟快取的機制
 
# appendfsync always   # 每次執行寫入都會執行同步,最安全也最慢
appendfsync everysec   # 每秒執行一次同步操作

# appendfsync no       # 不主動進行同步操作,而是完全交由作業系統來做(即每30秒一次),最快也最不安全
 
Redis允許同時開啟AOF和RDB,既保證了資料安全又使得進行備份等操作十分容易。此時重新啟動Redis後Redis會使用AOF檔案來恢復資料,因為AOF方式的持久化可能丟失的資料更少

相關文章