主從復制概述
有了 RDB 和 AOF 再也不怕宕機丟失數據了,但是 Redis 實例宕機了怎么實現高可用呢?既然一臺宕機了無法提供服務,那多臺呢?是不是就可以解決了。Redis 提供了主從模式,通過主從復制,將數據冗余一份復制到其他 Redis 服務器。
前者稱為主節點 (master),后者稱為從節點 (slave);數據的復制是單向的,只能由主節點到從節點。
為了保證副本數據的一致性,主從架構采用了讀寫分離的方式。
- 讀操作:主、從庫都可以執行;
- 寫操作:主庫先執行,之后將寫操作同步到從庫;
為何要采用讀寫分離的方式?
我們可以假設主從庫都可以執行寫指令,假如對同一份數據分別修改了多次,每次修改發送到不同的主從實例上,就導致是實例的副本數據不一致了。
搭建主從復制
主從復制的開啟,完全是在從節點發起的,不需要我們在主節點做任何事情。
可以通過 replicaof(Redis 5.0 之前使用 slaveof)命令形成主庫和從庫的關系。
在從節點開啟主從復制,有 3 種方式:
1、配置文件
在從服務器的配置文件中加入 replicaof <masterip> <masterport>
2、啟動命令
redis-server 啟動命令后面加入 --replicaof <masterip> <masterport>
3、客戶端命令
啟動多個 Redis 實例后,直接通過客戶端執行命令:replicaof <masterip> <masterport>,則該 Redis 實例成為從節點。
主從復制原理
主從庫模式一旦采用了讀寫分離,所有數據的寫操作只會在主庫上進行,不用協調三個實例。
主庫有了最新的數據后,會同步給從庫,這樣,主從庫的數據就是一致的。
同步分為三種情況:
1、第一次主從庫全量復制;
2、主從正常運行期間的同步;
3、主從庫間網絡斷開重連同步
主從庫第一次全量復制
主從庫第一次復制過程大體可以分為 3 個階段:連接建立階段(即準備階段)、主庫同步數據到從庫階段、發送同步期間新寫命令到從庫階段;
建立連接
該階段的主要作用是在主從節點之間建立連接,為數據全量同步做好準備。從庫會和主庫建立連接,從庫執行 replicaof 并發送 psync 命令并告訴主庫即將進行同步,主庫確認回復后,主從庫間就開始同步了。
從庫怎么知道主庫信息并建立連接的呢?
在從節點的配置文件中的 replicaof 配置項中配置了主節點的 IP 和 port 后,從節點就知道自己要和那個主節點進行連接了。
從節點內部維護了兩個字段,masterhost 和 masterport,用于存儲主節點的 IP 和 port 信息。
從庫執行 replicaof 并發送 psync 命令,表示要執行數據同步,主庫收到命令后根據參數啟動復制。命令包含了主庫的 runID 和 復制進度 offset 兩個參數。
- runID:每個 Redis 實例啟動都會自動生成一個 唯一標識 ID,第一次主從復制,還不知道主庫 runID,參數設置為 「?」。
- offset:第一次復制設置為 -1,表示第一次復制,記錄復制進度偏移量。
主庫收到 psync 命令后,會用 FULLRESYNC 響應命令帶上兩個參數:主庫 runID 和主庫目前的復制進度 offset,返回給從庫。從庫收到響應后,會記錄下這兩個參數。
FULLRESYNC 響應表示第一次復制采用的全量復制,也就是說,主庫會把當前所有的數據都復制給從庫。
主庫同步數據給從庫
第二階段
master 執行 bgsave命令生成 RDB 文件,并將文件發送給從庫,同時主庫為每一個 slave 開辟一塊 replication buffer 緩沖區記錄從生成 RDB 文件開始收到的所有寫命令。
從庫收到 RDB 文件后保存到磁盤,并清空當前數據庫的數據,再加載 RDB 文件數據到內存中。
發送新寫命令到從庫
第三階段
從節點加載 RDB 完成后,主節點將 replication buffer 緩沖區的數據發送到從節點,Slave 接收并執行,從節點同步至主節點相同的狀態。
主庫將數據同步到從庫過程中,可以正常接受請求么?
主庫不會被阻塞,在生成 RDB 文件之后的寫操作并沒有記錄到剛剛的 RDB 文件中,為了保證主從庫數據的一致性,所以主庫會在內存中使用一個叫 replication buffer 記錄 RDB 文件生成后的所有寫操作。
replication buffer 是一個在 master 端上創建的緩沖區,存放的數據是下面三個時間內所有的 master 數據寫操作。
1)master 執行 bgsave 產生 RDB 的期間的寫操作;
2)master 發送 rdb 到 slave 網絡傳輸期間的寫操作;
3)slave load rdb 文件把數據恢復到內存的期間的寫操作。
主從正常運行期間的同步
當主從庫完成了全量復制,它們之間就會一直維護一個網絡連接,主庫會通過這個連接將后續陸續收到的命令操作再同步給從庫,這個過程也稱為基于長連接的命令傳播,使用長連接的目的就是避免頻繁建立連接導致的開銷。
在命令傳播階段,除了發送寫命令,主從節點還維持著心跳機制:PING 和 REPLCONF ACK。
主->從:PING
每隔指定的時間,主節點會向從節點發送 PING 命令,這個 PING 命令的作用,主要是為了讓從節點進行超時判斷。
從->主:REPLCONF ACK
在命令傳播階段,從服務器默認會以每秒一次的頻率,向主服務器發送命令:
主從庫間網絡斷開重連同步
在 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之間的命令同步給從庫即可。
增量復制執行流程如下圖: