您好,登錄后才能下訂單哦!
這篇文章主要介紹“Redis高可用集群是什么”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Redis高可用集群是什么”文章能幫助大家解決問題。
幾種 Redis 高可用性的解決方案。包括:「主從模式」、「哨兵機制」以及「哨兵集群」。
「主從模式」具有讀寫分離,分擔讀壓力、數據備份,提供多個副本等優點。
「哨兵機制」在主節點故障后能自動將從節點提升成主節點,不需要人工干預操作就能恢復服務可用。
「哨兵集群」解決單點故障以及單機哨兵產生「誤判」問題。
Redis 從最簡單的單機版,經過數據持久化、主從多副本、哨兵集群,通過這么一番的優化,不管是性能還是穩定性,都越來越高。
但是隨著時間的發展,公司業務體量迎來了爆炸性增長,此時的架構模型,還能夠承擔這么大的流量嗎?
比如有這么一個需求:要用 Redis 保存 5000 萬
個鍵值對,每個鍵值對大約是 512B
,為了能快速部署并對外提供服務,我們采用云主機來運行 Redis 實例,那么,該如何選擇云主機的內存容量呢?
通過計算,這些鍵值對所占的內存空間大約是 25GB(5000 萬 *512B)。
想到的第一個方案就是:選擇一臺 32GB 內存的云主機來部署 Redis。因為 32GB 的內存能保存所有數據,而且還留有 7GB,可以保證系統的正常運行。
同時,還采用 RDB 對數據做持久化,以確保 Redis 實例故障后,還能從 RDB 恢復數據。
但是,在使用的過程中會發現,Redis 的響應有時會非常慢。通過 INFO命令
查看 Redis 的latest_fork_usec
指標值(表示最近一次 fork 的耗時),結果發現這個指標值特別高。
這跟 Redis 的持久化機制有關系。
在使用 RDB 進行持久化時,Redis 會 fork
子進程來完成,fork
操作的用時和 Redis 的數據量是正相關的,而 fork
在執行時會阻塞主線程。數據量越大,fork 操作造成的主線程阻塞的時間越長。
所以,在使用 RDB
對 25GB 的數據進行持久化時,數據量較大,后臺運行的子進程在 fork
創建時阻塞了主線程,于是就導致 Redis 響應變慢了。
顯然這個方案是不可行的,我們必須要尋找其他的方案。
為了保存大量數據,我們一般有兩種方法:「縱向擴展」和「橫向擴展」:
縱向擴展:升級單個 Redis 實例的資源配置,包括增加內存容量、增加磁盤容量、使用更高配置的 CPU;
橫向擴展:橫向增加當前 Redis 實例的個數。
首先,「縱向擴展」的好處是,實施起來簡單、直接。不過,這個方案也面臨兩個潛在的問題。
第一個問題是,當使用 RDB 對數據進行持久化時,如果數據量增加,需要的內存也會增加,主線程 fork
子進程時就可能會阻塞。
第二個問題:縱向擴展會受到硬件和成本的限制。 這很容易理解,畢竟,把內存從 32GB 擴展到 64GB 還算容易,但是,要想擴充到 1TB,就會面臨硬件容量和成本上的限制了。
與「縱向擴展」相比,「橫向擴展」是一個擴展性更好的方案。這是因為,要想保存更多的數據,采用這種方案的話,只用增加 Redis 的實例個數就行了,不用擔心單個實例的硬件和成本限制。
Redis 集群就是基于「橫向擴展」實現的 ,通過啟動多個 Redis 實例組成一個集群,然后按照一定的規則,把收到的數據劃分成多份,每一份用一個實例來保存。
Redis 集群是一種分布式數據庫方案,集群通過分片
(sharding
,也可以叫切片
)來進行數據共享,并提供復制和故障轉移功能。
回到我們剛剛的場景中,如果把 25GB 的數據平均分成 5 份(當然,也可以不做均分),使用 5 個實例來保存,每個實例只需要保存 5GB 數據。如下圖所示:
那么,在切片集群中,實例在為 5GB 數據生成 RDB 時,數據量就小了很多,fork
子進程一般不會給主線程帶來較長時間的阻塞。
采用多個實例保存數據切片后,我們既能保存 25GB 數據,又避免了 fork
子進程阻塞主線程而導致的響應突然變慢。
在實際應用 Redis 時,隨著業務規模的擴展,保存大量數據的情況通常是無法避免的。而 Redis 集群,就是一個非常好的解決方案。
下面我們開始研究如何搭建一個 Redis 集群?
一個 Redis 集群通常由多個節點組成,在剛開始的時候,每個節點都是相互獨立地,節點之間沒有任何關聯。要組建一個可以工作的集群,我們必須將各個獨立的節點連接起來,構成一個包含多節點的集群。
我們可以通過 CLUSTER MEET
命令,將各個節點連接起來:
CLUSTER MEET <ip> <port>
ip:待加入集群的節點 ip
port:待加入集群的節點 port
命令說明:通過向一個節點 A 發送 CLUSTER MEET
命令,可以讓接收命令的節點 A 將另一個節點 B 添加到節點 A 所在的集群中。
這么說有點抽象,下面看一個例子。
假設現在有三個獨立的節點 127.0.0.1:7001
、 127.0.0.1:7002
、 127.0.0.1:7003
。
我們首先使用客戶端連上節點 7001
:
$ redis-cli -c -p 7001
然后向節點 7001
發送命令,將節點 7002
添加到 7001
所在的集群里:
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7002
同樣的,我們向 7003
發送命令,也添加到 7001
和 7002
所在的集群。
127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7003
通過
CLUSTER NODES
命令可以查看集群中的節點信息。
現在集群中已經包含 7001
、 7002
和 7003
三個節點。不過,在使用單個實例的時候,數據存在哪兒,客戶端訪問哪兒,都是非常明確的。但是,切片集群不可避免地涉及到多個實例的分布式管理問題。
要想把切片集群用起來,我們就需要解決兩大問題:
數據切片后,在多個實例之間如何分布?
客戶端怎么確定想要訪問的數據在哪個實例上?
接下來,我們就一個個地解決。
在切片集群中,數據需要分布在不同實例上,那么,數據和實例之間如何對應呢?
這就和接下來要講的 Redis Cluster
方案有關了。不過,我們要先弄明白切片集群和 Redis Cluster
的聯系與區別。
在 Redis 3.0 之前,官方并沒有針對切片集群提供具體的方案。從 3.0 開始,官方提供了一個名為
Redis Cluster
的方案,用于實現切片集群。
實際上,切片集群是一種保存大量數據的通用機制,這個機制可以有不同的實現方案。 Redis Cluster
方案中就規定了數據和實例的對應規則。
具體來說, Redis Cluster
方案采用 哈希槽(Hash Slot),來處理數據和實例之間的映射關系。
在 Redis Cluster
方案中,一個切片集群共有 16384
個哈希槽(2^14),這些哈希槽類似于數據分區,每個鍵值對都會根據它的 key,被映射到一個哈希槽中。
在上面我們分析的,通過 CLUSTER MEET
命令將 7001
、7002
、7003
三個節點連接到同一個集群里面,但是這個集群目前是處于下線狀態的,因為集群中的三個節點沒有分配任何槽。
那么,這些哈希槽又是如何被映射到具體的 Redis 實例上的呢?
我們可以使用 CLUSTER MEET
命令手動建立實例間的連接,形成集群,再使用CLUSTER ADDSLOTS
命令,指定每個實例上的哈希槽個數。
CLUSTER ADDSLOTS <slot> [slot ...]
Redis5.0 提供
CLUSTER CREATE
命令創建集群,使用該命令,Redis 會自動把這些槽平均分布在集群實例上。
舉個例子,我們通過以下命令,給 7001
、7002
、7003
三個節點分別指派槽。
將槽 0 ~ 槽5000 指派給 給 7001
:
127.0.0.1:7001> CLUSTER ADDSLOTS 0 1 2 3 4 ... 5000
將槽 5001 ~ 槽10000 指派給 給 7002
:
127.0.0.1:7002> CLUSTER ADDSLOTS 5001 5002 5003 5004 ... 10000
將槽 10001~ 槽 16383 指派給 給 7003
:
127.0.0.1:7003> CLUSTER ADDSLOTS 10001 10002 10003 10004 ... 16383
當三個 CLUSTER ADDSLOTS
命令都執行完畢之后,數據庫中的 16384 個槽都已經被指派給了對應的節點,此時集群進入上線狀態。
通過哈希槽,切片集群就實現了數據到哈希槽、哈希槽再到實例的分配。
但是,即使實例有了哈希槽的映射信息,客戶端又是怎么知道要訪問的數據在哪個實例上呢?
一般來說,客戶端和集群實例建立連接后,實例就會把哈希槽的分配信息發給客戶端。但是,在集群剛剛創建的時候,每個實例只知道自己被分配了哪些哈希槽,是不知道其他實例擁有的哈希槽信息的。
那么,客戶端是如何可以在訪問任何一個實例時,就能獲得所有的哈希槽信息呢?
Redis 實例會把自己的哈希槽信息發給和它相連接的其它實例,來完成哈希槽分配信息的擴散。當實例之間相互連接后,每個實例就有所有哈希槽的映射關系了。
客戶端收到哈希槽信息后,會把哈希槽信息緩存在本地。當客戶端請求鍵值對時,會先計算鍵所對應的哈希槽,然后就可以給相應的實例發送請求了。
當客戶端向節點請求鍵值對時,接收命令的節點會計算出命令要處理的數據庫鍵屬于哪個槽,并檢查這個槽是否指派給了自己:
如果鍵所在的槽剛好指派給了當前節點,那么節點會直接執行這個命令;
如果沒有指派給當前節點,那么節點會向客戶端返回一個 MOVED
錯誤,然后重定向(redirect)到正確的節點,并再次發送之前待執行的命令。
節點通過以下算法來定義 key
屬于哪個槽:
crc16(key,keylen) & 0x3FFF;
crc16:用于計算 key 的 CRC-16 校驗和
0x3FFF:換算成 10 進制是 16383
& 0x3FFF:用于計算出一個介于 0~16383 之間的整數作為 key 的槽號。
通過
CLUSTER KEYSLOT <KEY>
命令可以查看 key 屬于哪個槽。
當節點計算出 key 所屬的 槽 i
之后,節點會判斷 槽 i
是否被指派了自己。那么如何判斷呢?
每個節點會維護一個 「slots數組」,節點通過檢查 slots[i]
,判斷 槽 i
是否由自己負責:
如果說 slots[i]
對應的節點是當前節點的話,那么說明 槽 i
由當前節點負責,節點可以執行客戶端發送的命令;
如果說 slots[i]
對應的不是當前節點,節點會根據 slots[i]
所指向的節點向客戶端返回 MOVED
錯誤,指引客戶端轉到正確的節點。
格式:
MOVED <slot> <ip>:<port>
slot:鍵所在的槽
ip:負責處理槽 slot 節點的 ip
port:負責處理槽 slot 節點的 port
比如:MOVED 10086 127.0.0.1:7002
,表示,客戶端請求的鍵值對所在的哈希槽 10086
,實際是在 127.0.0.1:7002
這個實例上。
通過返回的 MOVED
命令,就相當于把哈希槽所在的新實例的信息告訴給客戶端了。
這樣一來,客戶端就可以直接和 7002
連接,并發送操作請求了。
同時,客戶端還會更新本地緩存,將該槽與 Redis 實例對應關系更新正確。
集群模式的
redis-cli
客戶端在接收到MOVED
錯誤時,并不會打印出MOVED
錯誤,而是根據MOVED
錯誤自動進行節點轉向,并打印出轉向信息,所以我們是看不見節點返回的MOVED
錯誤的。而使用單機模式的redis-cli
客戶端可以打印MOVED
錯誤。
其實,Redis 告知客戶端重定向訪問新實例分兩種情況:MOVED
和 ASK
。下面我們分析下 ASK
重定向命令的使用方法。
在集群中,實例和哈希槽的對應關系并不是一成不變的,最常見的變化有兩個:
在集群中,實例有新增或刪除,Redis 需要重新分配哈希槽;
為了負載均衡,Redis 需要把哈希槽在所有實例上重新分布一遍。
重新分片可以在線進行,也就是說,重新分片的過程中,集群不需要下線。
舉個例子,上面提到,我們組成了 7001
、7002
、7003
三個節點的集群,我們可以向這個集群添加一個新節點127.0.0.1:7004
。
$ redis-cli -c -p 7001 127.0.0.1:7001> CLUSTER MEET 127.0.0.1 7004 OK
然后通過重新分片,將原本指派給節點 7003
的槽 15001 ~ 槽 16383 改為指派給 7004
。
在重新分片的期間,源節點向目標節點遷移槽的過程中,可能會出現這樣一種情況:如果某個槽的數據比較多,部分遷移到新實例,還有一部分沒有遷移咋辦?
在這種遷移部分完成的情況下,客戶端就會收到一條 ASK
報錯信息。
如果客戶端向目標節點發送一個與數據庫鍵有關的命令,并且這個命令要處理的鍵正好屬于被遷移的槽時:
源節點會先在自己的數據庫里查找指定的鍵,如果找到的話,直接執行命令;
相反,如果源節點沒有找到,那么這個鍵就有可能已經遷移到了目標節點,源節點就會向客戶端發送一個 ASK
錯誤,指引客戶端轉向目標節點,并再次發送之前要執行的命令。
看起來好像有點復雜,我們舉個例子來解釋一下。
如上圖所示,節點 7003
正在向 7004
遷移 槽 16383
,這個槽包含 hello
和 world
,其中鍵 hello
還留在節點 7003
,而 world
已經遷移到 7004
。
我們向節點 7003
發送關于 hello
的命令 這個命令會直接執行:
127.0.0.1:7003> GET "hello" "you get the key 'hello'"
如果我們向節點 7003
發送 world
那么客戶端就會被重定向到 7004
:
127.0.0.1:7003> GET "world" -> (error) ASK 16383 127.0.0.1:7004
客戶端在接收到 ASK
錯誤之后,先發送一個 ASKING
命令,然后在發送 GET "world"
命令。
ASKING
命令用于打開節點的ASKING
標識,打開之后才可以執行命令。
ASK
錯誤和 MOVED
錯誤都會導致客戶端重定向,它們的區別在于:
MOVED 錯誤代表槽的負責權已經從一個節點轉移到了另一個節點:在客戶端收到關于 槽 i
的 MOVED
錯誤之后,客戶端每次遇到關于 槽 i
的命令請求時,都可以直接將命令請求發送至 MOVED
錯誤指向的節點,因為該節點就是目前負責 槽 i
的節點。
而 ASK 只是兩個節點遷移槽的過程中的一種臨時措施:在客戶端收到關于 槽 i
的 ASK
錯誤之后,客戶端只會在接下來的一次命令請求中將關于 槽 i
的命令請求發送到 ASK
錯誤指向的節點,但是 ,如果客戶端再次請求 槽 i
中的數據,它還是會給原來負責 槽 i
的節點發送請求。
這也就是說,ASK 命令的作用只是讓客戶端能給新實例發送一次請求,而且也不會更新客戶端緩存的哈希槽分配信息。而不像 MOVED
命令那樣,會更改本地緩存,讓后續所有命令都發往新實例。
我們現在知道了 Redis 集群的實現原理。下面我們再來分析下,Redis 集群如何實現高可用的呢?
Redis 集群中的節點也是分為主節點和從節點。
主節點用于處理槽
從節點用于復制主節點,如果被復制的主節點下線,可以代替主節點繼續提供服務。
舉個例子,對于包含 7001
~ 7004
的四個主節點的集群,可以添加兩個節點:7005
、7006
。并將這兩個節點設置為 7001
的從節點。
設置從節點命令:
CLUSTER REPLICATE <node_id>
如圖:
如果此時,主節點 7001
下線,那么集群中剩余正常工作的主節點將在 7001
的兩個從節點中選出一個作為新的主節點。
例如,節點 7005
被選中,那么原來由節點 7001
負責處理的槽會交給節點 7005
處理。而節點 7006
會改為復制新主節點 7005
。如果后續 7001
重新上線,那么它將成為 7005
的從節點。如下圖所示:
集群中每個節點會定期向其他節點發送 PING
消息,來檢測對方是否在線。如果接收消息的一方沒有在規定時間內返回 PONG
消息,那么接收消息的一方就會被發送方標記為「疑似下線」。
集群中的各個節點會通過互相發消息的方式來交換各節點的狀態信息。
節點的三種狀態:
在線狀態
疑似下線狀態 PFAIL
已下線狀態 FAIL
一個節點認為某個節點失聯了并不代表所有的節點都認為它失聯了。在一個集群中,半數以上負責處理槽的主節點都認定了某個主節點下線了,集群才認為該節點需要進行主從切換。
Redis 集群節點采用 Gossip 協議來廣播自己的狀態以及自己對整個集群認知的改變。比如一個節點發現某個節點失聯了 (PFail),它會將這條信息向整個集群廣播,其它節點也就可以收到這點失聯信息。
我們都知道,哨兵機制可以通過監控、自動切換主庫、通知客戶端實現故障自動切換。那么 Redis Cluster
又是如何實現故障自動轉移呢?
當一個從節點發現自己正在復制的主節點進入了「已下線」狀態時,從節點將開始對下線主節點進行故障切換。
故障轉移的執行步驟:
在復制下線主節點的所有從節點里,選中一個從節點
被選中的從節點執行 SLAVEOF no one
命令,成為主節點
新的主節點會撤銷所有對已下線主節點的槽指派,將這些槽全部指派給自己
新的主節點向集群廣播一條 PONG
消息,讓集群中其他節點知道,該節點已經由從節點變為主節點,且已經接管了原主節點負責的槽
新的主節點開始接收自己負責處理槽有關的命令請求,故障轉移完成。
這個選主方法和哨兵的很相似,兩者都是基于 Raft算法
的領頭算法實現的。流程如下:
集群的配置紀元是一個自增計數器,初始值為0;
當集群里的某個節點開始一次故障轉移操作時,集群配置紀元加 1;
對于每個配置紀元,集群里每個負責處理槽的主節點都有一次投票的機會,第一個向主節點要求投票的從節點將獲得主節點的投票;
當從節點發現自己復制的主節點進入「已下線」狀態時,會向集群廣播一條消息,要求收到這條消息,并且具有投票權的主節點為自己投票;
如果一個主節點具有投票權,且尚未投票給其他從節點,那么該主節點會返回一條消息給要求投票的從節點,表示支持從節點成為新的主節點;
每個參與選舉的從節點會計算獲得了多少主節點的支持;
如果集群中有 N 個具有投票權的主節點,當一個從節點收到的支持票 大于等于 N/2 + 1
時,該從節點就會當選為新的主節點;
如果在一個配置紀元里沒有從節點收集到足夠多的票數,那么集群會進入一個新的配置紀元,并再次進行選主。
集群中的各個節點通過發送和接收消息來進行通信,我們把發送消息的節點稱為發送者,接收消息的稱為接收者。
節點發送的消息主要有五種:
MEET 消息
PING 消息
PONG 消息
FAIL 消息
PUBLISH 消息
集群中的各個節點通過 Gossip
協議交換不同節點的狀態信息, Gossip
是由 MEET
、PING
、PONG
三種消息組成。
發送者每次發送 MEET
、PING
、PONG
消息時,都會從自己已知的節點列表中隨機選出兩個節點(可以是主節點或者從節點)一并發送給接收者。
接收者收到 MEET
、PING
、PONG
消息時,根據自身是否認識這兩個節點來進行不同的處理:
如果被選中的節點不存在接收著已知的節點列表,說明是第一次接觸,接收者會根據選擇節點的 ip和端口號進行通信;
如果已經存在,說明之前已經完成了通信,然后會更新原有選中節點的信息。
關于“Redis高可用集群是什么”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。