高可用有兩個含義:一是數據盡量不丟失,二是服務盡可能提供服務。 AOF 和 RDB 保證了數據持久化盡量不丟失,而主從復制就是增加副本,一份數據保存到多個實例上。即使有一個實例宕機,其他實例依然可以提供服務。因此帶大家全方位吃透 Redis 高可用技術解決方案之一主從復制架構。(本文很重要的一部分內容原封參考知乎的【碼哥字節】)
核心知識點如下:

1、主從復制概述
問:有了 RDB 和 AOF 再也不怕宕機丟失數據了,但是 Redis 實例宕機了怎么實現高可用呢?
既然一臺宕機了無法提供服務,那多臺呢?是不是就可以解決了。Redis 提供了主從模式,通過主從復制,將數據冗余一份復制到其他 Redis 服務器。前者稱為主節點 (master),后者稱為從節點 (slave);數據的復制是單向的,只能由主節點到從節點。
默認情況下,每臺 Redis 服務器都是主節點;且一個主節點可以有多個從節點 (或沒有從節點),但一個從節點只能有一個主節點。
問:主從之間的數據如何保證一致性呢?
為了保證副本數據的一致性,主從架構采用了讀寫分離的方式。
- 讀操作:主、從庫都可以執行;
- 寫操作:主庫先執行,之后將些操作同步到從庫;

問:為何要采用讀寫分離的方式?
我們可以假設主從庫都可以執行寫指令,假如對同一份數據分別修改了多次,每次修改發送到不同的主從實例上,就導致是實例的副本數據不一致了。如果為了保證數據一致,Redis 需要加鎖,協調多個實例的修改,Redis 自然不會這么干!
問:主從復制還有其他作用么?
- 故障恢復:當主節點宕機,其他節點依然可以提供服務;
- 負載均衡:Master 節點提供寫服務,Slave 節點提供讀服務,分擔壓力;
- 高可用基石:是哨兵和 cluster 實施的基礎,是高可用的基石。
2、搭建主從復制
主從復制的開啟,完全是在從節點發起的,不需要我們在主節點做任何事情。
問:怎么搭建主從復制架構呀?
可以通過 replicaof(Redis 5.0 之前使用 slaveof)命令形成主庫和從庫的關系。在從節點開啟主從復制,有 3 種方式:
- 配置文件:在從服務器的配置文件中加入
replicaof <masterip> <masterport> - 啟動命令:redis-server 啟動命令后面加入
--replicaof <masterip> <masterport> - 客戶端命令:啟動多個 Redis 實例后,直接通過客戶端執行命令:
replicaof <masterip> <masterport>,則該 Redis 實例成為從節點。
比如假設現在有實例 1(172.16.88.1)、實例 2(172.16.88.2)和實例 3 (172.16.88.3),在實例 2 和實例 3 上分別執行以下命令,實例 2 和 實例 3 就成為了實例 1 的從庫,實例 1 成為 Master。
replicaof 172.16.88.1 6379
3、主從復制原理
主從庫模式一旦采用了讀寫分離,所有數據的寫操作只會在主庫上進行,不用協調三個實例。主庫有了最新的數據后,會同步給從庫,這樣,主從庫的數據就是一致的。
問:主從庫同步是如何完成的呢?主庫數據是一次性傳給從庫,還是分批同步?正常運行中又怎么同步呢?要是主從庫間的網絡斷連了,重新連接后數據還能保持一致嗎?
同步分為三種情況:
- 第一次主從庫全量復制;
- 主從正常運行期間的同步;
- 主從庫間網絡斷開重連同步。
3.1、主從庫第一次全量復制
主從庫第一次復制過程大體可以分為 3 個階段:連接建立階段(即準備階段)、主庫同步數據到從庫階段、發送同步期間新寫命令到從庫階段,如下圖所示:

