Redis設計與實現之:主從複製

目錄:

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:8001127.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 給客戶端,表示已經收到了命令,然後纔開始設置 masterhostmasterport 屬性。
(如果是同步命令,實現過程會怎樣? 從服務器收到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;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章