Redis 三種集群模式,你還傻傻分不清嗎?
前言
Redis 作為一種高性能的內(nèi)存數(shù)據(jù)庫(kù),普遍用于目前主流的分布式架構(gòu)系統(tǒng)中。為了提高系統(tǒng)的容錯(cuò)率,使用多實(shí)例的 Redis 也是必不可免的,但同樣復(fù)雜度也相比單實(shí)例高出很多。本文主要會(huì)介紹 Redis 在多機(jī)數(shù)據(jù)庫(kù)下的三種實(shí)現(xiàn)。
主從模式
Redis 的主從模式指的就是主從復(fù)制。
用戶可以通過(guò) SLAVEOF 命令或者配置的方式,讓一個(gè)服務(wù)器去復(fù)制另一個(gè)服務(wù)器即成為它的從服務(wù)器。
主從模式架構(gòu)
Redis 如何實(shí)現(xiàn)主從模式?
Redis 的從服務(wù)器在向主服務(wù)器發(fā)起同步時(shí),一般會(huì)使用 SYNC 或 PSYNC 命令。
初次同步
當(dāng)從服務(wù)器收到 SLAVEOF 命令后,會(huì)向其主服務(wù)器執(zhí)行同步操作,進(jìn)入主從復(fù)制流程。
- 從服務(wù)器向主服務(wù)器發(fā)起SYNC 或 PSYNC 命令
- 主服務(wù)器執(zhí)行 BGSAVE命令,生成 RDB 文件,并使用緩存區(qū)記錄從現(xiàn)在開(kāi)始的所有寫(xiě)命令
- RDB 文件生成完成后,主服務(wù)器會(huì)將其發(fā)送給從服務(wù)器
- 從服務(wù)器載入 RDB 文件,將自己的數(shù)據(jù)庫(kù)狀態(tài)同步更新為主服務(wù)器執(zhí)行 BGSAVE命令時(shí)的狀態(tài)。
- 主服務(wù)器將緩沖區(qū)的所有寫(xiě)命令發(fā)送給從服務(wù)器,從服務(wù)將執(zhí)行這些寫(xiě)命令,數(shù)據(jù)庫(kù)狀態(tài)同步為主服務(wù)器最新?tīng)顟B(tài)。
SYNC 與 PSYNC 的區(qū)別
當(dāng)主從同步完成后,如果此時(shí)從服務(wù)器宕機(jī)了一段時(shí)間,重新上線后勢(shì)必要重新同步一下主服務(wù)器,SYNC與 PSYNC命令的區(qū)別就在于斷線后重復(fù)制階段處理的方式不同。
- SYNC
從服務(wù)器重新向主服務(wù)器發(fā)起 SYNC命令,主服務(wù)器將所有數(shù)據(jù)再次重新生成 RDB 快照發(fā)給從服務(wù)器開(kāi)始同步
- PSYNC
從服務(wù)器重新向主服務(wù)器發(fā)起 PSYNC命令。主服務(wù)器根據(jù)雙方數(shù)據(jù)的偏差量判斷是否是需要完整重同步還是僅將斷線期間執(zhí)行過(guò)的寫(xiě)命令發(fā)給從服務(wù)器。
明顯可以發(fā)先 PSYNC 相比 SYNC 效率好很多,要知道同步所有數(shù)據(jù)是一個(gè)非常費(fèi)資源(磁盤(pán) IO,網(wǎng)絡(luò))的操作,而如果只是因?yàn)槎虝壕W(wǎng)絡(luò)不穩(wěn)定就同步所有資源是非常不值的。因此 Redis 在 2.8 版本后都開(kāi)始使用 PSYNC 進(jìn)行復(fù)制
PSYNC 如何實(shí)現(xiàn)部分重同步?
實(shí)現(xiàn)部分重同步主要靠三部分
1. 記錄復(fù)制偏移量
主服務(wù)器與從服務(wù)器都會(huì)維護(hù)一個(gè)復(fù)制偏移量。
- 當(dāng)主服務(wù)器向從服務(wù)器發(fā)送 N 個(gè)字節(jié)的數(shù)據(jù)后,會(huì)將自己的復(fù)制偏移量 +N。
- 當(dāng)從服務(wù)器收到主服務(wù)器 N 個(gè)字節(jié)大小數(shù)據(jù)后,也會(huì)將自己的復(fù)制偏移量 +N。
當(dāng)主從雙方數(shù)據(jù)是同步時(shí),這個(gè)偏移量是相等的。而一旦有個(gè)從服務(wù)器斷線一段時(shí)間而少收到了部分?jǐn)?shù)據(jù)。那么此時(shí)主從雙方的服務(wù)器偏移量是不相等的,而他們的差值就是少傳輸?shù)淖止?jié)數(shù)量。如果少傳輸?shù)臄?shù)據(jù)量不是很大,沒(méi)有超過(guò)主服務(wù)器的復(fù)制積壓緩沖區(qū)大小,那么將會(huì)直接將緩沖區(qū)內(nèi)容發(fā)送給從服務(wù)器避免完全重同步。反之還是需要完全重同步的。
2. 復(fù)制積壓緩沖區(qū)
復(fù)制積壓緩沖區(qū)是由主服務(wù)器維護(hù)的一個(gè)先進(jìn)先出的字節(jié)隊(duì)列,默認(rèn)大小是 1mb。每當(dāng)向從服務(wù)器發(fā)送寫(xiě)命令時(shí),都會(huì)將這些數(shù)據(jù)存入這個(gè)隊(duì)列。每個(gè)字節(jié)都會(huì)記錄自己的復(fù)制偏移量。從服務(wù)器在重連時(shí)會(huì)將自己的復(fù)制偏移量發(fā)送給主服務(wù)器,如果該復(fù)制偏移量之后的數(shù)據(jù)存在于復(fù)制積壓緩沖區(qū)中,則僅需要將之后的數(shù)據(jù)發(fā)送給從服務(wù)器即可。
3. 記錄服務(wù)器 ID
當(dāng)執(zhí)行主從同步時(shí),主服務(wù)器會(huì)將自己的服務(wù)器 ID (一般是自動(dòng)生成的 UUID ) 發(fā)送給從服務(wù)器。從服務(wù)器在斷線恢復(fù)后會(huì)判斷該 ID 是否為當(dāng)前連接的主服務(wù)器。如果是同一個(gè) ID 則代表主服務(wù)器沒(méi)變嘗試部分重同步。如果不是同一個(gè) ID 代表主服務(wù)有變動(dòng),則會(huì)與主服務(wù)器完全重同步。
具體流程圖如下:
Redis 哨兵模式 (Sentinel)
Redis 主從模式雖然能做到很好的數(shù)據(jù)備份,但是他并不是高可用的。一旦主服務(wù)器點(diǎn)宕機(jī)后,只能通過(guò)人工去切換主服務(wù)器。因此 Redis 的哨兵模式也就是為了解決主從模式的高可用方案。
哨兵模式引入了一個(gè) Sentinel 系統(tǒng)去監(jiān)視主服務(wù)器及其所屬的所有從服務(wù)器。一旦發(fā)現(xiàn)有主服務(wù)器宕機(jī)后,會(huì)自動(dòng)選舉其中的一個(gè)從服務(wù)器升級(jí)為新主服務(wù)器以達(dá)到故障轉(zhuǎn)義的目的。
同樣的 Sentinel 系統(tǒng)也需要達(dá)到高可用,所以一般也是集群,互相之間也會(huì)監(jiān)控。而 Sentinel 其實(shí)本身也是一個(gè)以特殊模式允許 Redis 服務(wù)器。
實(shí)現(xiàn)原理
1.Sentinel 與主從服務(wù)器建立連接
- Sentinel 服務(wù)器啟動(dòng)之后便會(huì)創(chuàng)建于主服務(wù)器的 命令連接 ,并訂閱主服務(wù)器的 sentinel:hello 頻道以創(chuàng)建 訂閱連接
- Sentinel 默認(rèn)會(huì)每 10 秒向主服務(wù)器發(fā)送 INFO 命令,主服務(wù)器則會(huì)返回主服務(wù)器本身的信息,以及其所有從服務(wù)器的信息。
- 根據(jù)返回的信息,Sentinel 服務(wù)器如果發(fā)現(xiàn)有新的從服務(wù)器上線后也會(huì)像連接主服務(wù)器時(shí)一樣,向從服務(wù)器同時(shí)創(chuàng)建命令連接與訂閱連接。
2.判定主服務(wù)器是否下線
每一個(gè) Sentinel 服務(wù)器每秒會(huì)向其連接的所有實(shí)例包括主服務(wù)器,從服務(wù)器,其他 Sentinel 服務(wù)器)發(fā)送 PING命令,根據(jù)是否回復(fù) PONG 命令來(lái)判斷實(shí)例是否下線。
判定主觀下線
如果實(shí)例在收到 PING命令的 down-after-milliseconds 毫秒內(nèi)(根據(jù)配置),未有有效回復(fù)。則該實(shí)例將會(huì)被發(fā)起 PING命令的 Sentinel 認(rèn)定為主觀下線。
判定客觀下線
當(dāng)一臺(tái)主服務(wù)器被某個(gè) Sentinel 服務(wù)器判定為客觀下線時(shí),為了確保該主服務(wù)器是真的下線, Sentinel 會(huì)向 Sentinel 集群中的其他的服務(wù)器確認(rèn),如果判定主服務(wù)器下線的 Sentinel 服務(wù)器達(dá)到一定數(shù)量時(shí)(一般是 N/2+1),那么該主服務(wù)器將會(huì)被判定為客觀下線,需要進(jìn)行故障轉(zhuǎn)移。
3.選舉領(lǐng)頭 Sentinel
當(dāng)有主服務(wù)器被判定客觀下線后,Sentinel 集群會(huì)選舉出一個(gè)領(lǐng)頭 Sentinel 服務(wù)器來(lái)對(duì)下線的主服務(wù)器進(jìn)行故障轉(zhuǎn)移操作。整個(gè)選舉其實(shí)是基于 RAFT 一致性算法而實(shí)現(xiàn)的,大致的思路如下:
- 每個(gè)發(fā)現(xiàn)主服務(wù)器下線的 Sentinel 都會(huì)要求其他 Sentinel 將自己設(shè)置為局部領(lǐng)頭 Sentinel。
- 接收到的 Sentinel 可以同意或者拒絕
- 如果有一個(gè) Sentinel 得到了半數(shù)以上 Sentinel 的支持則在此次選舉中成為領(lǐng)頭 Sentinel。
- 如果給定時(shí)間內(nèi)沒(méi)有選舉出領(lǐng)頭 Sentinel,那么會(huì)再一段時(shí)間后重新開(kāi)始選舉,直到選舉出領(lǐng)頭 Sentinel。
4.選舉新的主服務(wù)器
領(lǐng)頭服務(wù)器會(huì)從從服務(wù)中挑選出一個(gè)最合適的作為新的主服務(wù)器。挑選的規(guī)則是:
- 選擇健康狀態(tài)的從節(jié)點(diǎn),排除掉斷線的,最近沒(méi)有回復(fù)過(guò) INFO命令的從服務(wù)器。
- 選擇優(yōu)先級(jí)配置高的從服務(wù)器
- 選擇復(fù)制偏移量大的服務(wù)器(表示數(shù)據(jù)最全)
挑選出新的主服務(wù)器后,領(lǐng)頭服務(wù)器將會(huì)向新主服務(wù)器發(fā)送 SLAVEOF no one命令將他真正升級(jí)為主服務(wù)器,并且修改其他從服務(wù)器的復(fù)制目標(biāo),將舊的主服務(wù)器設(shè)為從服務(wù)器,以此來(lái)達(dá)到故障轉(zhuǎn)移。
Redis Cluster
Redis 哨兵模式實(shí)現(xiàn)了高可用,讀寫(xiě)分離,但是其主節(jié)點(diǎn)仍然只有一個(gè),即寫(xiě)入操作都是在主節(jié)點(diǎn)中,這也成為了性能的瓶頸。
因此 Redis 在 3.0 后加入了 Cluster 模式,它采用去無(wú)心節(jié)點(diǎn)方式實(shí)現(xiàn),集群將會(huì)通過(guò)分片方式保存數(shù)據(jù)庫(kù)中的鍵值對(duì)
節(jié)點(diǎn)
一個(gè) Redis 集群中會(huì)由多個(gè)節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)都是互相連接的,會(huì)保存自己與其他節(jié)點(diǎn)的信息。節(jié)點(diǎn)之間通過(guò) gossip 協(xié)議交換互相的狀態(tài),以及保新加入的節(jié)點(diǎn)信息。
數(shù)據(jù)的 Sharding
Redis Cluster 的整個(gè)數(shù)據(jù)庫(kù)將會(huì)被分為 16384 個(gè)哈希槽,數(shù)據(jù)庫(kù)中的每個(gè)鍵都屬于這 16384 個(gè)槽中的其中一個(gè),集群中的每個(gè)節(jié)點(diǎn)可以處 0 個(gè)或者最多 16384 個(gè)槽。
設(shè)置槽指派
通過(guò)命令 CLUSTER ADDSLOTS
如 127.0.0.1:7777> CLUSTER ADDSLOTS 1 2 3 4 5 命令就是將 1,2,3,4,5 號(hào)插槽指派給本地端口號(hào)為 7777 的節(jié)點(diǎn)負(fù)責(zé)。
設(shè)置后節(jié)點(diǎn)將會(huì)將槽指派的信息發(fā)送給其他集群,讓其他集群更新信息。
計(jì)算鍵屬于哪個(gè)槽
- def slot_number(key):
- return CRC16(key) & 16383
計(jì)算哈希槽位置其實(shí)使用的是 CRC16 算法對(duì)鍵值進(jìn)行計(jì)算后再對(duì) 16383 取模得到最終所屬插槽。
也可以使用 CLUSTER KEYSLOT
Sharding 流程
- 當(dāng)客戶端發(fā)起對(duì)鍵值對(duì)的操作指令后,將任意分配給其中某個(gè)節(jié)點(diǎn)
- 節(jié)點(diǎn)計(jì)算出該鍵值所屬插槽
- 判斷當(dāng)前節(jié)點(diǎn)是否為該鍵所屬插槽
- 如果是的話直接執(zhí)行操作命令
- 如果不是的話,向客戶端返回 moved 錯(cuò)誤,moved 錯(cuò)誤中將帶著正確的節(jié)點(diǎn)地址與端口,客戶端收到后可以直接轉(zhuǎn)向至正確節(jié)點(diǎn)
Redis Cluster 的高可用
Redis 的每個(gè)節(jié)點(diǎn)都可以分為主節(jié)點(diǎn)與對(duì)應(yīng)從節(jié)點(diǎn)。主節(jié)點(diǎn)負(fù)責(zé)處理槽,從節(jié)點(diǎn)負(fù)責(zé)復(fù)制某個(gè)主節(jié)點(diǎn),并在主節(jié)點(diǎn)下線時(shí),代替下線的主節(jié)點(diǎn)。
如何實(shí)現(xiàn)故障轉(zhuǎn)移
其實(shí)與哨兵模式類(lèi)似,Redis 的每個(gè)節(jié)點(diǎn)都會(huì)定期向其他節(jié)點(diǎn)發(fā)送 Ping 消息,以此來(lái)檢測(cè)對(duì)方是否在線。當(dāng)一個(gè)節(jié)點(diǎn)檢測(cè)到另一個(gè)節(jié)點(diǎn)下線后,會(huì)將其設(shè)置為疑似下線。如果一個(gè)機(jī)器中,有半數(shù)以上的節(jié)點(diǎn)將某個(gè)主節(jié)點(diǎn)設(shè)為疑似下線,則該節(jié)點(diǎn)將會(huì)被標(biāo)記為已下線狀態(tài),并開(kāi)始執(zhí)行故障轉(zhuǎn)移。
- 通過(guò) raft 算法從下線主節(jié)點(diǎn)的從節(jié)點(diǎn)中選出新的主節(jié)點(diǎn)
- 被選中的從節(jié)點(diǎn)執(zhí)行 SLAVEOF no one 命令,成為新的主節(jié)點(diǎn)
- 新的主節(jié)點(diǎn)撤銷(xiāo)掉已下線主節(jié)點(diǎn)的槽指派,并將這些槽指給自己
- 新的主節(jié)點(diǎn)向集群中廣播自己由從節(jié)點(diǎn)變?yōu)橹鞴?jié)點(diǎn)
- 新的主節(jié)點(diǎn)開(kāi)始接受和負(fù)責(zé)自己處理槽的有關(guān)命令請(qǐng)求
總結(jié)
本文主要介紹了 Redis 三種集群模式,總結(jié)一下
主從模式 可以實(shí)現(xiàn)讀寫(xiě)分離,數(shù)據(jù)備份。但是并不是「高可用」的
哨兵模式 可以看做是主從模式的「高可用」版本,其引入了 Sentinel 對(duì)整個(gè) Redis 服務(wù)集群進(jìn)行監(jiān)控。但是由于只有一個(gè)主節(jié)點(diǎn),因此仍然有寫(xiě)入瓶頸。
Cluster 模式 不僅提供了高可用的手段,同時(shí)數(shù)據(jù)是分片保存在各個(gè)節(jié)點(diǎn)中的,可以支持高并發(fā)的寫(xiě)入與讀取。當(dāng)然實(shí)現(xiàn)也是其中最復(fù)雜的。