3.1.1、建立連接
該階段的主要作用是在主從節點之間建立連接,為數據全量同步做好準備。從庫會和主庫建立連接,從庫執行 replicaof 并發送 psync 命令并告訴主庫即將進行同步,主庫確認回復后,主從庫間就開始同步了。
問:從庫怎么知道主庫信息并建立連接的呢?
在從節點的配置文件中的 replicaof 配置項中配置了主節點的 IP 和 port 后,從節點就知道自己要和那個主節點進行連接了。從節點內部維護了兩個字段,masterhost 和 masterport,用于存儲主節點的 IP 和 port 信息。從庫執行 replicaof 并發送 psync 命令,表示要執行數據同步,主庫收到命令后根據參數啟動復制。命令包含了主庫的 runID 和 復制進度 offset 兩個參數。
- runID:每個 Redis 實例啟動都會自動生成一個 唯一標識 ID,第一次主從復制,還不知道主庫 runID,參數設置為 「?」。
- offset:第一次復制設置為 -1,表示第一次復制,記錄復制進度偏移量。
主庫收到 psync 命令后,會用 FULLRESYNC 響應命令帶上兩個參數:主庫 runID 和主庫目前的復制進度 offset,返回給從庫。從庫收到響應后,會記錄下這兩個參數。FULLRESYNC 響應表示第一次復制采用的全量復制,也就是說,主庫會把當前所有的數據都復制給從庫。
3.1.2、主庫同步數據給從庫
該階段master fork一個子線程執行 bgsave命令生成 RDB 文件,阻塞是微秒級,對服務主進程影響很小,并將文件發送給從庫,同時主庫為每一個 slave 開辟一塊 replication buffer 緩沖區記錄從生成 RDB 文件開始收到的所有寫命令。從庫收到 RDB 文件后保存到磁盤,并清空當前數據庫的數據,再加載 RDB 文件數據到內存中。
3.1.3、發送新寫命令到從庫
該階段從節點加載 RDB 完成后,主節點將 replication buffer 緩沖區的數據發送到從節點,Slave 接收并執行,從節點同步至主節點相同的狀態。
問:主庫將數據同步到從庫過程中,可以正常接受請求么?
主庫不會被阻塞,在生成 RDB 文件之后的寫操作并沒有記錄到剛剛的 RDB 文件中,為了保證主從庫數據的一致性,所以主庫會在內存中使用一個叫 replication buffer 記錄 RDB 文件生成后的所有寫操作。
問:為啥從庫收到 RDB 文件后要清空當前數據庫?
因為從庫在通過 replcaof命令開始和主庫同步前可能保存了其他數據,防止主從數據之間的影響。
問replication buffer 到底是什么?
一個在 master 端上創建的緩沖區,存放的數據是下面三個時間內所有的 master 數據寫操作。
- master 執行 bgsave 產生 RDB 的期間的寫操作;
- master 發送 rdb 到 slave 網絡傳輸期間的寫操作;
- slave load rdb 文件把數據恢復到內存的期間的寫操作。
Redis 和客戶端通信也好,和從庫通信也好,Redis 都分配一個內存 buffer 進行數據交互,客戶端就是一個 client,從庫也是一個 client,我們每個 client 連上 Redis 后,Redis 都會分配一個專有 client buffer,所有數據交互都是通過這個 buffer 進行的。Master 先把數據寫到這個 buffer 中,然后再通過網絡發送出去,這樣就完成了數據交互。不管是主從在增量同步還是全量同步時,master 會為其分配一個 buffer ,只不過這個 buffer 專門用來傳播寫命令到從庫,保證主從數據一致,我們通常把它叫做 replication buffer。
問:replication buffer 太小會引發什么問題?
replication buffer 由 client-output-buffer-limit slave 設置,當這個值太小會導致主從復制連接斷開。
- 當 master-slave 復制連接斷開,master 會釋放連接相關的數據。replication buffer 中的數據也就丟失了,此時主從之間重新開始復制過程。
- 還有個更嚴重的問題,主從復制連接斷開,導致主從上出現重新執行 bgsave 和 rdb 重傳操作無限循環。
當主節點數據量較大,或者主從節點之間網絡延遲較大時,可能導致該緩沖區的大小超過了限制,此時主節點會斷開與從節點之間的連接;這種情況可能引起全量復制 -> replication buffer 溢出導致連接中斷 -> 重連 -> 全量復制 -> replication buffer 緩沖區溢出導致連接中斷……的循環。具體詳情:[top redis headaches for devops – replication buffer] 因而推薦把 replication buffer 的 hard/soft limit 設置成 512M。
config set client-output-buffer-limit "slave 536870912 536870912 0"
問:主從庫復制為何不使用 AOF 呢?相比 RDB 來說,丟失的數據更少。
原因如下:
- RDB 文件是二進制文件,網絡傳輸 RDB 和寫入磁盤的 IO 效率都要比 AOF 高。
- 從庫進行數據恢復的時候,RDB 的恢復效率也要高于 AOF。
3.2、增量復制
3.2.1、增量復制
問:主從庫間的網絡斷了咋辦?斷開后要重新全量復制么?
在 Redis 2.8 之前,如果主從庫在命令傳播時出現了網絡閃斷,那么,從庫就會和主庫重新進行一次全量復制,開銷非常大。從 Redis 2.8 開始,網絡斷了之后,主從庫會采用增量復制的方式繼續同步。
增量復制:用于網絡中斷等情況后的復制,只將中斷期間主節點執行的寫命令發送給從節點,與全量復制相比更加高效。
問:repl_backlog_buffer是什么?
斷開重連增量復制的實現奧秘就是 repl_backlog_buffer 緩沖區,不管在什么時候 master 都會將寫指令操作記錄在 repl_backlog_buffer 中,因為內存有限, repl_backlog_buffer 是一個定長的環形數組,如果數組內容滿了,就會從頭開始覆蓋前面的內容。master 使用 master_repl_offset記錄自己寫到的位置偏移量,slave 則使用 slave_repl_offset記錄已經讀取到的偏移量。master 收到寫操作,偏移量則會增加。從庫持續執行同步的寫指令后,在 repl_backlog_buffer 的已復制的偏移量 slave_repl_offset 也在不斷增加。正常情況下,這兩個偏移量基本相等。在網絡斷連階段,主庫可能會收到新的寫操作命令,所以 master_repl_offset會大于 slave_repl_offset。

