第三部分 多機(jī)數(shù)據(jù)庫的實現(xiàn)
復(fù)制
1.舊版復(fù)制功能的實現(xiàn)
舊版復(fù)制分為兩個階段 :
同步
和命令傳播
-
同步過程的執(zhí)行步驟
- 從服務(wù)器向主服務(wù)器發(fā)送SYNC命令
- 收到SYNC命令后昼扛,主服務(wù)器開始執(zhí)行BGSAVE操作生成RDB文件澳淑,并使用一個緩沖區(qū)記錄現(xiàn)在開始執(zhí)行的所有寫命令(用于命令傳播階段保持?jǐn)?shù)據(jù)庫一致性)。
- 當(dāng)主服務(wù)器的BGSAVE操作執(zhí)行完時轻掩,主服務(wù)器會將BGSAVE命令生成的RDB文件發(fā)送給從服務(wù)器签钩,從服務(wù)器接收并載入這個RDB文件,將自己的數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器執(zhí)行BGSAVE命令時的數(shù)據(jù)庫狀態(tài)。
- 主服務(wù)器將記錄在緩沖區(qū)里面的所有寫命令發(fā)送給從服務(wù)器勺良,從服務(wù)器執(zhí)行這些寫命令。將自己數(shù)據(jù)庫狀態(tài)更新至主服務(wù)器數(shù)據(jù)庫當(dāng)前狀態(tài)骄噪。
-
命令傳播的過程
- 主服務(wù)器將執(zhí)行的寫命令尚困,發(fā)送給從服務(wù)器執(zhí)行,當(dāng)從服務(wù)器執(zhí)行了相同寫命令后链蕊,主從服務(wù)器將再次回到一致性狀態(tài)
-
SYNC命令流程圖
image
2.舊版復(fù)制功能的缺陷
- 在從服務(wù)器A
斷線
的情況(此時保存了A1 - A99鍵值對)事甜,當(dāng)服務(wù)器重連上主服務(wù)器時 (此時主服務(wù)器保存到了A120), 從服務(wù)器會再次向主服務(wù)器發(fā)送SYNC命令滔韵,此時主服務(wù)器又需要將數(shù)據(jù)庫的所有鍵值對保存到RDB文件中(A1-A120鍵值對)逻谦,然而從服務(wù)器A其實只需要掉線時期產(chǎn)生的A100-A120數(shù)據(jù)就能恢復(fù)成一致性狀態(tài)。 所以為了讓從服務(wù)器補(bǔ)足一小部分缺失的數(shù)據(jù)陪蜻,而讓主服務(wù)器重新執(zhí)行一次BGSAVE操作邦马,造成斷線重連后的效率會很低。
3.新版復(fù)制功能的實現(xiàn)
- 如何提高斷線重連后同步的效率呢宴卖? 實際上我們只需要保存斷線后的那部分寫命令滋将,重連后讓主服務(wù)器再發(fā)送過來給從服務(wù)器執(zhí)行就好了。
- 新版復(fù)制解決了舊版復(fù)制在處理斷線重連時的低效情況症昏。使用PSYNC命令代替SYNC命令執(zhí)行復(fù)制時的同步操作随闽。
- PSYNC命令具有
完整重同步
和部分重同步
兩種模式- 完整重同步用于處理初次復(fù)制的情況:與SYNC命令的執(zhí)行步驟基本一致 , 都是通過讓主服務(wù)器創(chuàng)建并發(fā)送RDB文件肝谭,以及向從服務(wù)器發(fā)送保存在緩沖區(qū)里面的寫命令來進(jìn)行同步橱脸。
- 而部分重同步用于處理斷線后重復(fù)制的情況:當(dāng)從服務(wù)器在斷線后重新連接主服務(wù)器時,如果條件允許分苇,主服務(wù)器可以將主從服務(wù)器連接斷開期間執(zhí)行的寫命令發(fā)送給從服務(wù)器添诉,從服務(wù)器只要接收并執(zhí)行這些寫命令,就可以將數(shù)據(jù)庫更新至主服務(wù)器當(dāng)前所在狀態(tài)了医寿。
- 由于部分重同步只需要將服務(wù)器缺少的寫命令發(fā)送給從服務(wù)器執(zhí)行舊可以了栏赴,因此所需資源更少,速度更快靖秩。
4.部分重同步的實現(xiàn)
-
復(fù)制偏移量
主服務(wù)器和從服務(wù)器都維護(hù)一個復(fù)制偏移量须眷。 主服務(wù)器每次向從服務(wù)器傳播N個字節(jié)的數(shù)據(jù)時,就將自己的復(fù)制偏移量的值加上N沟突。
從服務(wù)器每次收到主服務(wù)器傳播來的N個字節(jié)的數(shù)據(jù)時花颗,就將自己的復(fù)制偏移量加上N
-
可以很容易
通過判斷主從服務(wù)器的復(fù)制偏移量來判斷是否處于一致的狀態(tài)
image
如果發(fā)現(xiàn)偏移量不一致時,如何判斷接下來要進(jìn)行完整重同步 or 部分重同步惠拭。主服務(wù)器又如何補(bǔ)償?shù)艟€期間丟失的數(shù)據(jù)呢扩劝?與復(fù)制積壓緩沖區(qū)有關(guān)庸论。
-
復(fù)制積壓緩沖區(qū)
-
復(fù)制積壓緩沖區(qū)維護(hù)一個固定長度的先進(jìn)先出隊列
,默認(rèn)大小為1MBimage -
當(dāng)服務(wù)器進(jìn)行命令傳播時棒呛,不僅將命令發(fā)送給所有從服務(wù)器聂示,還將寫命令入隊到復(fù)制積壓緩沖區(qū)中
image 當(dāng)從服務(wù)器重連主服務(wù)器時,從服務(wù)器通過PSYNC命令將復(fù)制偏移量offset也發(fā)送給主服務(wù)器簇秒,如果offset偏移量之后的數(shù)據(jù)仍存在復(fù)制積壓緩沖區(qū)中鱼喉,則執(zhí)行部分重同步操作 ; 而如果offset偏移量之后的數(shù)據(jù)已不存在復(fù)制積壓緩沖區(qū)中(掉線太久)趋观,則執(zhí)行完整重同步操作扛禽。
復(fù)制積壓緩沖區(qū)的最小大小應(yīng)為 平均重連時間 * 主服務(wù)器每秒產(chǎn)生的寫命令數(shù)量 ; 為了保證大部分掉線情況都能使用部分重同步皱坛, 應(yīng)將復(fù)制積壓緩沖區(qū)設(shè)為 2*平均重連時間 * 主服務(wù)器每秒產(chǎn)生的寫命令數(shù)量
-
-
服務(wù)器運行ID
- 用于記錄判斷 斷線重連后的服務(wù)器是否為 原先連接的服務(wù)器
- 當(dāng)從服務(wù)器對主服務(wù)器進(jìn)行初次復(fù)制時编曼,主服務(wù)器會將自己的運行ID傳送給從服務(wù)器,而從服務(wù)器則會將這個ID保存起來麸恍。 當(dāng)從服務(wù)器重新連接時,從服務(wù)器向主服務(wù)器發(fā)送之前保存的服務(wù)器運行ID
- 如果從服務(wù)器保存的ID和 當(dāng)前連接的主服務(wù)器運行ID相同搀矫,則說明斷線前復(fù)制的就是當(dāng)前的主服務(wù)器抹沪,則再根據(jù)復(fù)制積壓緩沖區(qū)判斷進(jìn)行部分重同步 還是 完整重同步
- 相反,則說明線前復(fù)制的不是當(dāng)前的主服務(wù)器瓤球,則需要進(jìn)行完整重同步操作融欧。
5.PSYNC命令的實現(xiàn)
- PSYNC <runid> <offset> : runid表示上一次復(fù)制的主服務(wù)器運行ID , offset表示當(dāng)前復(fù)制偏移量
- 主服務(wù)器返回 FULLRESYNC <runid> <offset> : 表示執(zhí)行完整重同步操作,offset作為從服務(wù)器的初始偏移量
- 主服務(wù)器返回 CONTINUE : 表示執(zhí)行部分重同步操作卦羡。 只需等待主服務(wù)器將缺失數(shù)據(jù)重發(fā)即可噪馏。
6.復(fù)制的實現(xiàn)
SLAVEOF <master_ip> <master_port>
- 設(shè)置主服務(wù)器的地址和端口號
- 建立套接字連接(讓從服務(wù)器成為主服務(wù)器客戶端)
- 發(fā)送PING命令 (檢測主服務(wù)器連接狀況)
- 身份驗證
- 發(fā)送端口信息(讓主服務(wù)器保存從服務(wù)器的相關(guān)信息)
- 同步(發(fā)送PSYNC命令,執(zhí)行同步操作绿饵。 并且由單工通信編程雙工通信欠肾,即主從服務(wù)器互相成為對方的客戶端)
- 命令傳播
- 心跳檢測(從服務(wù)器會每秒1次的頻率,向主服務(wù)器發(fā)送命令
REPLCONF ACK <從服務(wù)器偏移量>
)- 檢測主從服務(wù)器網(wǎng)絡(luò)連接狀態(tài)
- 復(fù)制實現(xiàn)min-slaves功能 (當(dāng)主服務(wù)器的從服務(wù)器數(shù)量小于min-slaves時拟赊,則認(rèn)為集群掛了)
- 檢測命令丟失(主服務(wù)器進(jìn)行部分重同步時由于網(wǎng)絡(luò)問題而丟失刺桃,則通過心跳檢測到后進(jìn)行補(bǔ)發(fā))
總結(jié)
- 當(dāng)客戶端向從服務(wù)器發(fā)送SLAVEOF命令,要求從服務(wù)器復(fù)制主服務(wù)器時吸祟,從服務(wù)器首先需要執(zhí)行同步操作瑟慈。將從服務(wù)器的數(shù)據(jù)庫叢臺更新至主服務(wù)器當(dāng)前所處的數(shù)據(jù)庫狀態(tài)。 在同步操作完成后屋匕,這種一致性的狀態(tài)不是一成不變的葛碧,當(dāng)主服務(wù)器執(zhí)行客戶端的寫命令時,主從服務(wù)器的狀態(tài)舊不再一致过吻, 因此主服務(wù)器需要對從服務(wù)器執(zhí)行命令傳播操作进泼, 即主服務(wù)器將執(zhí)行的寫命令,發(fā)送給從服務(wù)器執(zhí)行。
- Redis 2.8以前的復(fù)制功能不能高效地處理斷線后重復(fù)制情況缘琅,但Redis2.8新添加的部分重同步功能可以解決這個問題粘都。
- 部分重同步通過
復(fù)制偏移量
、復(fù)制積壓緩沖區(qū)
刷袍、服務(wù)器運行
ID三個部分來實現(xiàn)翩隧。 - 在復(fù)制操作剛開始的時候,從服務(wù)器會成為主服務(wù)器的客戶端呻纹,并通過向主服務(wù)器發(fā)送命令請求來執(zhí)行復(fù)制步驟堆生。 而在復(fù)制的后期(同步階段),主從服務(wù)器會互相成為對方的客戶端 (主服務(wù)器發(fā)送命令(命令傳播雷酪,部分重同步)給從服務(wù)器來維護(hù)數(shù)據(jù)一致性)
- 主服務(wù)器通過向從服務(wù)器傳播命令來更新從服務(wù)器的狀態(tài)淑仆,保證主從服務(wù)器一致,而從服務(wù)器則通過向主服務(wù)器發(fā)送命令來進(jìn)行心跳檢測哥力,以及命令丟失檢測蔗怠。
Sentinel哨兵機(jī)制
Sentinel是Redis的高可用性解決方案:由一個或多個Sentinel實例組成的Sentinel系統(tǒng)可以監(jiān)視任意多個主服務(wù)器,以及這些主服務(wù)器下的從服務(wù)器吩跋,并在被監(jiān)視的主服務(wù)器進(jìn)行下線狀態(tài)時寞射,自動將被下線主服務(wù)器下的某個重服務(wù)器升級為主服務(wù)器,然后由新的主服務(wù)器代替已下線的主服務(wù)器繼續(xù)處理命令請求(失效轉(zhuǎn)移)
Sentinel的構(gòu)成
-
Setinel啟動時的執(zhí)行步驟
- 初始化服務(wù)器(Sentinel本質(zhì)上只是運行在特殊模式下的Redis服務(wù)器锌钮, 部分初始化過程不相同)
- 將普通Redis服務(wù)器使用的代碼替換成Sentinel專用代碼(如沒有g(shù)et,set 而有ping,INFO)
- 初始化Sentinel狀態(tài)
- 初始化Sentinel的監(jiān)視主服務(wù)器列表
- 創(chuàng)建連向主服務(wù)器的網(wǎng)絡(luò)連接 (創(chuàng)建命令連接和訂閱連接桥温; 前者用于向主服務(wù)器發(fā)送命令 。 而后者是訂閱主服務(wù)器的 _sentinel_hello頻道梁丘,用于接收其他主服務(wù)器信息的變更的頻道信息)
Setineal通過讀取配置文件侵浸,使用字典記錄所有的Master; 并通過給主服務(wù)器發(fā)送INFO命令氛谜,得出主服務(wù)器下的所有從服務(wù)器信息掏觉。
如何獲取主服務(wù)器信息
- Sentinel默認(rèn)會以每10秒一次的頻率,通過命令連接向被監(jiān)視的主服務(wù)器發(fā)送INFO命令值漫,并通過分析INFO命令的回復(fù)來獲取主服務(wù)器的當(dāng)前信息(狀態(tài)履腋,從服務(wù)器slaves信息)
如何獲取從服務(wù)器信息
-
當(dāng)Sentinel發(fā)現(xiàn)主服務(wù)器有新的從服務(wù)器出現(xiàn)時,Sentinel除了會為這個新的從服務(wù)器創(chuàng)建相應(yīng)實例結(jié)構(gòu)外惭嚣,Sentinel還會創(chuàng)建連接到從服務(wù)器的命令連接和訂閱連接
-
創(chuàng)建命令連接后遵湖,每10秒向從服務(wù)器發(fā)送INFO命令
- 獲取從服務(wù)器的運行ID,角色晚吞,優(yōu)先級延旧,偏移量, 該從服務(wù)器對應(yīng)的主服務(wù)器連接信息槽地,狀態(tài)
-
向主從服務(wù)器發(fā)送消息
在默認(rèn)情況下迁沫,Sentinel會以每兩秒一次的頻率芦瘾,通過命令連接向所有被監(jiān)視的主服務(wù)器和從服務(wù)器發(fā)送訂閱消息
PUBLISH sentinel_:hello <sentinel的信息,主服務(wù)器的信息>
接收來自主服務(wù)器和從服務(wù)器的頻道信息
當(dāng)Sentinel與一個主服務(wù)器或者從服務(wù)器建立建立起訂閱連接之后集畅,Sentinel就會通過訂閱連接近弟,向服務(wù)器發(fā)送以下命令。一直持續(xù)到Sentinel與服務(wù)器的連接斷開為止挺智。
對于監(jiān)視同一個服務(wù)器的多個Sentinel來說祷愉,一個Sentinel發(fā)送的信息會被其他Sentinel接收到,這些信息會被用于更新其他Sentinel對發(fā)送信息Sentinel的認(rèn)知赦颇,也會被用于更新其他Sentinel對被監(jiān)視服務(wù)器的認(rèn)知
檢測主觀下線狀態(tài)
- 在默認(rèn)情況下二鳄,Sentinel會以每秒一次的頻率向所有與它創(chuàng)建了命令連接的實例(包括主服務(wù)器,從服務(wù)器媒怯,其他Sentinel在內(nèi))發(fā)送PING命令订讼,并通過實例返回的PING命令回復(fù)來判斷實例是否在線。
- Sentinel配置文件中的
down-after-milliseconds
指定了判斷實例主觀下線所需的時間長度扇苞。如果在down-after-milliseconds時間內(nèi)連續(xù)發(fā)送了無效回復(fù)欺殿,則Sentinel將判定該實例主觀下線,并將對應(yīng)的實例結(jié)構(gòu)的flags設(shè)置為 SRI_S_DOWN鳖敷。 (Subjective 主觀)
檢測客觀下線狀態(tài)
- 當(dāng)Sentinel將一個主服務(wù)器判斷為
主觀下線后
脖苏,為了確認(rèn)這個主服務(wù)器是否真的下線了,它會向同樣監(jiān)視這一主服務(wù)器的其他Sentinel進(jìn)行詢問哄陶,看它們是否也認(rèn)為主服務(wù)器已經(jīng)進(jìn)入了下線狀態(tài)帆阳。 當(dāng)Sentinel從其他Sentinel那里接收到足夠數(shù)量的已下線判斷后哺壶,Sentinel就會將從服務(wù)器判斷為客觀下線
屋吨,并將主服務(wù)器執(zhí)行故障轉(zhuǎn)移
操作。 - 當(dāng)判斷為客觀下線后山宾,將flags設(shè)置為 SRI_O_DOWN (Objective 客觀)
如何選舉領(lǐng)頭Setinel
- 當(dāng)一個主服務(wù)器被判斷為客觀下線時至扰,監(jiān)視這個下線主服務(wù)器的各個Sentinel會進(jìn)行協(xié)商,選舉出一個領(lǐng)頭Sentinel资锰,并由領(lǐng)頭Sentinel對下線主服務(wù)器執(zhí)行故障轉(zhuǎn)移操作敢课。
##請求格式
Sentinel is-master-down-by-addr <master的ip地址> <端口號> <配置紀(jì)元>(標(biāo)志是否同一輪選舉) <SentinelId>
##響應(yīng)格式
<上下線狀態(tài)> <局部領(lǐng)頭id> <當(dāng)前紀(jì)元>
故障轉(zhuǎn)移
- 在已下線主服務(wù)器屬下的所有從服務(wù)器里面,挑選出一個從服務(wù)器绷杜,并將其轉(zhuǎn)換為主服務(wù)器(升級)
- 讓已下線主服務(wù)器屬下的所有從服務(wù)器改為復(fù)制新的主服務(wù)器
- 將已下線主服務(wù)器設(shè)置為新的主服務(wù)器的從服務(wù)器直秆,當(dāng)這個舊的服務(wù)器重新上線時,它就會成為新的主服務(wù)器的從服務(wù)器(降級)
集群
Redis集群是Redis提供的分布式數(shù)據(jù)庫方案鞭盟,集群通過分片來進(jìn)行數(shù)據(jù)共享圾结,并提供復(fù)制和故障轉(zhuǎn)移功能。
主要內(nèi)容有集群中的節(jié)點齿诉,槽指派筝野,命令執(zhí)行晌姚,重新分片,轉(zhuǎn)向歇竟,故障轉(zhuǎn)移挥唠,消息等方面
節(jié)點通過握手來將其他節(jié)點添加到自己所處的集群當(dāng)中 (clustermeet ip port)
集群中的16384個槽可以分別指派給集群中的各個節(jié)點,每個節(jié)點都會記錄哪些槽指派給了自己焕议,哪些槽又被指派給了其他節(jié)點宝磨。
clusterState.slots數(shù)組記錄了集群中所有槽的指派信息,使得能以O(shè)(1)時間判斷某個槽指派給了哪個節(jié)點号坡;而clusterNode.slots數(shù)組只記錄了clusterNode結(jié)構(gòu)所代表的節(jié)點的槽指派信息懊烤。使得交換槽信息時只需發(fā)送clusterNode.slots數(shù)組,讓接收信息的節(jié)點更新字典表中對應(yīng)的clusterNode即可宽堆。而不用遍歷clusterState中所有元素腌紧,標(biāo)志當(dāng)前節(jié)點所指派的槽,再將其信息進(jìn)行傳送畜隶,時間復(fù)雜度為O(n)壁肋。下圖為節(jié)點7000創(chuàng)建的clusterState結(jié)構(gòu)。
節(jié)點在接收到一個命令請求時籽慢,會先檢查這個命令請求要處理的鍵所在的槽是否由自己負(fù)責(zé)(查看clusterState.slots數(shù)組)浸遗,如果不是的話,節(jié)點將向客戶端返回一個MOVED操作箱亿, MOVED錯誤攜帶的信息可以指引客戶端轉(zhuǎn)向至正在負(fù)責(zé)相關(guān)槽的節(jié)點跛锌。
節(jié)點還會使用跳躍表來保存槽與鍵之間的關(guān)系 (方便比如 查看 屬于某個slot的數(shù)據(jù)庫鍵由哪些的命令)
-
對Redis集群的重新分片工作是由redis-trib負(fù)責(zé)的,重新分片的關(guān)鍵是將屬于某個槽的所有鍵值對從一個節(jié)點轉(zhuǎn)移到另一個節(jié)點届惋。
image 如果節(jié)點A正在遷移槽i到節(jié)點B髓帽,那么當(dāng)節(jié)點A沒能在自己的數(shù)據(jù)庫中找到命令指定的數(shù)據(jù)庫鍵時,節(jié)點A會向客戶端返回一個ASK錯誤脑豹,指引客戶端到節(jié)點B繼續(xù)查找指定的數(shù)據(jù)庫鍵郑藏。
MOVED錯誤表示槽的負(fù)責(zé)權(quán)已經(jīng)從一個節(jié)點轉(zhuǎn)移到了另一個節(jié)點,而ASK錯誤只是兩個節(jié)點在遷移槽的過程中使用的一種臨時措施瘩欺。
-
集群里的從節(jié)點用于復(fù)制主節(jié)點必盖,并在主節(jié)點下線時,代替主節(jié)點繼續(xù)處理請求俱饿。
image 集群中的節(jié)點通過發(fā)送和接收消息來通信歌粥,常見的消息包括MEET,PING拍埠,PONG失驶,PUBLISH,F(xiàn)AIL五種械拍。