Redis集群->拓?fù)浣Y(jié)構(gòu)
Redis 集群是一個(gè)網(wǎng)狀結(jié)構(gòu),無中心結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都通過 TCP 連接跟其他每個(gè)節(jié)點(diǎn)連接。
在一個(gè)有 N 個(gè)節(jié)點(diǎn)的集群中,每個(gè)節(jié)點(diǎn)都有 N-1 個(gè)流出的 TCP 連接,和 N-1 個(gè)流入的連接。 這些 TCP 連接會永久保持,并不是按需創(chuàng)建的。
節(jié)點(diǎn)們使用一個(gè) gossip 協(xié)議來傳播集群的信息,這樣可以:發(fā)現(xiàn)新的節(jié)點(diǎn)、 發(fā)送ping包(用來確保所有節(jié)點(diǎn)都在正常工作中)、在特定情況發(fā)生時(shí)發(fā)送集群消息。集群連接也用于在集群中發(fā)布或訂閱消息。

Redis集群->鍵分布模型
鍵空間被分割為 16384 槽(slot),事實(shí)上集群的最大節(jié)點(diǎn)數(shù)量是 16384 個(gè)。(然而建議最大節(jié)點(diǎn)數(shù)量設(shè)置在1000這個(gè)數(shù)量級上)
Hash Key如下:
HASH_SLOT = CRC16(key) mod 16384
舉個(gè)例子:
有三個(gè)Redis節(jié)點(diǎn),節(jié)點(diǎn)1、2、3負(fù)責(zé)這些Slot范圍:0 - 5461,5462 - 10924,10924 - 16384

關(guān)于SLot的如何分配的?
官方提供了redis-trib的Ruby程序,其實(shí)就是集群管理工具,這個(gè)程序通過向?qū)嵗l(fā)送特殊命令來完成創(chuàng)建新集群, 檢查集群, 或者對集群進(jìn)行重新分片(reshared)等工作。Reshard是將Redis節(jié)點(diǎn)負(fù)現(xiàn)的Slot進(jìn)行重分配,運(yùn)維只需要發(fā)出Reshard指令,Reshard的過程是Redis集群自動進(jìn)行的
Redis集群->客戶端實(shí)現(xiàn)
Moved重定向
一個(gè) Redis 客戶端可以自由地向集群中的任意節(jié)點(diǎn)(包括從節(jié)點(diǎn))發(fā)送查詢。接收的節(jié)點(diǎn)會分析查詢,如果這個(gè)命令是集群可以執(zhí)行的(就是查詢中只涉及一個(gè)鍵),那么節(jié)點(diǎn)會找這個(gè)鍵所屬的哈希槽對應(yīng)的節(jié)點(diǎn)。
如果剛好這個(gè)節(jié)點(diǎn)就是對應(yīng)這個(gè)哈希槽,那么這個(gè)查詢就直接被節(jié)點(diǎn)處理掉。否則這個(gè)節(jié)點(diǎn)會查看它內(nèi)部的 哈希槽 -> 節(jié)點(diǎn)ID 映射,然后給客戶端返回一個(gè) MOVED 錯(cuò)誤。
這個(gè)錯(cuò)誤包括鍵(3999)的哈希槽和能處理這個(gè)查詢的節(jié)點(diǎn)的 ip:端口號(127.0.0.1:6381)??蛻舳诵枰匦掳l(fā)送查詢到給定 ip 地址和端口號的節(jié)點(diǎn)。 注意,即使客戶端在重發(fā)查詢之前等待了很長一段時(shí)間,與此同時(shí)集群的配置信息發(fā)生改變,如果哈希槽 3999 現(xiàn)在是為其他節(jié)點(diǎn)服務(wù),那么目標(biāo)節(jié)點(diǎn)會再向客戶端回復(fù)一個(gè) MOVED 錯(cuò)誤。
當(dāng)集群是穩(wěn)定的時(shí)候,所有客戶端最終都會得到一份哈希槽 -> 節(jié)點(diǎn)的映射表,這樣能使得集群效率非常高:客戶端直接定位目標(biāo)節(jié)點(diǎn),不用重定向、或代理或發(fā)生其他單點(diǎn)故障
Ask重定向
為什么我們不能單純地使用 MOVED 重定向呢?因?yàn)楫?dāng)我們使用 MOVED 的時(shí)候,意味著我們認(rèn)為哈希槽永久地被另一個(gè)不同的節(jié)點(diǎn)處理,并且希望接下來的所有查詢都嘗試發(fā)到這個(gè)指定的節(jié)點(diǎn)上去。而 ASK 意味著我們只要下一個(gè)查詢發(fā)送到指定節(jié)點(diǎn)上去。