當主從斷開重連后,slave 會先發送 psync 命令給 master,同時將自己的 runID,slave_repl_offset發送給 master。master 只需要把 master_repl_offset與 slave_repl_offset之間的命令同步給從庫即可。增量復制執行流程如下圖:
問:repl_backlog_buffer 太小的話從庫還沒讀取到就被 Master 的新寫操作覆蓋了咋辦?
我們要想辦法避免這個情況,一旦被覆蓋就會執行全量復制。我們可以調整 repl_backlog_size 這個參數用于控制緩沖區大小。計算公式:
repl_backlog_buffer = second * write_size_per_second
- second:從服務器斷開重連主服務器所需的平均時間;
- write_size_per_second:master 平均每秒產生的命令數據量大小(寫命令和數據大小總和);
例如,如果主服務器平均每秒產生 1 MB 的寫數據,而從服務器斷線之后平均要 5 秒才能重新連接上主服務器,那么復制積壓緩沖區的大小就不能低于 5 MB。為了安全起見,可以將復制積壓緩沖區的大小設為2 * second * write_size_per_second,這樣可以保證絕大部分斷線情況都能用部分重同步來處理。
3.2.2、基于長連接的命令傳播
問:完成全量同步后,正常運行過程如何同步呢?
當主從庫完成了全量復制,它們之間就會一直維護一個網絡連接,主庫會通過這個連接將后續陸續收到的命令操作再同步給從庫,這個過程也稱為基于長連接的命令傳播,使用長連接的目的就是避免頻繁建立連接導致的開銷。
在命令傳播階段,除了發送寫命令,主從節點還維持著心跳機制:PING 和 REPLCONF ACK。
主->從(PING):每隔指定的時間,主節點會向從節點發送 PING 命令,這個 PING 命令的作用,主要是為了讓從節點進行超時判斷。
從->主(REPLCONF ACK):在命令傳播階段,從服務器默認會以每秒一次的頻率,向主服務器發送命令:
REPLCONF ACK <replication_offset>
其中 replication_offset 是從服務器當前的復制偏移量。發送 REPLCONF ACK 命令對于主從服務器有三個作用:
- 檢測主從服務器的網絡連接狀態。
- 輔助實現 min-slaves 選項。
- 檢測命令丟失, 從節點發送了自身的 slave_replication_offset,主節點會用自己的 master_replication_offset 對比,如果從節點數據缺失,主節點會從
repl_backlog_buffer緩沖區中找到并推送缺失的數據。注意,offset 和 repl_backlog_buffer 緩沖區,不僅可以用于部分復制,也可以用于處理命令丟失等情形;區別在于前者是在斷線重連后進行的,而后者是在主從節點沒有斷線的情況下進行的。
問:如何確定執行全量同步還是部分同步?
在 Redis 2.8 及以后,從節點可以發送 psync 命令請求同步數據,此時根據主從節點當前狀態的不同,同步方式可能是全量復制或部分復制。本文以 Redis 2.8 及之后的版本為例。關鍵就是 psync的執行:

