Redis 高可靠性體現于:數據盡量少丟失,服務盡量少中斷。AOF 和 RDB 保證了前者,而后者,Redis 采用副本冗余量,即從庫。
Redis主從庫之間采用的是讀寫分離的方式。
讀操作:主庫、從庫都可以接收;
寫操作:首先到主庫執行,然后,主庫將寫操作同步給從庫。
主從庫同步的原理
當我們啟動多個 Redis 實例的時候,它們相互之間就可以通過 replicaof(Redis 5.0 之前使用 slaveof)命令形成主庫和從庫的關系。replicaof
1,第一階段是主從庫間建立連接、協商同步的過程,主要是為全量復制做準備。在這一步,從庫和主庫建立起連接,并告訴主庫即將進行同步,主庫確認回復后,主從庫間就可以開始同步了。
具體來說,從庫給主庫發送 psync 命令,表示要進行數據同步,主庫根據這個命令的參數來啟動復制。psync 命令包含了主庫的 runID 和復制進度 offset 兩個參數。
-》runID,是每個 Redis 實例啟動時都會自動生成的一個隨機 ID,用來唯一標記這個實例。當從庫和主庫第一次復制時,因為不知道主庫的 runID,所以將 runID 設為“?”。
-》offset,此時設為 -1,表示第一次復制。
主庫收到 psync 命令后,會用 FULLRESYNC 響應命令帶上兩個參數:主庫 runID 和主庫目前的復制進度 offset,返回給從庫。從庫收到響應后,會記錄下這兩個參數。
2,在第二階段,主庫將所有數據同步給從庫。從庫收到數據后,在本地完成數據加載。這個過程依賴于內存快照生成的 RDB 文件。從庫接收到 RDB 文件后,會先清空當前數據庫,然后加載 RDB 文件。
為了保證主從庫的數據一致性,主庫會在內存中用專門的 replication buffer,記錄 RDB 文件生成后收到的所有寫操作。
3,第三個階段,主庫會把第二階段執行過程中新收到的寫命令,再發送給從庫。具體的操作是,當主庫完成 RDB 文件發送后,就會把此時 replication buffer 中的修改操作發給從庫,從庫再重新執行這些操作。這樣一來,主從庫就實現同步了。
replication buffer
Redis和客戶端通信也好,和從庫通信也好,Redis都需要給分配一個 內存buffer進行數據交互,客戶端是一個client,從庫也是一個client,我們每個client連上Redis后,Redis都會分配一個client buffer,所有數據交互都是通過這個buffer進行的:Redis先把數據寫到這個buffer中,然后再把buffer中的數據發到client socket中再通過網絡發送出去,這樣就完成了數據交互。所以主從在增量同步時,從庫作為一個client,也會分配一個buffer,只不過這個buffer專門用來傳播用戶的寫命令到從庫,保證主從數據一致,我們通常把它叫做replication buffer。
(主從庫同步中,replication buffer主要是主庫用來同步命令給從庫的,一旦從庫斷聯,replication buffer也不存在了,但repl_backlog_buffer是存在的)
repl_backlog_buffer
它是為了從庫斷開之后,如何找到主從差異數據而設計的環形緩沖區,從而避免全量同步帶來的性能開銷。如果從庫斷開時間太久,repl_backlog_buffer環形緩沖區被主庫的寫命令覆蓋了,那么從庫連上主庫后只能乖乖地進行一次全量同步,所以repl_backlog_buffer配置盡量大一些,可以降低主從斷開后全量同步的概率。而在repl_backlog_buffer中找主從差異的數據后,如何發給從庫呢?這就用到了replication buffer。
只要有從庫存在,這個repl_backlog_buffer就會存在。主庫的所有寫命令除了傳播給從庫之外,都會在這個repl_backlog_buffer中記錄一份,緩存起來。
主從級聯模式
一次全量復制中,對于主庫來說,需要完成兩個耗時的操作:生成 RDB 文件和傳輸 RDB 文件。
如果從庫數量很多,為了避免給主庫的資源使用帶來壓力,我們可以通過“主 - 從 - 從”模式將主庫生成 RDB 和傳輸 RDB 的壓力,以級聯的方式分散到從庫上。
簡單來說,我們在部署主從集群的時候,可以手動選擇一個從庫(比如選擇內存資源配置較高的從庫),用于級聯其他的從庫。然后,我們可以再選擇一些從庫(例如三分之一的從庫),在這些從庫上執行如下命令,讓它們和剛才所選的從庫,建立起主從關系。(replicaof命令)
主從庫間網絡斷了
在 Redis 2.8 之前,如果主從庫在命令傳播時出現了網絡閃斷,那么,從庫就會和主庫重新進行一次全量復制,開銷非常大。
從 Redis 2.8 開始,網絡斷了之后,主從庫會采用增量復制的方式繼續同步。聽名字大概就可以猜到它和全量復制的不同:全量復制是同步所有數據,而增量復制只會把主從庫網絡斷連期間主庫收到的命令,同步給從庫。
當主從庫斷連后,主庫會把斷連期間收到的寫操作命令,寫入repl_backlog_buffer,當從庫斷連又重連之后,通過psync命令告訴主庫自己的slave_repl_offset,然后主庫根據自己的master_repl_offset和slave_repl_offset在repl_backlog_buffer中判斷是需要全量同步還是把兩者之間的命令增量同步給從庫(通過replication buffer)。
通過調大repl_backlog_size,可以減少從庫在網絡斷連時全量復制的風險。
主從全量同步使用RDB而不使用AOF的原因:
1、RDB文件內容是經過壓縮的二進制數據(不同數據類型數據做了針對性優化),文件很小。而AOF文件記錄的是每一次寫操作的命令,寫操作越多文件會變得很大,其中還包括很多對同一個key的多次冗余操作。在主從全量數據同步時,傳輸RDB文件可以盡量降低對主庫機器網絡帶寬的消耗,從庫在加載RDB文件時,一是文件小,讀取整個文件的速度會很快,二是因為RDB文件存儲的都是二進制數據,從庫直接按照RDB協議解析還原數據即可,速度會非常快,而AOF需要依次重放每個寫命令,這個過程會經歷冗長的處理邏輯,恢復速度相比RDB會慢得多,所以使用RDB進行主從全量同步的成本最低。
2、假設要使用AOF做全量同步,意味著必須打開AOF功能,打開AOF就要選擇文件刷盤的策略,選擇不當會嚴重影響Redis性能。而RDB只有在需要定時備份和主從全量同步數據時才會觸發生成一次快照。而在很多丟失數據不敏感的業務場景,其實是不需要開啟AOF的。
replication buffer需要注意的地方
如果主從在傳播命令時,因為某些原因從庫處理得非常慢,那么主庫上的這個buffer就會持續增長,消耗大量的內存資源,甚至OOM。所以Redis提供了client-output-buffer-limit參數限制這個buffer的大小,如果超過限制,主庫會強制斷開這個client的連接,也就是說從庫處理慢導致主庫內存buffer的積壓達到限制后,主庫會強制斷開從庫的連接,此時主從復制會中斷,中斷后如果從庫再次發起復制請求,那么此時可能會導致惡性循環,引發復制風暴,這種情況需要格外注意。
主從一致性
如果從庫同步較慢的話,例如從庫正在執行bigkey操作,那么復制進度就會落后,此時,從庫數據不是強一致性保證。
腦裂
原主庫“假故障”,主從切換后等它從假故障中恢復后,又開始處理請求,這樣一來,就會和新主庫同時存在,形成腦裂。
處理方案:
1,min-slaves-to-write:這個配置項設置了主庫能進行數據同步的最少從庫數量;
2,min-slaves-max-lag:這個配置項設置了主從庫間進行數據復制時,從庫給主庫發送 ACK 消息的最大延遲(以秒為單位)。
把 min-slaves-to-write 和 min-slaves-max-lag 這兩個配置項搭配起來使用,分別給它們設置一定的閾值,假設為 N 和 T。這兩個配置項組合后的要求是,主庫連接的從庫中至少有 N 個從庫,和主庫進行數據復制時的 ACK 消息延遲不能超過 T 秒,否則,主庫就不會再接收客戶端的請求了。
即使原主庫是假故障,它在假故障期間也無法響應哨兵心跳,也不能和從庫進行同步,自然也就無法和從庫進行 ACK 確認了。這樣一來,min-slaves-to-write 和 min-slaves-max-lag 的組合要求就無法得到滿足,原主庫就會被限制接收客戶端請求,客戶端也就不能在原主庫中寫入新數據了。