前言
關系數據庫通常會使用一個主服務器向多個從服務器發送更新,並使用從服務器來處理所有的讀請求,Redis采用了同樣方法來實現自己的復制特性。
簡單總結起來就是:在接收到主服務器發送的數據初始副本之后,客戶端每次主服務器進行寫命令時,從服務器都會實時地得到更新。部署好主從服務器之后,客戶端可以向任意的從服務器發送讀請求。
本文主要介紹Redis實現數據同步復制簡單過程、新舊版本之間的對比、復制的實現、以及一些需要注意的細節,但不會涉及Redis源碼解釋。只對基本流程及功能進行詳細描述。
主要參考資料《Redis實戰》與《Redis設計與實現》(有需要的同學可以私信或者評論)
一、舊版復制功能
Redis 2.8以前采用的復制都為舊版復制,主要使用SYNC命令同步復制,SYNC存在很大的缺陷嚴重消耗主服務器的資源以及大量的網絡連接資源。Redis 2.8之后采用PSYNC命令替代SYNC,解決完善這些缺陷,但在介紹新版復制功能之前,必須先介紹舊版復制過程,這樣才能更好地形成對比。
1、復制功能的兩種模式
同時復制過程分為同步sync與命令傳播(command propagate),兩個過程配合執行才能實現Redis復制。
1)同步操作:
通過從服務器發送到SYNC命令給主服務器-------->主服務器生成RDB文件並發送給從服務器,同時發送保存所有寫命令給從服務器------>從服務器清空之前數據並執行解釋RDB文件------->保持數據一致(還需要命令傳播過程才能保持一致)
2)命令傳播操作:
主服務器的數據庫狀態被修改(主服務器執行寫命令,修改數據庫),導致主從服務器數據庫不一致時,通過發送讓主從服務器不一致的命令(主服務器接收到的新寫命令)給從服務器並執行,讓主從服務器的數據庫重新回到一致狀態。
比如初次同步完成后,主從服務器數據庫中都已經存在k1-k5的鍵,處於數據一致的狀態
之后,主服務器客戶端發送DEL刪除命令,刪除k3鍵,導致主從服務器數據不一致
為了讓主從服務器數據再次回到一致狀態,主服務器向從服務器發送DEL命令,緊接着從服務器接收並執行。即可回到一致狀態
2、配置選項前提
1)主從復制的前提不用多說,就是先正確配置redis主從服務器,主要通過slaveof ip port選項配置或者SLAVEOF 命令。
2)保證主服務器的RDB+AOF配置正確,特別是RDB中dbfilename選項與AOF中的dir選項,兩個文件路徑對於Redis是可寫的
3、主從復制過程
文字簡單總結描述:
1)slave會建立和master的連接,然后發送sync命令;
2)master都會啟動一個后台進程執行BGSAVE命令,將數據快快照保存到文件中,同時master主進程會開始收集新的寫命令並緩存起來;、
3)后台進程完成寫文件后,master發送文件給slave,slave將文件保存到磁盤上,然后加載到內存恢復數據庫快照到slave上。\
4)緊接着master就會把緩存命令轉發給slave,后續的master收到的寫命令也通過跟slave連接發送給slave;
5)如果master同時接收到多個slave發來的同步連接請求,只會啟動一個進程來寫數據庫鏡像,然后發送給所有slave。
也可以參考以下表,其中步驟1-4可以認為是sync同步操作,而步驟5即為命令傳播模式
注意事項
1)從服務器在同步時,會清空所有數據,服務器在與主服務器進行初連接時,數據庫中的所有數據都將丟失,替換成主服務器發送的數據。
2)Redis不支持主主復制
3)主從復制不會阻塞master(不會阻塞master處理客戶端請求),相反slave在初次同步數據時會阻塞不能處理客戶端請求。
4)當多個從服務器嘗試連接同一個主服務器的時候,就會出現以下兩種情況:
一是:步驟3還未執行,所有從服務器都會接收到相同的快照文件和相同緩沖區寫命令。
二是:步驟3正在執行或者已經執行完畢,當主服務器與較早的從服務器完成以上全部步驟之后,主服務器會新連接的從服務器重新依次執行1-5步驟。
在大部分情況下,Redis會盡可能去減少復制所需要的工作,但是從服務器連接的時機不湊巧的話,只好多做一些外額外工作。
5)多個從服務器連接主服務器時候,同步數據可能會占用很大一部分的帶寬,可能會導致其他請求難以到達主服務器。
4、SYNC命令的缺陷
主要是主從服務器斷線后重復制,即處於命令傳播階段的主從服務器由於網絡斷開,從服務器一直嘗試連接主服務器連接成功后,繼續復制主服務器。如下過程在主從服務器斷開后重新連接期間,主服務器繼續執行三個SET命令,導致從服務器連接后發送L SYNC命令,重新進行了“全量”復制過程,RDB文件中包含k1-k10089全部的鍵。
其中可以明顯看出重新連接主服務器之后,SYNC命令創建包含k1-k10089的RDB文件。而事實上只需要再同步斷線后的k10087-k10089即可。SYNC的“全同步”對於從服務來說是不必要的。
SYNC命令非常消耗資源,原因有三點:
1)主服務器執行BGSAVE命令生成RDB文件,這個生成過程會大量消耗主服務器資源(CPU、內存和磁盤I/O資源)
2)主服務器需要將自己生成的RBD文件發送給從從服務器,這個發送操作會消耗主從服務器大量的網絡資源(帶寬與流量)
3)接收到RDB文件你的從服務器需要載入RDB文件,載入期間從服務器會因為阻塞而導致沒辦法處理命令請求。
二、新版復制功能
為了解決舊版本中斷線情況下SYNC低效問題,在Redis 2.8之后使用PSYNC命令代替SYNC命令執行復制同步操作,自然PSYNC具備完整重同步和部分重同步模式
1)完整重同步:跟舊版復制基本是一致的,可以理解為“全量”復制。
2)部分重同步:在命令傳播階段,斷線重復制只需要發送主服務器在斷開期間執行的寫命給從服務器即可,可以理解為“增量”復制。
斷開連接后發送+CONTINUE回復,表示使用PSYNC部分重同步,只需要同步k10087-10089即可,不需要生成RDB文件
相關流程圖如下:
三、復制的實現
Redis不管是舊版還是新版,復制的實現都可以分為七個步驟,流程圖如下:
1、設置主服務的地址與端口
當客戶端向從服務器發送一下命令時或者在配置文件中配置slaveof選項
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
從服務器將向發送SLAVEOF命令的客戶端返回OK,表示復制指令已經被接收,而實際上復制工作是在OK返回之后進行。
2、建立套接字連接
從服務器根據設置的套接字創建連向主服務器的套接字連接
主服務器接收從服務器的套接字連接之后,為該套接字創建響應的客戶端狀態,並將此時的從服務器看做是主服務器的客戶端,也就是該從服務器同時具備服務器與客戶端兩個身份。
3、發送PING命令
從服務器成為主服務器的客戶端之后,做的第一件事就是向主服務器發送PING命令。PING命令主要有兩種作用:
1) 雖然建立了套接字連接,但是還未使用過,通過發送PING命令檢查套接字的讀寫狀態是否正常
2) 通過發送PING命令檢查主服務器能否正常處理命令請求
從服務器在發送PING命令之后將遇到以下三種情況的其中一種:
1) 主服務器向從服務器返回一個回復,但是從服務器卻不能在規定的會時間(timeout)內讀取命令回復的內容,則表示當前主從服務器之間的網絡狀態連接不佳,不能基礎執行復制工作的后續步驟,這時從服務器會斷開套接字連接重新創建。
2) 主服務器向從服務器返回一個錯誤,那么表示主服務器暫時沒有辦法處理從服務器的命令請求,不能繼續執行復制工作的后續步驟,這時從服務器會斷開套接字連接重新創建。
3) 如果從服務器讀取到“PONG”回復,那么表示主從服務器之間網絡連接正常,並且主服務器可以處理從服務器發送的命令請求。
4、身份驗證
從服務器接收到主服務器返回的“PONG”回復,接下來就需要考慮身份驗證的事。
如果從服務器設置了masterauth選項,那么進行身份驗證
如果從服務器沒有設置masterauth選項,那么不進行身份驗證
從服務器在身份驗證的時候可能遇到三種情況
1) 主服務沒有設置requirepass選項,並且從服務器也沒有設置masterquth選項,那么主服務器繼續執行從服務器命令,完成復制工作
2) 如果從服務器通過AUTH命令發送的密碼與主服務器中requirepass密碼相同,那么主服務器將繼續執行從服務器發送的命令,復制工作繼續,與此相反,密碼不一致,則會返回invalid password錯誤
3) 如果從服務器沒有設置masterauth選項,而主服務器設置了requirepass選項,那么主服務器將返回一個NOAUTH錯誤。反之沒有設設置masterauth選項,而設置了requirepass選項,那么會返回no password is set錯誤。
5、發送端口信息
在身份驗證步驟之后,從服務器將執行命令REPLCONF listening-port <port>,向主服務器發送從服務器的監聽端口號。
6、同步
就是上述所指的同步操作,從服務器向主服務器發送PSYNC命令,執行同步操作,值得注意的是只有從服務器是主服務器的客戶端,但是執行同步操作之后,主服務器也會成為從服務器的客戶端。
7、命令傳播
主從服務器就會進入命令傳播階段,主服務器只要將自己執行的寫命令發送給從服務器,而從服務器只要一直執行並接收主服務器發來的寫命令(上述已經介紹過,這里不過多介紹)