上圖中,配置Slot1需要從節(jié)點(diǎn)A遷移到節(jié)點(diǎn)B,遷移過程中Key1、Key2已遷到節(jié)點(diǎn)B,而Key3、Key4仍在節(jié)點(diǎn)A??蛻舳酥氨4娴挠成浔碇蠸lot1 -> 節(jié)點(diǎn)A,所以客戶端來節(jié)點(diǎn)A來查詢Key1、Key2時(shí),節(jié)點(diǎn)A已不負(fù)責(zé)這兩個(gè)Key,因而返回Ask重定向,通知客戶端去節(jié)點(diǎn)B去獲取數(shù)據(jù)。由于是Ask重定向,客戶并不會更新Slot1 -> 節(jié)點(diǎn)A的映射表(除非遷移過程完成),這樣在獲取Key3、Key4的數(shù)據(jù)時(shí),客戶端可以一次性獲取到。
Redis集群->一致性保證
Redis 并不能保證數(shù)據(jù)的強(qiáng)一致性. 這意味這在實(shí)際中集群在特定的條件下可能會丟失寫操作.
- 原因是因?yàn)榧菏怯昧水惒綇?fù)制. 寫操作過程:
客戶端向主節(jié)點(diǎn)B寫入一條命令.
主節(jié)點(diǎn)B向客戶端回復(fù)命令狀態(tài).
主節(jié)點(diǎn)將寫操作復(fù)制給他得從節(jié)點(diǎn) B1, B2 和 B3.
主節(jié)點(diǎn)對命令的復(fù)制工作發(fā)生在返回命令回復(fù)之后, 因?yàn)槿绻看翁幚砻钫埱蠖夹枰却龔?fù)制操作完成的話, 那么主節(jié)點(diǎn)處理命令請求的速度將極大地降低 —— 我們必須在性能和一致性之間做出權(quán)衡。 注意:Redis 集群可能會在將來提供同步寫的方法。 -
Redis 集群另外一種可能會丟失命令的情況是集群出現(xiàn)了網(wǎng)絡(luò)分區(qū), 并且一個(gè)客戶端與至少包括一個(gè)主節(jié)點(diǎn)在內(nèi)的少數(shù)實(shí)例被孤立。
image.png
Redis集群->同步時(shí)鐘
Redis集群->集群階段,邏輯時(shí)鐘
本質(zhì)上說,epoch 是一個(gè)集群里的邏輯時(shí)鐘,并決定一個(gè)給定的消息贏了另一個(gè)帶著更小 epoch 的消息。
,它是用來記錄事件的版本號,所以當(dāng)有多個(gè)節(jié)點(diǎn)提供了沖突的信息的時(shí)候,另外的節(jié)點(diǎn)就可以通過這個(gè)狀態(tài)來了解哪個(gè)是最新的。 currentEpoch 是一個(gè) 64bit 的 unsigned 數(shù)。
Redis 集群中的每個(gè)節(jié)點(diǎn),包括主節(jié)點(diǎn)和從節(jié)點(diǎn),都在創(chuàng)建的時(shí)候設(shè)置了 currentEpoch 為0。
當(dāng)節(jié)點(diǎn)接收到來自其他節(jié)點(diǎn)的 ping 包或 pong 包的時(shí)候,如果發(fā)送者的 epoch(集群連接消息頭部的一部分)大于該節(jié)點(diǎn)的 epoch,那么更新發(fā)送者的 epoch 為 currentEpoch。
由于這個(gè)語義,最終所有節(jié)點(diǎn)都會支持集群中較大的 epoch。
Redis集群->配置階段,配置時(shí)鐘
每一個(gè)主節(jié)點(diǎn)總是通過發(fā)送 ping 包和 pong 包向別人宣傳它的 configEpoch 和一份表示它負(fù)責(zé)的哈希槽的位圖。
當(dāng)一個(gè)新節(jié)點(diǎn)被創(chuàng)建的時(shí)候,主節(jié)點(diǎn)中的 configEpoch 設(shè)為零。
從節(jié)點(diǎn)由于故障轉(zhuǎn)移事件被提升為主節(jié)點(diǎn)時(shí),為了取代它那失效的主節(jié)點(diǎn),會把 configEpoch 設(shè)置為它贏得選舉的時(shí)候的 configEpoch 值。
configEpoch 用于在不同節(jié)點(diǎn)提出不同的配置信息的時(shí)候(這種情況或許會在分區(qū)之后發(fā)生)解決沖突。(按我理解:ConfigEpoch是管配置更新的,有需要才去更新configEpoch,而像Epoch是定時(shí)包來更新)
從節(jié)點(diǎn)也會在 ping 包和 pong 包中向別人宣傳它的 configEpoch 域,不過從節(jié)點(diǎn)的這個(gè)域表示的是上一次跟它的主節(jié)點(diǎn)交換數(shù)據(jù)的時(shí)候主節(jié)點(diǎn)的 configEpoch 值。這能讓其他個(gè)體檢測出從節(jié)點(diǎn)的配置信息是不是需要更新了(主節(jié)點(diǎn)不會給一個(gè)配置信息過時(shí)的從節(jié)點(diǎn)投票)。
每次由于一些已知節(jié)點(diǎn)的值比自己的值大而更新 configEpoch 值,它都會永久性地存儲在 nodes.conf 文件中。
當(dāng)一個(gè)節(jié)點(diǎn)重啟,它的 configEpoch 值被設(shè)為所有已知節(jié)點(diǎn)中最大的那個(gè) configEpoch 值。
Redis集群->選舉機(jī)制
從節(jié)點(diǎn)的選舉和提升都是由從節(jié)點(diǎn)處理的,主節(jié)點(diǎn)會投票要提升哪個(gè)從節(jié)點(diǎn)。一個(gè)從節(jié)點(diǎn)的選舉是在主節(jié)點(diǎn)被至少一個(gè)具有成為主節(jié)點(diǎn)必備條件的從節(jié)點(diǎn)標(biāo)記為 FAIL 的狀態(tài)的時(shí)候發(fā)生的。
當(dāng)以下條件滿足時(shí),一個(gè)從節(jié)點(diǎn)可以發(fā)起選舉:
- 該從節(jié)點(diǎn)的主節(jié)點(diǎn)處于 FAIL 狀態(tài)。
- 這個(gè)主節(jié)點(diǎn)負(fù)責(zé)的哈希槽數(shù)目不為零。
-
從節(jié)點(diǎn)和主節(jié)點(diǎn)之間的重復(fù)連接(replication link)斷線不超過一段給定的時(shí)間,這是為了確保從節(jié)點(diǎn)的數(shù)據(jù)是可靠的。
image.png
選舉完成后,從節(jié)點(diǎn)正式提升為主節(jié)點(diǎn),并需要更新其在集群中負(fù)責(zé)的配置
image.png
Redis集群 -> 備份遷移
Redis 集群實(shí)現(xiàn)了一個(gè)叫做備份遷移(replica migration)的概念,以提高系統(tǒng)的可用性。在集群中有主節(jié)點(diǎn)-從節(jié)點(diǎn)的設(shè)定,如果主從節(jié)點(diǎn)間的映射關(guān)系是固定的,那么久而久之,當(dāng)發(fā)生多個(gè)單一節(jié)點(diǎn)獨(dú)立故障的時(shí)候,系統(tǒng)可用性會變得很有限。(其實(shí)就是一主多備的機(jī)制,多備的機(jī)器可以復(fù)用給其它的主)

假設(shè)集群有兩個(gè)主節(jié)點(diǎn) A,B。
1.節(jié)點(diǎn) A 有一個(gè)從節(jié)點(diǎn)A1 。節(jié)點(diǎn) B 有兩個(gè)從節(jié)點(diǎn):B1 和 B2。
2.主節(jié)點(diǎn) B 失效。B1 被提升為主節(jié)點(diǎn)。
3.節(jié)點(diǎn) B2 遷移成為節(jié)點(diǎn) A1 的從節(jié)點(diǎn),要不然 A1 就沒有任何從節(jié)點(diǎn)。
4.三個(gè)小時(shí)后節(jié)點(diǎn) A1 也失效了。
5.節(jié)點(diǎn) B2 被提升為取代 A1 的新主節(jié)點(diǎn)。
集群仍然能繼續(xù)正常工作。選新備機(jī)的方法:找備機(jī)最多的主要


