目錄:
1.主從複製是什麼?有什麼用途?
2.主從複製的環境如何搭建?
3.主從複製的功能如何實現?
4.一個完整的主從複製過程?
參考內容:
《Reis設計與實現》第三部分 第十五章 複製
Redis官方文檔:https://redis.io/topics/replication
1.主從複製是什麼?有什麼用途?
概念:
主從複製是指在Redis中通過配置,讓一個服務器去複製另一個服務器的數據,使二者存有相同的數據內容,達到"數據庫狀態一致"的效果。
用途:
(1) 通過主從複製,一個主節點可以有多個從節點,主節點可寫可讀,從節點可讀,把讀操作轉移到從節點上,可以降低主節點的訪問負載;
(2) 如果主節點意外掛掉(例如某些硬件故障導致數據無法恢復),則從節點上的數據備份可以避免數據丟失。
缺點:
不能提供高可用性,因爲不支持自動故障轉移。
當主節點掛掉,服務器將無法對外提供寫服務,而在實際生產環境中服務是一刻都不能停止的,所以一般的生產環境不會單單使用主從模式,而是在其上封裝一層的哨兵模式或集羣模式(high availability features provided as an additional layer by Redis Cluster or Redis Sentinel)
2.主從複製的環境如何搭建?
兩種方式:
方法一: 修改配置文件後啓動從機
修改 redis.conf
配置文件:
redis.conf ---> slaveof 127.0.0.1:8001
然後啓動服務器:
127.0.0.1:8002>./redis-server redis.conf
使 127.0.0.1:8002 成爲 127.00.1:8001 的從機。
方法二: 在運行中的從機上輸入 slaveof 命令
127.0.0.1:8002> SLAVEOF 127.0.0.1:8001
// SLAVEOF <master_host>:<master_port>
輸入 SLAVEOF
命令後,從機上的數據會被覆蓋(原有數據清零,數據同步爲與主機一致)。
查看主從關係:
在連接服務器的客戶端上輸入 INFO
命令:
主機:
127.0.0.1:8001> INFO REPLICATION
#Replication
role:master
connected slaves:1
slave0:ip=127.0.0.1,port=8002,state=online, offset=224,lag=1
從機:
127.0.0.1:8002> INFO REPLICATION
#Replication
role:slave
master _host:127.0.0.1
naster_ port:8001
master_link_status:up
master_last_io_seconds_ ago:2
slave_priority:100
slave_read_only:1
connected slaves:0
如何斷開主從關係:
在從機上輸入命令:
127.0.0.1:8002> SLAVEOF NO ONE
3.主從複製的功能如何實現?
Redis的主從複製功能由 “同步(sync)” 和 “命令傳播(command propagate)” 兩個操作實現:
(1) 當主從節點首次建立連接,或連接斷開後重連時,通過 “同步” 的操作來完成主從複製;
(2) 當主從節點間保持正常連接時,通過 “命令傳播” 的操作完成主從複製。
注:
主從複製功能的“同步"和"命令傳播"類似於epoll中的ET和LT模式:
“同步"是一種“邊沿觸發”: 當主從首次建立連接,或連接中斷重連後,採用同步的方式完成主從複製;
“命令傳播"是一種“水平沿觸發”: 當主從保持連接狀態,主服務器上發生寫明令改寫數據時,通過命令傳播向從服務器傳遞寫明令操作。
3.1 “同步”:
從服務器向主服務器發送 PSYNC
命令執行同步操作:
PSYNC命令的實現:
① 從服務器發送的PSYNC命令有兩種格式:
第一種:
PSYNC ? -1
如果從服務器之前沒有複製過任何服務器,或者執行了 SLAVEOF no one
命令,則說明此時不可能執行部分重同步,PSYNC 調用的格式如上(請求完整重同步),
第二種:
PSYNC <runid> <offset>
如果從服務器之前已經複製過了某個主服務器,那麼在開始一次新的複製時的 PSYNC 調用格式如上,
其中,<runid>
表示上一次複製的主服務器的“運行ID”,<runid>
表示從服務器的當前“複製偏移量”。
② 主服務器的返回值有三種格式:
第一種:
+FULLRESYNC
表示主服務器將與從服務器執行完整重同步;
第二種:
+CONTINUE
表示主服務器將與從服務器執行部分重同步;
第三種:
-ERR
表示主服務器版本低於 Redis 2.8,無法識別 PSYNC 命令,從服務器將發送 FULLRESYNC 命令並執行完整重同步。
完全重同步(full resynchronization)的實現:
① PSYNC:
客戶端向從服務器發送 SLAVEOF 命令,從服務器在建立套接字連接、執行 PING 命令、身份驗證後,向主服務器發送 PSYNC 命令;
② BGSAVE:
主服務器收到 PSYNC 後,判斷需要執行完整重同步,執行 BGSAVE 命令,在後臺生成一個 RDB文件,並使用緩衝區記錄從現在開始執行的所有寫命令;
③ RDB:
主服務器BGSAVE執行完畢後,將生成的RDB文件發送給從服務器,從服務器收到後寫入;
④ 緩衝區:
主服務器將緩衝區中記錄的寫明令發送給從服務器,從服務器執行收到的這些寫命令。
部分重同步(partial resynchronization)的實現:
部分重同步功能由三部分構成:
① 主、從服務器的“複製偏移量” (replication offset);
② 主服務器的複製“積壓緩衝區”(replication backlog);
③ 服務器的“運行ID”(runID)。
過程:
a. 主從服務器會分別維護一個“複製偏移量”,主服務器在發送N個字節的數據後對偏移量+N,從服務器在收到N個字節的數據後對偏移量+N,因此正常連接的情況下主從雙方的複製偏移量相等。
當主從間連接斷開重連後,從服務器會發送PSYNC命令,並攜帶自己的複製偏移量的值;
b. 主服務器維護的“複製積壓緩衝區"本質上是一個【固定長度的先進先出隊列】,其中保存着主服務器最近執行的 【寫命令】,隊列滿時則會將先進入的數據丟棄;
c. 主服務器收到從服務器的 PSYNC 命令後,判斷其中攜帶的從服務器當前複製偏移量之後的數據(即 offset+1 開始的數據)是否仍然存在於複製積壓緩衝區中:
如果是,主從服務返回 “+CONTINUE” 給從服務器,表示將以 “部分重同步” 的模式進行數據同步,
如果不是,則執行完整重同步。
d. 每個服務器都在啓動時生成一個由40個十六進制數組成的“運行ID”,初次連接時從服務器會保存主服務器的 運行ID。在從服務器斷線重連後,會將之前保存的運行ID發送個主服務器,如果新舊ID相同,主服務器會繼續嘗試使用部分重同步,否則,主服務器發現此從服務器是新連接的,則執行完整重同步。
附: 配置"複製積壓緩衝區”的大小:
Redis複製積壓緩衝區的默認大小爲 1 MB
,需要根據實際情況來設計緩衝區的大小,以便保證 PSYNC 命令的部分重複制功能正常發揮作用。
複製積壓緩衝區的大小可根據以下公式來估算:
second * write_size_per_second
其中,second
爲從服務器斷線重連所需的平均時間,write_size_per_second
爲主服務器平均每秒產生的寫命令數據量。
3.2 “命令傳播”:
在 “同步” 操作執行完畢後,主從服務器達到數據庫狀態一致,隨後當主服務器上發生寫操作時,主服務器上的數據被改寫,主服務器通過命令傳播方式將這條寫命令發送到從服務器,從服務器收到後執行這條寫明令。
心跳檢測:
在命令傳播階段,從服務器默認會以 每秒一次
的頻率向主服務器發送命令:
REPLCONF ACK <replication_offset>
其中,replication_offset
是從服務器當前的複製偏移量。
心跳檢測有三個作用:
(1) 檢測主從服務器的網絡連接狀態:
主服務器知道正常情況下會 每隔一秒鐘收到一次從服務器的心跳包,如果長時間沒有收到,則主服務器就可以知道主從間的連接出現了問題。
在主服務器上 INFO replication
可以查看收到各個從服務器上次心跳包的時間:
(正常情況下在0到1秒之間跳變)
127.0.0.1:8001> INFO replication
#Replication
role:master
connected slaves:3
slave0:ip=127.0.0.1,port=8002,state=online,offset=252,lag=1 <===
slave1:ip=127.0.0.1,port=8003, state=online,offset=252,lag=1 <===
slave2:ip=127.0.0.1,port=8004,state=online,offset=252,lag=0 <===
master_replid:9b9ea7eef504c05da56067b53f64 59827283c46
master_replid2:0000000000000000000000000000000000000000
其中的 lag
字段表示的就是上次收到這個slave從服務器上的心跳檢測時間,正常值爲0或1。
(2) 輔助實現min-slave配置選項:(防止主服務器在不安全情況執行寫)
爲了防止主服務器在不安全的情況下執行寫命令,Redis提供兩個配置選項:
min-slaves-to-wite 3
min-slaves-max-lag 10
表示如果從服務器的個數小於3,或者有三個最大從服務器的延遲(lag)都大於10秒,則主服務器不執行寫命令。
(3) 檢測命令丟失:
心跳檢測中帶有 replication_offset
字段,主服務器可以判斷上次的命令傳播是否丟失,如丟失,可及時通過複製積壓緩衝區中的數據重傳。
4. 一個完整的主從複製過程:
假設有 127.0.0.1:8001
和 127.0.0.1:8002
兩個Redis服務器,現在讓 8002 成爲 8001 的從機,整個過程如下:
步驟1: 從服務器:設置主服務器的地址和端口:(redisServer )
在連接從服務器(8002)的客戶端上輸入 SLAVEOF
命令:
127.0.0.1:8002> SLAVEOF 127.0.0.1:8001
OK
從服務器收到命令後,保存待連接主服務器的 IP地址 和 端口號:
struct redisServer {
char *masterhost; //主服務器的IP地址
int masterport; //主服務器的端口號
};
注意 SLAVEOF
是一個 異步命令
,從服務器收到 SLAVEOF
命令後,會直接返回OK
給客戶端,表示已經收到了命令,然後纔開始設置 masterhost
和 masterport
屬性。
(如果是同步命令,實現過程會怎樣? 從服務器收到SLAVEOF命令後,客戶端阻塞等待命令返回,從服務器執行完成後返回OK給客戶端,客戶端繼續向下執行。)
步驟2: 從服務器:建立與主服務器的套接字連接:(connect)
從服務器根據在 步驟1 中保存的主服務器IP和端口號後,創建連向主服務器的套接字連接,即從節點調用 connect,主節點調用 accept 接受連接。
如果連接建立成功,從服務器會創建一個專門用於處理主從複製工作的 IO事件處理器(在epoll中添加一個監聽fd),後續的複製工作(例如接收RDB文件、接收主服務器傳播過來的寫命令等)都是依靠這個IO事件處理器來完成。
步驟3: 從服務器:發送PING命令:(PING)
在 步驟2 中主從服務器建立了socket套接字連接,但雙方還未使用過該套接字進行通信,因此需要先執行 PING
命令檢查雙方的讀寫處理是否正常(步驟2相當於完成了TCP三次握手,主從雙方的connect、accept調用正常,步驟3 是用來檢測主從雙方的read、write 調用是否正常)。
(1)若從服務器發出PING命令後正常收到主服務器的PONG回覆,則說明連接狀態正常,可執行下面步驟;
(2)若從服務器發出PING命令後收到主服務器回覆的錯誤,或未在規定時間內收到主服務器的回覆(TIMEOUT),則從服務器斷開重連。
步驟4: 身份驗證:(AUTH)
(1) 關於身份驗證的配置項:
在 redis.conf
配置文件中有兩個關於主從複製過程中身份驗證的配置項:
① 從服務器上設置主服務器的密碼:(默認未設置)
# masterauth <master-password>
例如:
masterauth 10086
② 主服務器上設置自身的密碼:(默認未設置)
# requirepass foobared
例如:
requirepass 10086
(2) 身份驗證的過程:
(1) 如果從服務器設置了 masterauth
選項,則從服務器會發起身份驗證,從服務器向主服務器發送 AUTH
命令,命令的參數是 masterauth
選項的值,例如:
AUTH 10086
此時:
如果主服務器設置了 `requirepass` 選項且二者密碼匹配,則驗證通過,連接成功;
如果密碼不匹配,則驗證失敗,從新發起連接直至成功或從服務器放棄連接.
(2) 如果從服務器未設置 masterauth
選項,則從服務器不會發起身份驗證,此時:
如果主服務器也未設置requirepass選項,則連接成功;
如果主服務器設置了requirepass選項,則驗證失敗,重新發起連接直至成功或從服務器放棄連接。
步驟5: 發送端口信息:(listening-port)
從服務器向主服務器發送自己的監聽端口號,主服務器收到後保存在對應從服務器的 redisClient
結構中從服務器調用命令:
REPLCONF listening-port 8002
主服務器保存:
typedef struct redisClient
int slave_listening_port; //從服務器的監聽端口號
} redisClient;
slave_listening_port
屬性的唯一作用是主服務器執行 INFO replication
命令時打印出從服務器的端口號。
步驟6: 同步:
從服務器向主服務器發送 PSYNC
命令,執行同步操作,並將自己的數據庫更新至與主服務器一致。
注:
在同步操作之前,只有從服務器是主服務器的客戶端;
在同步操作之後,主服務器也會成爲從服務器的客戶端。
這是因爲無論是"部分重同步"還是"完整重同步",都需要主服務器向從服務器發送數據(RDB文件 或 寫命令),因此主服務器必須成爲從服務器的客戶端,才能完成寫操作。
步驟7: 命令傳播:
在完成同步操作之後,主服務器就會進入命令傳播階段,這時主服務器只需要一直將自己執行的寫明令發送給從服務器,從服務器執行傳播來的寫命令,即可保持主從數據庫狀態一致。
同時,在命令傳播階段從服務器會按照固定的頻率(默認一秒一次)向主服務器發送心跳檢測命令。
到目前爲止,所接觸到的 redisServer
結構中的成員:
struct redisServer{
redisDb *db;
int dbnum; //數據庫
long long dirty;
time_t lastchange;
struct saveparam *saveparams; //RDB持久化
sds aof_buf; //AOF持久化
char *masterhost;
int masterport; //主從複製,當此服務器爲從機時,保存主機IP及端口號
};
redisClient
:
typedef struct redisClient {
redisDb *db; //記錄客戶端當前正在使用的數據庫
int slare_listening_port; //當這個結構體對應的客戶端是一個從服務器,用來保存從服務器的監聽端口號
} redisClient;