架構師總結金九最高頻面試技術文件:Redis+Nginx+Dubbo精選+面試題+Java進階精選視訊
Redis-水滴石穿之(五)主從複製
目錄
在上一篇 Rdis 水滴石穿之(四)持久化中,我們介紹了Redis 的三種持久化方式、實現原理和常用配置等,本章我們來討論下Redis的高可用方案之——主從複製。
一、概述
在上一章我們提到過,Redis為了內部資料的安全考慮,會把自身的資料以檔案的形式儲存在硬碟中,這樣在斷電或者重啟之後資料會自動恢復,保證了資料不會丟失,提高了系統應對災難故障的效能,其側重解決的是Redis單機備份的問題,即資料從記憶體到硬碟的備份。
但是如果Redis服務所在的機器硬碟損壞了,即使我們進行了持久化,資料仍然會丟失,所以為了避免這種單點故障,Redis提供了多機熱備方案——主從複製。
主從複製,是指將一臺Redis伺服器的資料,複製到其他的Redis伺服器。前者稱為主節點(master),後者稱為從節點(slave);資料的複製是單向的,只能由主節點到從節點,一主可以有多從,結構如圖,
當然,從機下也可以繼續掛載從節點,就像這樣,
很多企業可能都沒有使用到 Redis 的叢集,但是至少都做了主從。當 master 掛掉的時候,就可以讓從庫過來接管,繼續對外提供服務,否則 master 需要經過資料恢復和重啟的過程,這就可能會拖很長的時間,影響線上業務的持續服務。
主從複製的作用主要包括:
- 資料容災,任何伺服器都有宕機的可能,我們可以通過主從複製功能提升Redis服務的可靠性,因為主伺服器與從伺服器的資料保持同步,一旦主伺服器發生宕機的情況,可以立即將請求切換到從伺服器,從而避免Redis服務中斷。
- 讀寫分離:雖說Redis的效能十分優秀,但畢竟單伺服器能支撐的QPS是有限的,通過主從複製,我們可以部署一臺主伺服器,多臺從伺服器,主伺服器只處理寫請求,從伺服器處理讀請求,以此提升Redis服務能力,尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的併發量;
- 另外我們還可以通過主從複製功能,讓主伺服器免於執行持久化操作,即關閉主伺服器的持久化功能,交由從伺服器執行持久化即可。
主從複製是 Redis 分散式的基礎,Redis 的高可用離開了主從複製將無法進行。後面的章節我們會介紹 Redis 的哨兵模式、叢集模式,這幾種模式都依賴於本章所講的主從複製。
不過主從複製也不是必須的,如果你將 Redis 只用來做快取,也就無需從庫做備份,掛掉了重新啟動一下即可。但是隻要你使用了 Redis 的持久化功能,就必須認真對待主從複製,它是系統資料安全的基礎保障。
下面我們將詳細介紹Redis主從複製的細節及實現原理。
二、主從配置
Redis 主從複製的使用,主伺服器無需配置,只需在從伺服器配置即可,假設現在有兩臺伺服器,127.0.0.1:6379為主伺服器,127.0.0.1:6380為從伺服器,具體的配置方式如下,
-
方式一
在redis.conf配置檔案中,加入slaveof < masterip > < masterport> 即可,
- masterip表示Redis主伺服器ip地址
- masterport表示Redis主伺服器埠號,
比如 slaveof 192.168.1.1 6379
-
方式二
redis-server啟動命令後加入 --slaveof < masterip> < masterport>,比如啟動從伺服器時,可以這樣指定
./redis-server redis.conf --slaveof 127.0.0.1 6379 &
-
方式三
Redis伺服器啟動後,連線到客戶端後執行命令:slaveof < masterip> < masterport>,則該Redis例項成為從節點,比如
127.0.0.1:6380> slaveof 127.0.0.1 6379
以上3種方式都可以建立主從關係,如果想要取消主從關係,可以連線從機客戶端,執行slaveof no one命令斷開。需要注意的是,從節點斷開復制後,不會刪除已有的資料,只是不再接收主節點新的資料變化,從節點執行slaveof no one後,從節點又變回為主節點,即可以正常讀寫資料。
小插曲
有人認為 Redis 中的術語 master/slave (主人 / 奴隸)冒犯到了別人,要求 Redis 作者 ANTIREZ 修改這個術語,事實證明,槓精是不分國界的,且槓精是永遠不會消失的!!!
所以Redis作者不得不做出一些改變,其中一條我們需要注意的是將 SLAVEOF 為 REPLICAOF,雖然仍然可以使用 SLAVEOF,但現在有了選擇, 配置指令也從 slaveof 更改為 replicaof,所以我們現在可以這樣配置,
replicaof 127.0.0.1 6379, 等價於 slaveof 127.0.0.1 6379
需要注意的是
4.0之前只能slaveof
4.0之後預設replicaof,slaveof都起作用.
“當我處理 Redis 社群時,我不想成為它的國王,我需要為這裡的人們服務。“ ——Redis作者
三、實現原理
Redis 的主從資料是非同步同步的,所以分散式的 Redis 系統並不滿足一致性要求。Redis 保證最終一致性,如果網路斷開了,主從節點的資料將會出現不一致,一旦網路恢復,從節點會努力追趕主節點,最終從節點的狀態會和主節點的狀態將保持一致。
3.1、主要流程
我們上面提到主從之間建立關係,是從機發起slaveof命令開始的,下面我們就來看下slaveof的主要流程,
- 根據主節點的ip+port建立socket連線;
- 傳送ping命令,檢查socket連線是否可用;
- 身份認證(如果需要的話);
- 節點資訊同步;
- 從伺服器向主伺服器傳送psync命令(2.8以前是傳送sync命令),請求同步資料;
- 主節點根據收到的psync命令,及當前伺服器狀態,決定執行全量複製還是部分複製;
- 從伺服器接收並載入RDB檔案;
- 主伺服器將緩衝區的命令同步給從伺服器;
- 從機初始化完成後,每當主伺服器接收到寫命令時,都會同步給從伺服器,從伺服器接收並處理主伺服器傳送過來的命令。
上述流程,步驟5中在Redis2.8以前是通過sync命令同步資料的,此時的同步方式為全量同步,但這並不一定是每次建立主從複製所必須的,假設主從建立連線不久後,網路連線發生了問題,在此期間主伺服器只執行了少量的寫命令,待主從網路恢復連線後,從伺服器會重新連線到主伺服器,併發送sync命令請求同步資料,事實上這時主伺服器是不需要再做一次持久化的,只需要主伺服器能夠快取連線故障期間執行的寫命令即可。
在Redis2.8及以後,會根據主從節點當前狀態的不同,同步方式可能是全量同步或部分同步。從節點會記錄已經從主節點接收到的資料複製偏移量;而主節點會維護一個複製緩衝區,記錄自己已執行且待發送給從節點的命令請求,同時還記錄複製緩衝區第一個位元組的複製偏移量。
從節點請求同步資料的命令也改成了psync。當從節點連線到主節點時,會向主節點發送psync命令請求同步資料,同時告訴主節點自己已接收到的複製偏移量,主節點判斷該複製偏移量是否還包含在複製緩衝區中;如果包含,則不需要執行持久化操作,直接向從伺服器傳送複製緩衝區中命令即可,這稱為部分同步;如果不包含,則需要執行持久化操作,同時將所有新執行的寫命令快取到複製緩衝區中,並重置複製緩衝區第一個位元組的複製偏移量,這稱為全量同步,下面我們來介紹全量和部分同步的具體實現過程。
3.2 全量複製
全量複製用於初次複製或其它無法進行部分複製的情況,將主節點中的所有資料全部發送給從節點。
Redis 的全量同步過程主要分同步快照、同步寫緩衝、同步增量三個階段,通過psync命令進行全量複製的過程如下:
1、同步快照階段
(1) 主節點根據收到的psync命令,及當前伺服器狀態,決定執行全量複製還是部分複製,當主節點和從節點都判斷無法進行部分複製時,主節點會執行全量複製;
(2)主節點收到全量複製的命令後,執行bgsave,在後臺生成RDB檔案,並使用一個緩衝區(複製緩衝區)記錄從現在開始執行的所有寫命令。
這裡需要注意的是,RDB持久化時可以有兩種選擇:
- 直接通過Socket傳送給從伺服器,從伺服器支援“eof”功能時,主伺服器便可以直接將資料庫中的資料以RDB協議格式通過 Socket傳送給從伺服器,免去了本地磁碟檔案不必要的讀寫操作。
- 持久化資料到本地檔案, 待持久化完成後再將該檔案傳送給從伺服器。
其中變數repl_diskless_sync可通過配置引數repl-diskless-sync進行設定,預設為0;即預設情況下,主伺服器都是先持久化資料到本地,再將該檔案傳送給從伺服器。
(3)主節點的bgsave執行完成後,將RDB檔案傳送給從節點;從節點需要先清除自己的舊資料,然後載入接收到的RDB檔案,將資料庫狀態更新至主節點執行bgsave時的資料庫狀態。
2、同步寫緩衝階段
主節點將複製緩衝區中的所有寫命令傳送給從節點,從節點執行這些寫命令,將資料庫狀態更新至主節點的最新狀態。
3、同步增量階段
當所有流程執行完畢後,主伺服器每次接收到寫命令請求時,都會將該命令請求廣播給所有從伺服器,同時記錄在複製緩衝區中。
3.3、部分複製
部分複製用於網路中斷等情況後的複製,只將中斷期間主節點執行的寫命令傳送給從節點,與全量複製相比更加高效。需要注意的是,如果網路中斷時間過長,導致主節點沒有能夠完整地儲存中斷期間執行的寫命令,則無法進行部分複製,仍需使用全量複製。
關於判斷是否執行全量複製還是部分複製具體流程圖如下,
可以看到執行部分同步的要求還是比較嚴格的,首先RUN_ID必須相等,其次複製偏移量必須包含在複製緩衝區中,這裡引出幾個比較重要的概念,偏移量OFFSET、複製緩衝區、RUN_ID。
-
偏移量OFFSET
主節點和從節點分別維護一個偏移量offset,代表主節點向從節點傳遞的位元組數,偏移量初始化為-1( 如果最近執行了slaveof no one 偏移量值也為-1);主節點每次向從節點傳播N個位元組資料時,主節點的offset加N;從節點每次收到主節點傳來的N個位元組資料時,從節點的offset加N。
offset主要用於判斷主從節點的資料庫狀態是否一致,如果二者的offset相同,則一致;如果offset不同,則不一致,此時可以根據兩個offset找出從節點少的那部分資料。
-
複製緩衝區
複製緩衝區是一個先進先出的迴圈佇列,當寫入資料量超過緩衝區大小時,舊的資料會被覆蓋。因此隨著每次資料的寫入,需要更新緩衝區中資料第一個位元組的複製偏移量repl_backlog_off,同時記錄下次寫入資料時的 索引位置repl_backlog_idx,以及當前緩衝區中有效資料長度 repl_backlog_histlen。
當主從節點offset的差距過大超過緩衝區長度時,將無法執行部分複製,只能執行全量複製。
-
RUN_ID
每臺Redis伺服器在啟動時都會自動生成一個隨機ID,由40個隨機的十六進位制字元組成,用來唯一識別一個Redis節點。可以通過info Server命令檢視節點的RUN_ID,需要注意的是,RUN_ID每次重新啟動都會發生變化。
從伺服器每次傳送psync請求同步資料時,會攜帶自己需要同步主伺服器的RUN_ID。主伺服器接收到psync命令時,需要判斷命令引數RUN_ID與自己的RUN_ID是否相等,只有相等才有可能執行部分重同步。而當從伺服器首次請求主伺服器同步資料時,從伺服器顯然是不知道主伺服器的執行ID,此時執行ID以“?”填充。
介紹完部分複製依賴的相關概念後,我們回過頭再來看這個流程圖,如果從節點之前未執行過slaveof或最近執行了slaveof no one,則從節點發送命令為psync ? -1,向主節點請求全量複製;如果從節點之前執行了slaveof,則傳送命令為psync runid offset,其中runid為上次複製的主節點的runid,offset為上次複製截止時從節點儲存的複製偏移量。
當滿足執行部分複製條件時,主伺服器會將該節點新增到自己的從服務連結串列slaves,並向客戶端返回“+CONTINUE”,表明可以執行部分複製, 接下來主伺服器會根據PSYNC請求引數中的複製偏移量,將複製緩衝區中的部分命令請求同步給從伺服器。由於有新的從伺服器連線成功,主伺服器還需要更新有效從伺服器數目。
然而在生產環境中,經常會出現以下兩種情況:
- 從伺服器重啟(複製資訊丟失);
- 主伺服器故障導致主從切換(從多個從伺服器重新選舉出一臺機器作為主伺服器,主伺服器執行ID發生改變)。
這時候顯然是無法執行部分重同步的,而這兩種情況又很常見, 因此Redis 4.0針對主從複製又提出了兩點優化,提出了psync2協議,
-
優化1:持久化主從複製資訊。
Redis伺服器關閉時,將主從複製資訊(複製的主伺服器RUN_ID 與複製偏移量)作為輔助欄位儲存在RDB檔案中;Redis伺服器啟動載入RDB檔案時,恢復主從複製資訊,重新同步主伺服器時攜帶。
-
方案2:從機儲存上一個主伺服器複製資訊。
預留兩個欄位,當主伺服器發生故障,自己成為新的主伺服器時,便使用這兩個欄位儲存之前主伺服器的RUN_ID與複製偏移量。
3.4、心跳機制
主伺服器和從伺服器之間是通過TCP長連線互動資料的,就必然需要週期性地傳送心跳包來檢測連線有效性,可通過配置引數repl-ping-replica-period或者repl-pingslave-period設定,預設為10。該欄位表示傳送心跳包的週期,主伺服器以此週期向所有從伺服器傳送心跳包。
此外從伺服器通過命令“REPLCONF ACK reploff”定時向主伺服器彙報自己的複製偏移量,主伺服器使用變數repl_ack_time儲存接收到該命令的時間,以此作為檢測從伺服器是否有效的標準。
超時判斷
在複製連線建立過程中及之後,主從節點都有機制判斷連線是否超時,其意義在於:
(1)如果主節點判斷連線超時,其會釋放對應從節點的連線,從而釋放各種資源,否則無效的從節點仍會佔用主節點的各種資源包括輸出緩衝區、頻寬、連線等;此外連線超時的判斷可以讓主節點更準確的知道當前有效從節點的個數,有助於保證資料安全。
(2)如果從節點判斷連線超時,則可以及時重新建立連線,避免與主節點資料長期的不一致。
判斷機制
主從複製超時判斷的核心,在於repl-timeout引數,該引數規定了超時時間的閾值,預設60s,對於主節點和從節點同時有效;主從節點觸發超時的條件分別如下:
(1)主節點:每秒1次呼叫複製定時函式replicationCron(),在其中判斷當前時間距離上次收到各個從節點REPLCONF ACK的時間,是否超過了repl-timeout值,如果超過了則釋放相應從節點的連線。
(2)從節點:從節點對超時的判斷同樣是在複製定時函式中判斷,基本邏輯是:
- 如果當前處於連線建立階段,且距離上次收到主節點的資訊的時間已超過repl-timeout,則釋放與主節點的連線;
- 如果當前處於同步快照階段,且收到主節點的RDB檔案的時間超時,則停止資料同步,釋放連線;
- 如果當前處於同步增量階段,且距離上次收到主節點的PING命令或資料的時間已超過repl-timeout值,則釋放與主節點的連線。
四、複製相關的配置
配置大致可以分為主節點相關配置、從節點相關配置以及與主從節點都有關的配置,下面分別說明。
4.1、與主從節點都有關的配置
首先介紹最特殊的配置,它決定了該節點是主節點還是從節點:
-
slaveof < masterip > < masterport>:作用是建立複製關係,開啟了該配置的Redis伺服器在啟動後成為從節點。預設註釋掉不起作用,即Redis伺服器預設都是主節點。
-
repl-timeout 60:與各個階段主從節點連線超時判斷有關。
4.2、主節點相關配置
-
repl-diskless-sync no:作用於全量複製階段,控制主節點是否使用diskless複製(無盤複製)。所謂diskless複製,是指在全量複製時,主節點不再先把資料寫入RDB檔案,而是直接寫入slave的socket中,整個過程中不涉及硬碟;diskless複製在磁碟IO很慢而網速很快時更有優勢。預設是關閉的。
-
repl-diskless-sync-delay 5:該配置作用於全量複製階段,當主節點使用diskless複製時,該配置決定主節點向從節點發送之前停頓的時間,單位是秒;只有當diskless複製開啟時有效,預設5s。之所以設定停頓時間,是基於以下兩個考慮:
- 向slave的socket的傳輸一旦開始,新連線的slave只能等待當前資料傳輸結束,才能開始新的資料傳輸
- 多個從節點有較大的概率在短時間內建立主從複製。
-
client-output-buffer-limit slave 256MB 64MB 60:與全量複製階段主節點的緩衝區大小有關,見前面的介紹。
-
repl-disable-tcp-nodelay no:與命令傳播階段的延遲有關。
-
masterauth :與連線建立階段的身份驗證有關。
-
repl-ping-slave-period 10:與命令傳播階段主從節點的超時判斷有關。
-
repl-backlog-size 1mb:複製積壓緩衝區的大小。
-
repl-backlog-ttl 3600:當主節點沒有從節點時,複製積壓緩衝區保留的時間,這樣當斷開的從節點重新連進來時,可以進行部分複製;預設3600s。如果設定為0,則永遠不會釋放複製積壓緩衝區。
-
min-slaves-to-write 3與min-slaves-max-lag 10:規定了主節點的最小從節點數目,及對應的最大延遲。
4.3、從節點相關配置
-
slave-serve-stale-data yes:與從節點資料陳舊時是否響應客戶端命令有關。
-
slave-read-only yes:從節點是否只讀;預設是隻讀的。由於從節點開啟寫操作容易導致主從節點的資料不一致,因此該配置儘量不要修改。
五、總結
本章節我們主要介紹了Redis主從複製的作用、應用、實現原理及相關配置。
主從複製雖然可以解決資料容災、故障恢復、讀負載均衡等問題,但細想其中還是會存在一些問題或不足的地方,比如發生故障時,不能自主進行主從切換;讀可負載,但是寫無法負載等。所以Redis提供了哨兵和叢集等機制來完善提高整個系統的能力,在下一章Redis-水滴石穿之(六)哨兵我們會介紹Redis的哨兵機制,不見不散,感謝關注。
六、參考文獻
《Redis設計與實現》
《Redis設計與原始碼分析》
https://www.cnblogs.com/kismetv/9236731.html
http://www.redis.cn/
https://www.cnblogs.com/qtxdy/p/7723774.html
每日一皮
生而為人,我很抱歉 -----太宰治
如果可以的話,我希望我們永遠不要有機會對自己說這句話!