2.數(shù)據(jù)庫狀態(tài)一致
主從復(fù)制,服務(wù)器雙方數(shù)據(jù)庫將保存相同的數(shù)據(jù),這種現(xiàn)象稱為“數(shù)據(jù)庫狀態(tài)一致”
3.執(zhí)行方式
>>>slaveof 127.0.0.1 6379
4.舊版復(fù)制功能的實(shí)現(xiàn)(2.8以前的版本)
復(fù)制功能都分為兩個(gè)基本步驟:同步和命令傳播
同步:將從服務(wù)器的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器當(dāng)前所處的數(shù)據(jù)庫狀態(tài)。
命令傳播:主服務(wù)器的數(shù)據(jù)庫狀態(tài)被修改,導(dǎo)致主從服務(wù)器的數(shù)據(jù)庫狀態(tài)不一致,讓主從服務(wù)器數(shù)據(jù)庫重新回到一致狀態(tài)。
1.同步
當(dāng)客戶端向從服務(wù)器發(fā)送slaveof命令,要求從服務(wù)器復(fù)制主服務(wù)器時(shí),從服務(wù)器首先需要執(zhí)行同步操作,也就是將從服務(wù)器的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器當(dāng)前所處的數(shù)據(jù)庫狀態(tài)。而從服務(wù)器對主服務(wù)器的同步操作需要通過向主服務(wù)器發(fā)送SYNC命令來完成。
從服務(wù)器發(fā)送SYNC命令的執(zhí)行步驟:
a.從服務(wù)器向主服務(wù)器發(fā)送SYNC命令。
b.收到SYNC命令的主服務(wù)器執(zhí)行BGSAVE命令,在后臺生成一個(gè)RDB文件,并使用一個(gè)緩沖區(qū)記錄從現(xiàn)在開始執(zhí)行的所有寫命令。
c.當(dāng)主服務(wù)器的BGSAVE命令執(zhí)行完畢時(shí),主服務(wù)器會將BGSAVE命令生成的RDB文件發(fā)送給從服務(wù)器,從服務(wù)器接收接收并載入這個(gè)RBD文件,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器執(zhí)行BGSAVE命令時(shí)的數(shù)據(jù)庫狀態(tài)。
d.主服務(wù)器將記錄在緩沖區(qū)里面的所有寫命令發(fā)送給從服務(wù)器,從服務(wù)器執(zhí)行這些寫命令,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器當(dāng)前所處的狀態(tài)。
2.命令傳播
在執(zhí)行完同步操作以后,如果客戶端又再次向主服務(wù)器發(fā)送寫命令,如果此時(shí)該命令沒有傳播到從服務(wù)器,那么主從服務(wù)器的數(shù)據(jù)庫狀態(tài)必然會不一樣,因此,在執(zhí)行完同步操作以后,還必須得執(zhí)行命令傳播,用來傳播主服務(wù)器接收到的新的命令請求。
為了讓主從服務(wù)器再次回到一致狀態(tài),主服務(wù)器需要對從服務(wù)器執(zhí)行命令傳播操作:主服務(wù)器會將自己執(zhí)行的那條寫命令,發(fā)送給從服務(wù)器,當(dāng)從服務(wù)器執(zhí)行了相同的寫命令之后,主從服務(wù)器將再次回到一致狀態(tài)。
3.舊版復(fù)制存在的缺陷
從服務(wù)器對主服務(wù)器的復(fù)制分為以下兩種:
初次復(fù)制:從服務(wù)器沒有復(fù)制任何主服務(wù)器,或者從服務(wù)器當(dāng)前要復(fù)制的主服務(wù)器和上一次復(fù)制的主服務(wù)器不同。
斷線后重復(fù)制: 處理命令傳播階段的主從服務(wù)器因?yàn)榫W(wǎng)絡(luò)原因而中斷了復(fù)制,但從服務(wù)器通過自動(dòng)重連接重新連接上主服務(wù)器,并繼續(xù)復(fù)制主服務(wù)器 。
當(dāng)主從服務(wù)器斷開以后,從服務(wù)器通過自動(dòng)重連連上主服務(wù)器,然后從服務(wù)器向主服務(wù)器發(fā)送SYNC命令,進(jìn)行同步操作,但是主服務(wù)器此時(shí)會將數(shù)據(jù)庫狀態(tài)寫入到RDB文件中,如上述紅色方框(重復(fù)復(fù)制了許多鍵值對),這部分就是舊版復(fù)制存在的缺陷。
4.舊版復(fù)制問題的解決方案
為了解決舊版復(fù)制功能在處理斷線重復(fù)復(fù)制情況時(shí)的低效問題,redis從2.8以后,使用PSYNC命令代替SYNC命令來執(zhí)行復(fù)制時(shí)的同步操作。
psync命令具有完整重同步和部分重同步兩種模式。
完整重同步:用以解決初次復(fù)制的問題。執(zhí)行操作與sync一模一樣。
部分重同步:用于處理斷線后重復(fù)制情況:當(dāng)從服務(wù)器在斷線后重新連上主服務(wù)器時(shí),如果條件允許,主服務(wù)器可以將主從服務(wù)器連接斷開期間執(zhí)行的寫命令發(fā)送給從服務(wù)器,從服務(wù)器只要接收并執(zhí)行這些寫命令,就可以將數(shù)據(jù)更新至主服務(wù)器當(dāng)前所處的狀態(tài)。
PSYNC命令的部分重同步解決了舊版復(fù)制功能在處理斷線后重復(fù)復(fù)制時(shí)出現(xiàn)的低效情況。
主從服務(wù)器執(zhí)行部分重同步的過程:
5.部分重同步的實(shí)現(xiàn)
要實(shí)現(xiàn)部分重同步,必須解決以下三個(gè)問題:
1.當(dāng)前主從服務(wù)器各復(fù)制了多少數(shù)據(jù)??
2.如果主從服務(wù)器斷線以后,主服務(wù)器新接收到的命令請求,該如何處理?
3.如果在一個(gè)集群系統(tǒng)中,如何找到上一次復(fù)制的那個(gè)主服務(wù)器呢?
部分重同步功能由以下三個(gè)部分構(gòu)成:
a.主服務(wù)器的復(fù)制偏移量和從服務(wù)器的復(fù)制偏移量
b.主服務(wù)器的復(fù)制積壓緩沖區(qū)
c.服務(wù)器的運(yùn)行ID
typedef struct redisClient {
// 復(fù)制狀態(tài)
int replstate; /* replication state if this is a slave */
// 用于保存主服務(wù)器傳來的 RDB 文件的文件描述符
int repldbfd; /* replication DB file descriptor */
// 讀取主服務(wù)器傳來的 RDB 文件的偏移量
off_t repldboff; /* replication DB file offset */
// 主服務(wù)器傳來的 RDB 文件的大小
off_t repldbsize; /* replication DB file size */
sds replpreamble; /* replication DB preamble. */
// 主服務(wù)器的復(fù)制偏移量
long long reploff; /* replication offset if this is our master */
// 從服務(wù)器最后一次發(fā)送 REPLCONF ACK 時(shí)的偏移量
long long repl_ack_off; /* replication ack offset, if this is a slave */
// 從服務(wù)器最后一次發(fā)送 REPLCONF ACK 的時(shí)間
long long repl_ack_time;/* replication ack time, if this is a slave */
// 主服務(wù)器的 master run ID
// 保存在客戶端,用于執(zhí)行部分重同步
char replrunid[REDIS_RUN_ID_SIZE+1]; /* master run id if this is a master */
// 從服務(wù)器的監(jiān)聽端口號
int slave_listening_port; /* As configured with: SLAVECONF listening-port */
// 最后被寫入的全局復(fù)制偏移量
long long woff; /* Last write global replication offset. */
} redisClient;
下面我們對上面三個(gè)部分一一解釋一下:
復(fù)制偏移量
執(zhí)行復(fù)制的雙方---主從服務(wù)器都會維護(hù)一個(gè)復(fù)制偏移量。
主服務(wù)器每次向從服務(wù)器傳播N個(gè)字節(jié)的數(shù)據(jù)時(shí),就將自己的復(fù)制偏移量的值加上N。
從服務(wù)器每次接收到主服務(wù)器傳播來的N個(gè)字節(jié)的數(shù)據(jù)時(shí),就將自己的復(fù)制偏移量加上N。
通過對比主從服務(wù)器的復(fù)制偏移量,程序很容易知道主從服務(wù)器是否處于一致狀態(tài)。
主從狀態(tài)一致:
主從狀態(tài)不一致:
假如從服務(wù)器A在斷線后就立即重新連接主服務(wù)器,并且成功,那么接下來,從服務(wù)器將向主服務(wù)器發(fā)送PSYNC命令,報(bào)告從服務(wù)器A當(dāng)前的復(fù)制偏移量為10086,那么這時(shí),主服務(wù)器應(yīng)該對從服務(wù)器執(zhí)行完全重同步還是部分重同步?如果執(zhí)行部分重同步的話,主服務(wù)器又如何補(bǔ)償從服務(wù)器A在斷線期間丟失的那部分?jǐn)?shù)據(jù)呢?
復(fù)制積壓區(qū)
復(fù)制積壓區(qū)是由主服務(wù)器維護(hù)的一個(gè)固定長度的隊(duì)列,默認(rèn)大小為1M。
當(dāng)主服務(wù)器進(jìn)行命令傳播時(shí),它不僅將寫命令發(fā)送給所有從服務(wù)器,還會將寫命令入列到復(fù)制積壓區(qū)緩沖區(qū)里面。如下圖:
因此,主服務(wù)器的復(fù)制積壓區(qū)里面會保存著一部分最近傳播的寫命令,并且復(fù)制積壓緩沖區(qū)會為隊(duì)列中的每個(gè)字節(jié)記錄相應(yīng)的復(fù)制偏移量。
當(dāng)從服務(wù)器重新連上主服務(wù)器時(shí),從服務(wù)器會通過PSYNC命令將自己的復(fù)制偏移量offset發(fā)送給主服務(wù)器,主服務(wù)器會根據(jù)這個(gè)復(fù)制偏移量來決定對主服務(wù)器進(jìn)行何種復(fù)制操作:
如果offset偏移量之后的數(shù)據(jù),仍然存在于復(fù)制積壓區(qū)里面,那么主服務(wù)器將對從服務(wù)器執(zhí)行部分重同步操作。
如果offset偏移量之后的數(shù)據(jù),不在復(fù)制積壓區(qū)里面,那么主服務(wù)器將會對從服務(wù)器進(jìn)行完全重同步操作。
服務(wù)器允許ID
每個(gè)Redis服務(wù)器,不論是主服務(wù)器還是從服務(wù)器都會有自己的運(yùn)行ID。這個(gè)ID在服務(wù)器啟動(dòng)時(shí)自動(dòng)生成,由40個(gè)隨機(jī)十六進(jìn)制字符組成。
當(dāng)從服務(wù)器對主服務(wù)器進(jìn)行初次復(fù)制時(shí),主服務(wù)器會將自己的運(yùn)行ID傳送給從服務(wù)器,而從服務(wù)器會將這個(gè)運(yùn)行ID保存起來。
當(dāng)從服務(wù)器斷線并重連上一個(gè)主服務(wù)器時(shí),從服務(wù)器將向當(dāng)前連接的主服務(wù)器發(fā)送自己的之前保存的運(yùn)行ID:
如果ID一致,說明短線后重連的就是之前連接的服務(wù)器;
如果ID不一致,說明短信??重連的不是之前鏈接的服務(wù)器,那么主服務(wù)器將對從服務(wù)器進(jìn)行完整重同步操作。