1、從節點根據當前狀態,發送 psync命令給 master:
- 如果從節點從未執行過
replicaof,則從節點發送psync ? -1,向主節點發送全量復制請求; - 如果從節點之前執行過
replicaof則發送psync <runID> <offset>, runID 是上次復制保存的主節點 runID,offset 是上次復制截至時從節點保存的復制偏移量。
2、主節點根據接受到的psync命令和當前服務器狀態,決定執行全量復制還是部分復制:
- runID 與從節點發送的 runID 相同,且從節點發送的
slave_repl_offset之后的數據在repl_backlog_buffer緩沖區中都存在,則回復CONTINUE,表示將進行部分復制,從節點等待主節點發送其缺少的數據即可; - runID 與從節點發送的 runID 不同,或者從節點發送的 slave_repl_offset 之后的數據已不在主節點的
repl_backlog_buffer緩沖區中 (在隊列中被擠出了),則回復從節點FULLRESYNC <runid> <offset>,表示要進行全量復制,其中 runID 表示主節點當前的 runID,offset 表示主節點當前的 offset,從節點保存這兩個值,以備使用。
一個從庫如果和主庫斷連時間過長,造成它在主庫 repl_backlog_buffer的 slave_repl_offset 位置上的數據已經被覆蓋掉了,此時從庫和主庫間將進行全量復制。
3.2.3、總結
- 每個從庫會記錄自己的
slave_repl_offset,每個從庫的復制進度也不一定相同。 - 在和主庫重連進行恢復時,從庫會通過 psync 命令把自己記錄的
slave_repl_offset發給主庫,主庫會根據從庫各自的復制進度,來決定這個從庫可以進行增量復制,還是全量復制。 - replication buffer 和 repl_backlog
- replication buffer 對應于每個 slave,通過
config set client-output-buffer-limit slave設置。 repl_backlog_buffer是一個環形緩沖區,整個 master 進程中只會存在一個,所有的 slave 公用。repl_backlog 的大小通過 repl-backlog-size 參數設置,默認大小是 1M,其大小可以根據每秒產生的命令、(master 執行 rdb bgsave) +( master 發送 rdb 到 slave) + (slave load rdb 文件)時間之和來估算積壓緩沖區的大小,repl-backlog-size 值不小于這兩者的乘積。
- replication buffer 對應于每個 slave,通過
總的來說,replication buffer 是主從庫在進行全量復制時,主庫上用于和從庫連接的客戶端的 buffer,而 repl_backlog_buffer 是為了支持從庫增量復制,主庫上用于持續保存寫操作的一塊專用 buffer。
repl_backlog_buffer是一塊專用 buffer,在 Redis 服務器啟動后,開始一直接收寫操作命令,這是所有從庫共享的。主庫和從庫會各自記錄自己的復制進度,所以,不同的從庫在進行恢復時,會把自己的復制進度(slave_repl_offset)發給主庫,主庫就可以和它獨立同步。如圖所示:

4、主從應用問題
4.1、讀寫分離的問題
問:主從復制的場景下,從節點會刪除過期數據么?
為了主從節點的數據一致性,從節點不會主動刪除數據。我們知道 Redis 有兩種刪除策略:
- 惰性刪除:當客戶端查詢對應的數據時,Redis 判斷該數據是否過期,過期則刪除。
- 定期刪除:Redis 通過定時任務刪除過期數據。
問:那客戶端通過從節點讀取數據會不會讀取到過期數據?
Redis 3.2 開始,通過從節點讀取數據時,先判斷數據是否已過期。如果過期則不返回客戶端,并且刪除數據。
4.2、單機內存大小限制
如果 Redis 單機內存達到 10GB,一個從節點的同步時間在幾分鐘的級別;如果從節點較多,恢復的速度會更慢。如果系統的讀負載很高,而這段時間從節點無法提供服務,會對系統造成很大的壓力。
如果數據量過大,全量復制階段主節點 fork + 保存 RDB 文件耗時過大,從節點長時間接收不到數據觸發超時,主從節點的數據同步同樣可能陷入全量復制->超時導致復制中斷->重連->全量復制->超時導致復制中斷……的循環。
此外,主節點單機內存除了絕對量不能太大,其占用主機內存的比例也不應過大:最好只使用 50% - 65% 的內存,留下 30%-45% 的內存用于執行 bgsave 命令和創建復制緩沖區等。
5、總結
- 主從復制的作用:AOF 和 RDB 二進制文件保證了宕機快速恢復數據,盡可能的防止丟失數據。但是宕機后依然無法提供服務,所以便演化出主從架構、讀寫分離。
- 主從復制原理:連接建立階段、數據同步階段、命令傳播階段;數據同步階段又分為全量復制和部分復制;命令傳播階段主從節點之間有 PING 和 REPLCONF ACK 命令互相進行心跳檢測。
- 主從復制雖然解決或緩解了數據冗余、故障恢復、讀負載均衡等問題,但其缺陷仍很明顯:故障恢復無法自動化;寫操作無法負載均衡;存儲能力受到單機的限制;這些問題的解決,需要哨兵和集群的幫助。
浙公網安備 33010602011771號