很多企業都沒有使用到 Redis 的集群,但是至少都做了主從。有了主從,當 master 掛掉的時候,運維讓從庫過來接管,服務就可以繼續,否則 master 需要經過數據恢復和重啟的過程,這就可能會拖很長的時間,影響線上業務的持續服務。
在了解 Redis 的主從復制之前,讓我們先來理解一下現代分布式系統的理論基石——CAP 原理。
CAP 原理
CAP 原理就好比分布式領域的牛頓定律,它是分布式存儲的理論基石。自打 CAP 的論文發表之后,分布式存儲中間件猶如雨后春筍般一個一個涌現出來。理解這個原理其實很簡單,本節我們首先對這個原理進行一些簡單的講解。
C - Consistent ,一致性
A - Availability ,可用性
P - Partition tolerance ,分區容忍性
分布式系統的節點往往都是分布在不同的機器上進行網絡隔離開的,這意味着必然會有網絡斷開的風險,這個網絡斷開的場景的專業詞匯叫着「網絡分區」。在網絡分區發生時,兩個分布式節點之間無法進行通信,我們對一個節點進行的修改操作將無法同步到另外一個節點,所以數據的「一致性」將無法滿足,因為兩個分布式節點的數據不再保持一致。除非我們犧牲「可用性」,也就是暫停分布式節點服務,在網絡分區發生時,不再提供修改數據的功能,直到網絡狀況完全恢復正常再繼續對外提供服務。

一句話概括 CAP 原理就是——網絡分區發生時,一致性和可用性兩難全。
最終一致
Redis 的主從數據是異步同步的,所以分布式的 Redis 系統並不滿足「一致性」要求。當客戶端在 Redis 的主節點修改了數據后,立即返回,即使在主從網絡斷開的情況下,主節點依舊可以正常對外提供修改服務,所以 Redis 滿足「可用性」。Redis 保證「最終一致性」,從節點會努力追趕主節點,最終從節點的狀態會和主節點的狀態將保持一致。如果網絡斷開了,主從節點的數據將會出現大量不一致,一旦網絡恢復,從節點會采用多種策略努力追趕上落后的數據,繼續盡力保持和主節點一致。
主從同步
Redis 同步支持主從同步和從從同步,從從同步功能是 Redis 后續版本增加的功能,為了減輕主庫的同步負擔。后面為了描述上的方便,統一理解為主從同步。

增量同步
Redis 同步的是指令流,主節點會將那些對自己的狀態產生修改性影響的指令記錄在本地的內存 buffer 中,然后異步將 buffer 中的指令同步到從節點,從節點一邊執行同步的指令流來達到和主節點一樣的狀態,一遍向主節點反饋自己同步到哪里了 (偏移量)。因為內存的 buffer 是有限的,所以 Redis 主庫不能將所有的指令都記錄在內存 buffer中。Redis 的復制內存 buffer 是一個定長的環形數組,如果數組內容滿了,就會從頭開始覆蓋前面的內容。

如果因為網絡狀況不好,從節點在短時間內無法和主節點進行同步,那么當網絡狀況恢復時,Redis 的主節點中那些沒有同步的指令在 buffer 中有可能已經被后續的指令覆蓋掉了,從節點將無法直接通過指令流來進行同步,這個時候就需要用到更加復雜的同步機制 —— 快照同步。
快照同步
快照同步是一個非常耗費資源的操作,它首先需要在主庫上進行一次 bgsave 將當前內存的數據全部快照到磁盤文件中,然后再將快照文件的內容全部傳送到從節點。從節點將快照文件接受完畢后,立即執行一次全量加載,加載之前先要將當前內存的數據清空。加載完畢后通知主節點繼續進行增量同步。在整個快照同步進行的過程中,主節點的復制 buffer 還在不停的往前移動,如果快照同步的時間過長或者復制 buffer 太小,都會導致同步期間的增量指令在復制 buffer 中被覆蓋,這樣就會導致快照同步完成后無法進行增量復制,然后會再次發起快照同步,如此極有可能會陷入快照同步的死循環。

所以務必配置一個合適的復制 buffer 大小參數,避免快照復制的死循環。
增加從節點
當從節點剛剛加入到集群時,它必須先要進行一次快照同步,同步完成后再繼續進行增量同步。
無盤復制
主節點在進行快照同步時,會進行很重的文件 IO 操作,特別是對於非 SSD 磁盤存儲時,快照會對系統的負載產生較大影響。特別是當系統正在進行 AOF 的 fsync 操作時如果發生快照,fsync 將會被推遲執行,這就會嚴重影響主節點的服務效率。所以從 Redis 2.8.18 版開始支持無盤復制。所謂無盤復制是指主服務器直接通過套接字將快照內容發送到從節點,生成快照是一個遍歷的過程,主節點會一邊遍歷內存,一遍將序列化的內容發送到從節點,從節點還是跟之前一樣,先將接收到的內容存儲到磁盤文件中,再進行一次性加載。
Wait 指令
Redis 的復制是異步進行的,wait 指令可以讓異步復制變身同步復制,確保系統的強一致性 (不嚴格)。wait 指令是 Redis3.0 版本以后才出現的。
> set key value
OK
> wait 1 0
(integer) 1
wait 提供兩個參數,第一個參數是從庫的數量 N,第二個參數是時間 t,以毫秒為單位。它表示等待 wait 指令之前的所有寫操作同步到 N 個從庫 (也就是確保 N 個從庫的同步沒有滯后),最多等待時間 t。如果時間 t=0,表示無限等待直到 N 個從庫同步完成達成一致。假設此時出現了網絡分區,wait 指令第二個參數時間 t=0,主從同步無法繼續進行,wait 指令會永遠阻塞,Redis 服務器將喪失可用性。