本文主要介紹Redis集群中主從服務(wù)器復(fù)制功能的實(shí)現(xiàn)。
在Redis中臊恋,用戶可以通過(guò)執(zhí)行SLAVEOF
命令或設(shè)置slaveof選項(xiàng)琅催,讓一個(gè)服務(wù)器去復(fù)制另一個(gè)服務(wù)器,稱被復(fù)制的服務(wù)器為主服務(wù)器(master)界逛,而對(duì)主服務(wù)器進(jìn)行復(fù)制的服務(wù)器稱為從服務(wù)器(slave)。
例如:
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
OK
則服務(wù)器127.0.0.1:12345就成為了127.0.0.1 6379的slave纺座,127.0.0.1 6379成為了127.0.0.1:12345的master息拜。
進(jìn)行復(fù)制中的主從服務(wù)器雙方的數(shù)據(jù)庫(kù)保存同樣的數(shù)據(jù),概念上稱之為數(shù)據(jù)庫(kù)的一致性净响。
I少欺、舊版復(fù)制功能的實(shí)現(xiàn)
Redis的復(fù)制功能分為同步(sync) 和命令傳播(command propagate)兩個(gè)操作:
· 同步操作用于將從服務(wù)器的數(shù)據(jù)庫(kù)狀態(tài)更新至主服務(wù)器當(dāng)前所處的數(shù)據(jù)庫(kù)狀態(tài)。
· 命令傳播操作則用于主服務(wù)器的數(shù)據(jù)被修改后馋贤,讓主從服務(wù)器的數(shù)據(jù)庫(kù)狀態(tài)重新回到一致赞别。
1.1 同步
從服務(wù)器對(duì)主服務(wù)器的同步操作需要通過(guò)向主服務(wù)器發(fā)送SYNC
命令來(lái)完成,以下是SYNC
命令的執(zhí)行步驟:
1配乓、 從服務(wù)器向主服務(wù)器發(fā)送SYNC
命令仿滔。
2、收到命令之后的主服務(wù)器執(zhí)行BDSAVE
命令犹芹,在后臺(tái)生成一個(gè)RDB文件崎页,并使用一個(gè)緩沖區(qū)記錄從現(xiàn)在開(kāi)始執(zhí)行的所有寫(xiě)命令。
3腰埂、當(dāng)主服務(wù)器BGSAVE
命令執(zhí)行完畢時(shí)飒焦,主服務(wù)器會(huì)將生成的RDB文件發(fā)送給從服務(wù)器,從服務(wù)器load這個(gè)RDB文件屿笼,將自己的數(shù)據(jù)庫(kù)狀態(tài)更新至主服務(wù)器執(zhí)行BGSAVE
命令時(shí)數(shù)據(jù)庫(kù)狀態(tài)牺荠。
4翁巍、主服務(wù)器將記錄在緩沖區(qū)中的所有寫(xiě)命令發(fā)送給從服務(wù)器,從服務(wù)器執(zhí)行這些寫(xiě)命令志电,將自己的數(shù)據(jù)庫(kù)狀態(tài)更新至主服務(wù)器當(dāng)前所處狀態(tài)。
下圖展示了這一過(guò)程:
下圖說(shuō)明了一個(gè)同步實(shí)例:
1.2 命令傳播
主服務(wù)器每次執(zhí)行寫(xiě)名列會(huì)同時(shí)將命令發(fā)送給從服務(wù)器執(zhí)行蛔趴,從而實(shí)現(xiàn)命令傳播挑辆,達(dá)到主從服務(wù)器的一致性。
II孝情、舊版復(fù)制功能的缺點(diǎn)
從服務(wù)器在除了在初次復(fù)制主服務(wù)器之外鱼蝉,還會(huì)在斷線后重復(fù)制是也進(jìn)行賦值操作。而剛剛介紹的舊版復(fù)制功能對(duì)于在斷線后重復(fù)制情況來(lái)說(shuō)箫荡,效率很低魁亦。
這是由于需要重寫(xiě)執(zhí)行SYNC
操作引起的(一旦執(zhí)行就會(huì)發(fā)生RDB的復(fù)制等操作)。
如下圖所示:
而要解決這個(gè)問(wèn)題的關(guān)鍵就在于上圖中紅圈位置的復(fù)制操作羔挡,我們應(yīng)該盡量不再重新使用SYNC
命令洁奈,而采用其他方式。
III绞灼、新版復(fù)制功能的實(shí)現(xiàn)
在Redis2.8版本之后利术,采用了PSYNC
命令的代替了SYNC
同步操作。
PSYNC
命令具有完整重同步和部分重同步兩種模式:
· 完整重同步與舊版本的SYNC
命令一樣低矮。
· 部分重同步專門(mén)用于處理斷線后重復(fù)制的情況: 當(dāng)從服務(wù)器在斷線后重新連接主服務(wù)器時(shí)印叁,如果條件允許,主服務(wù)器可以將主從服務(wù)器連接斷開(kāi)期間執(zhí)行的寫(xiě)命令發(fā)送給從服務(wù)器军掂,從服務(wù)器只要接收并執(zhí)行這些寫(xiě)命令轮蜕,就可以將數(shù)據(jù)塊更新至最新?tīng)顟B(tài)。
下圖展示了相同情況下使用部分重同步的實(shí)例:
IV蝗锥、部分重同步的實(shí)現(xiàn)
部分重同步功能由以下三個(gè)部分構(gòu)成:
· 主服務(wù)器的復(fù)制偏移量和從服務(wù)器的復(fù)制偏移量跃洛;
· 主服務(wù)器的復(fù)制積壓緩沖區(qū);
· 服務(wù)器的運(yùn)行ID终议。
4.1 復(fù)制偏移量
執(zhí)行復(fù)制的雙方都要維護(hù)一個(gè)復(fù)制偏移量:
· 主服務(wù)器每次向從服務(wù)器傳播N個(gè)字節(jié)的數(shù)據(jù)時(shí)税课,就將自己的復(fù)制偏移量+N;
· 從服務(wù)器每次收到主服務(wù)器傳播來(lái)的N個(gè)字節(jié)的數(shù)據(jù)時(shí)痊剖,就將自己的復(fù)制偏移量+N韩玩;
復(fù)制偏移量具有兩個(gè)作用:
· 通過(guò)對(duì)比主從服務(wù)器的復(fù)制偏移量,程序可以很容易的知道主從服務(wù)器的數(shù)據(jù)是否處于一致?tīng)顟B(tài)陆馁;
· 通過(guò)對(duì)比主從服務(wù)器的復(fù)制偏移量找颓,可以知道從服務(wù)器丟失了哪些數(shù)據(jù),從而執(zhí)行部分重同步叮贩。
4.2 復(fù)制積壓緩沖區(qū)
復(fù)制積壓緩沖區(qū)是由主服務(wù)器維護(hù)的一個(gè)固定長(zhǎng)度的先進(jìn)先出隊(duì)列击狮。
當(dāng)服務(wù)器執(zhí)行命令傳播時(shí)佛析,不僅會(huì)將寫(xiě)命令傳播給從服務(wù)器,還會(huì)將寫(xiě)命令寫(xiě)入復(fù)制積壓緩沖區(qū)中:
當(dāng)從服務(wù)器重新連上主服務(wù)器時(shí)彪蓬,從服務(wù)器會(huì)通過(guò)PSYNC
命令將自己的復(fù)制偏移量offset發(fā)送給主服務(wù)器寸莫,主服務(wù)器會(huì)根據(jù)這個(gè)復(fù)制偏移量來(lái)決定對(duì)從服務(wù)器執(zhí)行何種同步操作:
· 如果offset偏移量之后的數(shù)據(jù)仍然存在于復(fù)制積壓緩沖區(qū)中,則主服務(wù)器將對(duì)從服務(wù)器執(zhí)行部分重同步档冬;
· 如果offset偏移量之后的數(shù)據(jù)已經(jīng)不在復(fù)制積壓緩沖區(qū)中膘茎,主服務(wù)器會(huì)對(duì)從服務(wù)器執(zhí)行完整的重同步操作;
4.3 服務(wù)器運(yùn)行ID
每個(gè)Redis服務(wù)器酷誓,都會(huì)有自己的運(yùn)行ID披坏,實(shí)現(xiàn)部分重同步需要復(fù)制偏移量,復(fù)制積壓緩沖區(qū)盐数,運(yùn)行ID一起完成棒拂。
當(dāng)從服務(wù)器對(duì)主服務(wù)器進(jìn)行初次復(fù)制時(shí),主服務(wù)器會(huì)將自己的運(yùn)行ID發(fā)送給從服務(wù)器玫氢,從服務(wù)器將主服務(wù)器的運(yùn)行ID保存起來(lái)帚屉。
當(dāng)從服務(wù)器斷線并重寫(xiě)連接上一個(gè)主服務(wù)器之后(這個(gè)主服務(wù)器可能已經(jīng)不是之前的主服務(wù)器,可以參見(jiàn)Redis中的sentinel)漾峡,從服務(wù)器向當(dāng)前連接的主服務(wù)器發(fā)送自己之前保存的運(yùn)行ID:
· 如果從服務(wù)器保存的運(yùn)行ID和當(dāng)前主服務(wù)器的運(yùn)行ID相同涮阔,則說(shuō)明從服務(wù)器斷線之前復(fù)制的就是當(dāng)前連接的這個(gè)主服務(wù)器,主服務(wù)器可以嘗試執(zhí)行部分重同步操作灰殴;
· 如果主服務(wù)器發(fā)現(xiàn)與自己的運(yùn)行ID不同敬特,則只能執(zhí)行完整重同步操作;
V牺陶、PSYNC命令的實(shí)現(xiàn)
PSYNC
命令的調(diào)用方法有兩種:
1伟阔、 如果從服務(wù)器以前沒(méi)有復(fù)制過(guò)任何主服務(wù)器,或者之前執(zhí)行過(guò)SLAVEOF no one
命令掰伸,則從服務(wù)器在開(kāi)始一次新的復(fù)制時(shí)將向主服務(wù)器發(fā)送PSYNC ? -1
命令皱炉,主動(dòng)請(qǐng)求主服務(wù)器進(jìn)行完整重同步。
2狮鸭、 如果從服務(wù)器已經(jīng)復(fù)制過(guò)某個(gè)主服務(wù)器合搅,則從服務(wù)器在開(kāi)始一次新的復(fù)制時(shí)會(huì)向主服務(wù)器發(fā)送PSYNC <runid> <offset>
命令,runid為上一次保存的主服務(wù)器運(yùn)行ID歧蕉,offset為從服務(wù)器維護(hù)的復(fù)制偏移量灾部。
主服務(wù)器根據(jù)接收到的PSYNC
命令,會(huì)執(zhí)行下面的三種回復(fù):
1惯退、 主服務(wù)器返回+FULLRESYNC <runid> <offset>
回復(fù)赌髓,表示主服務(wù)器與從服務(wù)器執(zhí)行完整重同步,runid為主服務(wù)器自己的運(yùn)行ID,offset為主服務(wù)器自身的復(fù)制偏移量锁蠕,從服務(wù)器將用這個(gè)值初始化自己的偏移量夷野。
2、 主服務(wù)器回復(fù)+COUTINUE
荣倾,表示主服務(wù)器將與從服務(wù)器執(zhí)行部分重同步悯搔,根據(jù)接收到的offset,已經(jīng)主服務(wù)器自己的復(fù)制積壓緩沖區(qū)來(lái)完成舌仍。
3妒貌、 主服務(wù)器回復(fù)-ERR
,表示主服務(wù)器版本低于Redis2.8抡笼,無(wú)法識(shí)別PSYNC
命令苏揣,之后從服務(wù)器將發(fā)送SYNC
黄鳍,執(zhí)行低版本的完整重同步推姻。
下圖完整描述了這一過(guò)程:
VI、復(fù)制的實(shí)現(xiàn)
6.1 步驟1:設(shè)置主服務(wù)器的地址和端口
客戶端向從服務(wù)器發(fā)送:
127.0.0.1:12345> SLAVEOF 127.0.0.1 6379
OK
從服務(wù)器首先要做的是將客戶端給定的主服務(wù)器IP地址127.0.0.1以及端口6379保存到服務(wù)器狀態(tài)的masterhost和masterport屬性中:
struct redisServer {
//主服務(wù)器地址
char *masterhost;
//端口
int masterport;
};
6.2 步驟2: 建立套接字連接
從服務(wù)器根據(jù)命令的IP地址和端口框沟,創(chuàng)建向主服務(wù)器的套接字連接:
創(chuàng)建套接字成功藏古,則從服務(wù)器會(huì)專門(mén)為這個(gè)套接字關(guān)聯(lián)一個(gè)用于復(fù)制工作的文件事件處理器,負(fù)責(zé)執(zhí)行后續(xù)的復(fù)制工作忍燥。
主服務(wù)器在accept從服務(wù)器的套接字連接之后拧晕,將為該套接字創(chuàng)建相應(yīng)的客戶端狀態(tài),并將從服務(wù)器看作是一個(gè)連接到主服務(wù)器的客戶端來(lái)對(duì)待梅垄,這時(shí)從服務(wù)器將同時(shí)具有服務(wù)器和客戶端兩個(gè)身份厂捞。
因?yàn)閺?fù)制工作接下來(lái)的幾個(gè)步驟都會(huì)以從服務(wù)器向主服務(wù)器發(fā)送命令請(qǐng)求的形式進(jìn)行,所以理解從服務(wù)器是主服務(wù)器的客戶端相當(dāng)重要队丝。
6.3 步驟3: 發(fā)送PING命令
從服務(wù)器成為主服務(wù)器的客戶端之后靡馁,向主服務(wù)器發(fā)送PING
命令:
這個(gè)PING
命令有兩個(gè)作用:
1、檢查套接字的讀寫(xiě)狀態(tài)是否正常机久;
2臭墨、檢查主服務(wù)器是否能正常處理請(qǐng)求。
而從服務(wù)器會(huì)根據(jù)主服務(wù)器的相應(yīng)回復(fù)狀態(tài)判斷下一步的動(dòng)作:
1膘盖、如果主服務(wù)器向從服務(wù)器回復(fù)一個(gè)命令胧弛,但從服務(wù)器在規(guī)定時(shí)間內(nèi)無(wú)法讀取出命令回復(fù)的內(nèi)容,則表示主從服務(wù)器之間的網(wǎng)絡(luò)連接狀態(tài)不佳侠畔。
從服務(wù)器會(huì)斷開(kāi)并重新創(chuàng)建連向主服務(wù)器的套接字结缚;
2、如果主服務(wù)器返回了一個(gè)錯(cuò)誤软棺,表示主服務(wù)器暫時(shí)無(wú)法處理從服務(wù)器的命令請(qǐng)求掺冠,從服務(wù)器同樣斷開(kāi)并重新創(chuàng)建連向主服務(wù)器的套接字;
3、從服務(wù)器讀取到了PONG
命令德崭,表示主從服務(wù)器網(wǎng)絡(luò)連接正常斥黑,繼續(xù)執(zhí)行下面的任務(wù)。
6.4 步驟4: 身份驗(yàn)證
身份驗(yàn)證步驟可以通過(guò)下圖來(lái)說(shuō)明:
6.5 步驟5: 發(fā)送端口信息
在執(zhí)行完身份驗(yàn)證步驟之后眉厨,從服務(wù)器會(huì)向主服務(wù)器發(fā)送從服務(wù)器的監(jiān)聽(tīng)端口號(hào)(用于后續(xù)的命令傳播等操作)锌奴,主服務(wù)器將端口號(hào)記錄在從服務(wù)器所對(duì)應(yīng)的客戶端狀態(tài)中:
typedef struct redisClient {
//從服務(wù)器的監(jiān)聽(tīng)端口號(hào)
int slave_listening_port;
} redisClient;
6.6 步驟6: 同步
從服務(wù)器向主服務(wù)器發(fā)送PSYNC
命令,執(zhí)行同步操作憾股,并將自己的數(shù)據(jù)庫(kù)更新至主服務(wù)器數(shù)據(jù)庫(kù)當(dāng)前狀態(tài)鹿蜀。
在同步操作執(zhí)行之前,只有從服務(wù)器是主服務(wù)器的客戶端服球,但在執(zhí)行同步操作之后茴恰,主服務(wù)器也會(huì)成為從服務(wù)器的客戶端:
· 如果要執(zhí)行完整重同步操作,主服務(wù)器需要成為從服務(wù)器的客戶端斩熊,才能將保存在緩沖區(qū)中的寫(xiě)命令發(fā)送給從服務(wù)器執(zhí)行往枣;
· 如果執(zhí)行的部分重同步操作,主服務(wù)器需要成為從服務(wù)器客戶端粉渠,才能將復(fù)制積壓緩沖區(qū)的內(nèi)容發(fā)送給從服務(wù)器執(zhí)行分冈。
因此,在同步操作執(zhí)行之后霸株,主從服務(wù)器都是雙方的客戶端雕沉,它們可以互相向?qū)Ψ桨l(fā)送命令請(qǐng)求,或者互相向?qū)Ψ椒祷孛罨貜?fù):
6.7 步驟7: 命令傳播
主服務(wù)器將自己的寫(xiě)命令發(fā)送給從服務(wù)器執(zhí)行去件,進(jìn)入命令傳播階段坡椒,這一操作的基礎(chǔ)也是主服務(wù)器時(shí)從服務(wù)器的客戶端。
VII尤溜、心跳檢測(cè)
在命令傳播階段倔叼,從服務(wù)器會(huì)以每秒一次的頻率向主服務(wù)器發(fā)送心跳檢測(cè)命令:
REPLCONF ACK <replication_offset>
replication_offset是從服務(wù)器當(dāng)前的復(fù)制偏移量。
REPLCONF ACK
命令對(duì)主從服務(wù)器有三個(gè)作用:
· 檢測(cè)網(wǎng)絡(luò)連接狀態(tài)靴跛;
· 輔助實(shí)現(xiàn)min-slaves選項(xiàng)(min-slaves表示最小從服務(wù)器數(shù)量缀雳,可以防止主服務(wù)器在不安全的狀態(tài)執(zhí)行寫(xiě)命令,不安全的狀態(tài)指從服務(wù)器數(shù)量少于某個(gè)數(shù)值)梢睛;
· 檢測(cè)命令丟失(根據(jù)復(fù)制偏移量的對(duì)比)肥印;
【參考】
[1] 《Redis設(shè)計(jì)與實(shí)現(xiàn)》
歡迎轉(zhuǎn)載,轉(zhuǎn)載請(qǐng)注明出處wenmingxing Redis之主從服務(wù)器的復(fù)制