什麼是Cluster?Redis中為什麼需要Cluster?

2021-09-24 22:00:38
什麼是Cluster?Redis中為什麼需要Cluster?本篇文章就來帶大家詳細瞭解一下Cluster 叢集,聊聊Cluster叢集能支援的資料量,希望對大家有所幫助!

本文將對叢集的節點、槽指派、命令執行、重新分片、轉向、故障轉移、訊息等各個方面進行深入拆解。【相關推薦:Redis視訊教學

Redis 叢集原理總覽

目的在於掌握什麼是 Cluster ?Cluster 分片原理,使用者端定位資料原理、故障切換,選主,什麼場景使用 Cluster,如何部署叢集 …... [toc]

為什麼需要 Cluster

65 哥:碼哥,自從用上了你說的哨兵叢集實現故障自動轉移後,我終於可以開心的跟女朋友麼麼噠也不怕 Redis 宕機深夜宕機了。

可是最近遇到一個糟心的問題,Redis 需要儲存 800 萬個鍵值對,佔用 20 GB 的記憶體。

我就使用了一臺 32G 的記憶體主機部署,但是 Redis 響應有時候非常慢,使用 INFO 命令檢視 latest_fork_usec 指標(最近一次 fork 耗時),發現特別高。

主要是 Redis RDB 持久化機制導致的,Redis 會 Fork 子程序完成 RDB 持久化操作,fork 執行的耗時與 Redis 資料量成正相關。

而 Fork 執行的時候會阻塞主執行緒,由於資料量過大導致阻塞主執行緒過長,所以出現了 Redis 響應慢的表象。

65 哥:隨著業務規模的拓展,資料量越來越大。主從架構升級單個範例硬體難以拓展,且儲存巨量資料量會導致響應慢問題,有什麼辦法可以解決麼?

儲存大量資料,除了使用大記憶體主機的方式,我們還可以使用切片叢集。俗話說「眾人拾材火焰高」,一臺機器無法儲存所有資料,那就多臺分擔。

使用 Redis Cluster 叢集,主要解決了巨量資料量儲存導致的各種慢問題,同時也便於橫向拓展。

兩種方案對應著 Redis 資料增多的兩種拓展方案:垂直擴充套件(scale up)、水平擴充套件(scale out)。

  1. 垂直拓展:升級單個 Redis 的硬體設定,比如增加記憶體容量、磁碟容量、使用更強大的 CPU。
  2. 水平拓展:橫向增加 Redis 範例個數,每個節點負責一部分資料。

比如需要一個記憶體 24 GB 磁碟 150 GB 的伺服器資源,有以下兩種方案:

水平拓展與垂直拓展

在面向百萬、千萬級別的使用者規模時,橫向擴充套件的 Redis 切片叢集會是一個非常好的選擇。

65 哥:那這兩種方案都有什麼優缺點呢?

  • 垂直拓展部署簡單,但是當資料量大並且使用 RDB 實現持久化,會造成阻塞導致響應慢。另外受限於硬體和成本,拓展記憶體的成本太大,比如拓展到 1T 記憶體。
  • 水平拓展便於拓展,同時不需要擔心單個範例的硬體和成本的限制。但是,切片叢集會涉及多個範例的分散式管理問題,需要解決如何將資料合理分佈到不同範例,同時還要讓使用者端能正確存取到範例上的資料

什麼是 Cluster 叢集

Redis 叢集是一種分散式資料庫方案,叢集通過分片(sharding)來進行資料管理(「分治思想」的一種實踐),並提供複製和故障轉移功能。

將資料劃分為 16384 的 slots,每個節點負責一部分槽位。槽位的資訊儲存於每個節點中。

它是去中心化的,如圖所示,該叢集有三個 Redis 節點組成,每個節點負責整個叢集的一部分資料,每個節點負責的資料多少可能不一樣。

Redis 叢集架構

三個節點相互連線組成一個對等的叢集,它們之間通過 Gossip協定相互互動叢集資訊,最後每個節點都儲存著其他節點的 slots 分配情況。

開篇寄語

技術不是萬能的,程式設計師也不是最厲害的,一定要搞清楚,不要覺得「老子天下第一」。一旦有了這個意識,可能會耽誤我們的成長。

技術是為了解決問題的,如果說一個技術不能解決問題,那這個技術就一文不值。

不要去炫技,沒有意義。

叢集安裝

點選 -> 《Redis 6.X Cluster 叢集搭建》檢視

一個 Redis 叢集通常由多個節點(node)組成,在剛開始的時候,每個節點都是相互獨立的,它們都處於一個只包含自己的叢集當中,要組建一個真正可工作的叢集,我們必須將各個獨立的節點連線起來,構成一個包含多個節點的叢集。

連線各個節點的工作可以通過 CLUSTER MEET 命令完成:CLUSTER MEET <ip> <port>

向一個節點 node 傳送 CLUSTER MEET 命令,可以讓 node 節點與 ip 和 port 所指定的節點進行握手(handshake),當握手成功時,node 節點就會將 ip 和 port 所指定的節點新增到 node 節點當前所在的叢集中。

CLUSTER MEET

就好像 node 節點說:「喂,ip = xx,port = xx 的老哥,要不要加入「碼哥位元組」技術群,加入叢集就找到了一條大神成長之路,關注「碼哥位元組」公眾號回覆「加群」,是兄弟就跟我一起來!」

關於 Redis Cluster 叢集搭建詳細步驟,請點選文末左下角「閱讀原文」或者點選 -> 《Redis 6.X Cluster 叢集搭建》檢視,官方關於 Redis Cluster 的詳情請看:redis.io/topics/clus…

Cluster 實現原理

65 哥:資料切片後,需要將資料分佈在不同範例上,資料和範例之間如何對應上呢?

Redis 3.0 開始,官方提供了 Redis Cluster 方案實現了切片叢集,該方案就實現了資料和範例的規則。Redis Cluster 方案採用雜湊槽(Hash Slot,接下來我會直接稱之為 Slot),來處理資料和範例之間的對映關係。

跟著「碼哥位元組」一起進入 Cluster 實現原理探索之旅…...

將資料分成多份存在不同範例上

叢集的整個資料庫被分為 16384 個槽(slot),資料庫中的每個鍵都屬於這 16384 個槽的其中一個,叢集中的每個節點可以處理 0 個或最多 16384 個槽。

Key 與雜湊槽對映過程可以分為兩大步驟:

  • 根據鍵值對的 key,使用 CRC16 演演算法,計算出一個 16 bit 的值;

  • 將 16 bit 的值對 16384 執行取模,得到 0 ~ 16383 的數表示 key 對應的雜湊槽。

Cluster 還允許使用者強制某個 key 掛在特定槽位上,通過在 key 字串裡面嵌入 tag 標記,這就可以強制 key 所掛在的槽位等於 tag 所在的槽位。

雜湊槽與 Redis 範例對映

65 哥:雜湊槽又是如何對映到 Redis 範例上呢?

部署叢集的樣例中通過 cluster create 建立,Redis 會自動將 16384 個 雜湊槽平均分佈在叢集範例上,比如 N 個節點,每個節點上的雜湊槽數 = 16384 / N 個。

除此之外,可以通過 CLUSTER MEET 命令將 7000、7001、7002 三個節點連在一個叢集,但是叢集目前依然處於下線狀態,因為三個範例都沒有處理任何雜湊槽。

可以使用 cluster addslots 命令,指定每個範例上的雜湊槽個數。

65 哥:為啥要手動制定呢?

能者多勞嘛,加入叢集中的 Redis 範例設定不一樣,如果承擔一樣的壓力,對於垃圾機器來說就太難了,讓牛逼的機器多支援一點。

三個範例的叢集,通過下面的指令為每個範例分配雜湊槽:範例 1負責 0 ~ 5460 雜湊槽,範例 2 負責 5461~10922 雜湊槽,範例 3 負責 10923 ~ 16383 雜湊槽。

redis-cli -h 172.16.19.1 –p 6379 cluster addslots 0,5460
redis-cli -h 172.16.19.2 –p 6379 cluster addslots 5461,10922
redis-cli -h 172.16.19.3 –p 6379 cluster addslots 10923,16383

鍵值對資料、雜湊槽、Redis 範例之間的對映關係如下:

資料、Slot與例項的對映

Redis 鍵值對的 key 「碼哥位元組」「牛逼」經過 CRC16 計算後再對雜湊槽總個數 16394 取模,模數結果分別對映到範例 1 與範例 2 上。

切記,當 16384 個槽都分配完全,Redis 叢集才能正常工作

複製與故障轉移

65 哥:Redis 叢集如何實現高可用呢?Master 與 Slave 還是讀寫分離麼?

Master 用於處理槽,Slave 節點則通過《Redis 主從架構資料同步》方式同步主節點資料。

當 Master 下線,Slave 代替主節點繼續處理請求。主從節點之間並沒有讀寫分離, Slave 只用作 Master 宕機的高可用備份。

Redis Cluster 可以為每個主節點設定若干個從節點,單主節點故障時,叢集會自動將其中某個從節點提升為主節點。

如果某個主節點沒有從節點,那麼當它發生故障時,叢集將完全處於不可用狀態

不過 Redis 也提供了一個引數cluster-require-full-coverage可以允許部分節點故障,其它節點還可以繼續提供對外存取。

比如 7000 主節點宕機,作為 slave 的 7003 成為 Master 節點繼續提供服務。當下線的節點 7000 重新上線,它將成為當前 70003 的從節點。

故障檢測

65 哥:在《Redis 高可用篇:Sentinel 哨兵叢集原理》我知道哨兵通過監控、自動切換主庫、通知使用者端實現故障自動切換,Cluster 又如何實現故障自動轉移呢?

一個節點認為某個節點失聯了並不代表所有的節點都認為它失聯了。只有當大多數負責處理 slot 節點都認定了某個節點下線了,叢集才認為該節點需要進行主從切換。

Redis 叢集節點採用 Gossip 協定來廣播自己的狀態以及自己對整個叢集認知的改變。比如一個節點發現某個節點失聯了 (PFail),它會將這條資訊向整個叢集廣播,其它節點也就可以收到這點失聯資訊。

關於 Gossip 協定可閱讀悟空哥的一篇文章:《病毒入侵,全靠分散式

如果一個節點收到了某個節點失聯的數量 (PFail Count) 已經達到了叢集的大多數,就可以標記該節點為確定下線狀態 (Fail),然後向整個叢集廣播,強迫其它節點也接收該節點已經下線的事實,並立即對該失聯節點進行主從切換。

故障轉移

當一個 Slave 發現自己的主節點進入已下線狀態後,從節點將開始對下線的主節點進行故障轉移。

  • 從下線的 Master 及節點的 Slave 節點列表選擇一個節點成為新主節點。

  • 新主節點會復原所有對已下線主節點的 slot 指派,並將這些 slots 指派給自己。

  • 新的主節點向叢集廣播一條 PONG 訊息,這條 PONG 訊息可以讓叢集中的其他節點立即知道這個節點已經由從節點變成了主節點,並且這個主節點已經接管了原本由已下線節點負責處理的槽。

  • 新的主節點開始接收處理槽有關的命令請求,故障轉移完成。

選主流程

65 哥:新的主節點如何選舉產生的?

  • 叢集的設定紀元 +1,是一個自曾計數器,初始值 0 ,每次執行故障轉移都會 +1。

  • 檢測到主節點下線的從節點向叢集廣播一條CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST訊息,要求所有收到這條訊息、並且具有投票權的主節點向這個從節點投票。

  • 這個主節點尚未投票給其他從節點,那麼主節點將向要求投票的從節點返回一條CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK訊息,表示這個主節點支援從節點成為新的主節點。

  • 參與選舉的從節點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK訊息,如果收集到的票 >= (N/2) + 1 支援,那麼這個從節點就被選舉為新主節點。

  • 如果在一個設定紀元裡面沒有從節點能收集到足夠多的支援票,那麼叢集進入一個新的設定紀元,並再次進行選舉,直到選出新的主節點為止。

跟哨兵類似,兩者都是基於 Raft 演演算法來實現的,流程如圖所示:

叢集Leader選舉

用表儲存鍵值對和範例的關聯關係可行麼

65 哥,我來考考你:「Redis Cluster 方案通過雜湊槽的方式把鍵值對分配到不同的範例上,這個過程需要對鍵值對的 key 做 CRC 計算並對 雜湊槽總數取模對映到範例上。如果用一個表直接把鍵值對和範例的對應關係記錄下來(例如鍵值對 1 在範例 2 上,鍵值對 2 在範例 1 上),這樣就不用計算 key 和雜湊槽的對應關係了,只用查表就行了,Redis 為什麼不這麼做呢?」

使用一個全域性表記錄的話,假如鍵值對和範例之間的關係改變(重新分片、範例增減),需要修改表。如果是單執行緒操作,所有操作都要序列,效能太慢。

多執行緒的話,就涉及到加鎖,另外,如果鍵值對資料量非常大,儲存鍵值對與範例關係的表資料所需要的儲存空間也會很大。

而雜湊槽計算,雖然也要記錄雜湊槽與範例時間的關係,但是雜湊槽的數量少得多,只有 16384 個,開銷很小。

使用者端如何定位資料所在範例

65 哥:使用者端又怎麼確定存取的資料到底分佈在哪個範例上呢?

Redis 範例會將自己的雜湊槽資訊通過 Gossip 協定傳送給叢集中其他的範例,實現了雜湊槽分配資訊的擴散。

這樣,叢集中的每個範例都有所有雜湊槽與範例之間的對映關係資訊。

在切片資料的時候是將 key 通過 CRC16 計算出一個值再對 16384 取模得到對應的 Slot,這個計算任務可以在使用者端上執行傳送請求的時候執行。

但是,定位到槽以後還需要進一步定位到該 Slot 所在 Redis 範例。

當用戶端連線任何一個範例,範例就將雜湊槽與範例的對映關係響應給使用者端,使用者端就會將雜湊槽與範例對映資訊快取在本地。

當用戶端請求時,會計算出鍵所對應的雜湊槽,在通過本地快取的雜湊槽範例對映資訊定位到資料所在範例上,再將請求傳送給對應的範例。

Redis 客戶端定位資料所在節點

重新分配雜湊槽

65 哥:雜湊槽與範例之間的對映關係由於新增範例或者負載均衡重新分配導致改變了咋辦?

叢集中的範例通過 Gossip 協定互相傳遞訊息獲取最新的雜湊槽分配資訊,但是,使用者端無法感知。

Redis Cluster 提供了重定向機制:使用者端將請求傳送到範例上,這個範例沒有相應的資料,該 Redis 範例會告訴使用者端將請求傳送到其他的範例上

65 哥:Redis 如何告知使用者端重定向存取新範例呢?

分為兩種情況:MOVED 錯誤、ASK 錯誤

MOVED 錯誤

MOVED 錯誤(負載均衡,資料已經遷移到其他範例上):當用戶端將一個鍵值對操作請求傳送給某個範例,而這個鍵所在的槽並非由自己負責的時候,該範例會返回一個 MOVED 錯誤指引轉向正在負責該槽的節點。

GET 公眾號:碼哥位元組
(error) MOVED 16330 172.17.18.2:6379

該響應表示使用者端請求的鍵值對所在的雜湊槽 16330 遷移到了 172.17.18.2 這個範例上,埠是 6379。這樣使用者端就與 172.17.18.2:6379 建立連線,並行送 GET 請求。

同時,使用者端還會更新本地快取,將該 slot 與 Redis 範例對應關係更新正確

MOVED 指令

ASK 錯誤

65 哥:如果某個 slot 的資料比較多,部分遷移到新範例,還有一部分沒有遷移咋辦?

如果請求的 key 在當前節點找到就直接執行命令,否則時候就需要 ASK 錯誤響應了,槽部分遷移未完成的情況下,如果需要存取的 key 所在 Slot 正在從從 範例 1 遷移到 範例 2,範例 1 會返回使用者端一條 ASK 報錯資訊:使用者端請求的 key 所在的雜湊槽正在遷移到範例 2 上,你先給範例 2 傳送一個 ASKING 命令,接著發傳送操作命令

GET 公眾號:碼哥位元組
(error) ASK 16330 172.17.18.2:6379

比如使用者端請求定位到 key = 「公眾號:碼哥位元組」的槽 16330 在範例 172.17.18.1 上,節點 1 如果找得到就直接執行命令,否則響應 ASK 錯誤資訊,並指引使用者端轉向正在遷移的目標節點 172.17.18.2。

ASK 錯誤

注意:ASK 錯誤指令並不會更新使用者端快取的雜湊槽分配資訊

所以使用者端再次請求 Slot 16330 的資料,還是會先給 172.17.18.1 範例傳送請求,只不過節點會響應 ASK 命令讓使用者端給新範例傳送一次請求。

MOVED指令則更新使用者端本地快取,讓後續指令都發往新範例。

叢集可以設定多大?

65 哥:有了 Redis Cluster,再也不怕巨量資料量了,我可以無限水平拓展麼?

答案是否定的,Redis 官方給的 Redis Cluster 的規模上線是 1000 個範例

65 哥:到底是什麼限制了叢集規模呢?

關鍵在於範例間的通訊開銷,Cluster 叢集中的每個範例都儲存所有雜湊槽與範例對應關係資訊(Slot 對映到節點的表),以及自身的狀態資訊。

在叢集之間每個範例通過 Gossip協定傳播節點的資料,Gossip 協定工作原理大概如下:

  1. 從叢集中隨機選擇一些範例按照一定的頻率傳送 PING 訊息傳送給挑選出來的範例,用於檢測範例狀態以及交換彼此的資訊。 PING 訊息中封裝了傳送者自身的狀態資訊、部分其他範例的狀態資訊、Slot 與範例對映表資訊。
  2. 範例接收到 PING 訊息後,響應 PONG 訊息,訊息包含的資訊跟 PING 訊息一樣。

叢集之間通過 Gossip協定可以在一段時間之後每個範例都能獲取其他所有範例的狀態資訊。

所以在有新節點加入,節點故障,Slot 對映變更都可以通過 PINGPONG 的訊息傳播完成叢集狀態在每個範例的傳播同步。

Gossip 訊息

傳送的訊息結構是 clusterMsgDataGossip結構體組成:

typedef struct {
    char nodename[CLUSTER_NAMELEN];  //40位元組
    uint32_t ping_sent; //4位元組
    uint32_t pong_received; //4位元組
    char ip[NET_IP_STR_LEN]; //46位元組
    uint16_t port;  //2位元組
    uint16_t cport;  //2位元組
    uint16_t flags;  //2位元組
    uint32_t notused1; //4位元組
} clusterMsgDataGossip;

所以每個範例傳送一個 Gossip訊息,就需要傳送 104 位元組。如果叢集是 1000 個範例,那麼每個範例傳送一個 PING 訊息則會佔用 大約 10KB。

除此之外,範例間在傳播 Slot 對映表的時候,每個訊息還包含了 一個長度為 16384 bit 的 Bitmap

每一位對應一個 Slot,如果值 = 1 則表示這個 Slot 屬於當前範例,這個 Bitmap 佔用 2KB,所以一個 PING 訊息大約 12KB。

PONGPING 訊息一樣,一發一回兩個訊息加起來就是 24 KB。叢集規模的增加,心跳訊息越來越多就會佔據叢集的網路通訊頻寬,降低了叢集吞吐量。

範例的通訊頻率

65 哥:碼哥,傳送 PING 訊息的頻率也會影響叢集頻寬吧?

Redis Cluster 的範例啟動後,預設會每秒從原生的範例列表中隨機選出 5 個範例,再從這 5 個範例中找出一個最久沒有收到 PING 訊息的範例,把 PING 訊息傳送給該範例。

65 哥:隨機選擇 5 個,但是無法保證選中的是整個叢集最久沒有收到 PING 通訊的範例,有的範例可能一直沒有收到訊息,導致他們維護的叢集資訊早就過期了,咋辦呢?

這個問題問的好,Redis Cluster 的範例每 100 ms 就會掃描本地範例列表,當發現有範例最近一次收到 PONG 訊息的時間 > cluster-node-timeout / 2。那麼就立刻給這個範例傳送 PING 訊息,更新這個節點的叢集狀態資訊。

當叢集規模變大,就會進一步導致範例間網路通訊延遲怎加。可能會引起更多的 PING 訊息頻繁傳送。

降低範例間的通訊開銷

  • 每個範例每秒傳送一條 PING訊息,降低這個頻率可能會導致叢集每個範例的狀態資訊無法及時傳播。
  • 每 100 ms 檢測範例 PONG訊息接收是否超過 cluster-node-timeout / 2,這個是 Redis 範例預設的週期性檢測任務頻率,我們不會輕易修改。

所以,只能修改 cluster-node-timeout的值:叢集中判斷範例是否故障的心跳時間,預設 15 S。

所以,為了避免過多的心跳訊息佔用叢集寬頻,將 cluster-node-timeout調成 20 秒或者 30 秒,這樣 PONG 訊息接收超時的情況就會緩解。

但是,也不能設定的太大。都則就會導致範例發生故障了,卻要等待 cluster-node-timeout時長才能檢測出這個故障,影響叢集正常服務、

總結

  • 哨兵叢集實現故障自動轉移,但是當資料量過大導致生成 RDB 時間過長。而 Fork 執行的時候會阻塞主執行緒,由於資料量過大導致阻塞主執行緒過長,所以出現了 Redis 響應慢的表象。
  • 使用 Redis Cluster 叢集,主要解決了巨量資料量儲存導致的各種慢問題,同時也便於橫向拓展。在面向百萬、千萬級別的使用者規模時,橫向擴充套件的 Redis 切片叢集會是一個非常好的選擇。
  • 叢集的整個資料庫被分為 16384 個槽(slot),資料庫中的每個鍵都屬於這 16384 個槽的其中一個,叢集中的每個節點可以處理 0 個或最多 16384 個槽。
  • Redis 叢集節點採用 Gossip 協定來廣播自己的狀態以及自己對整個叢集認知的改變。
  • 使用者端連線到叢集候任何一個範例後,範例會將雜湊槽與範例對映資訊傳送給使用者端,使用者端將資訊儲存,用於將 key 定位到對應的節點。
  • 叢集並不能無限增加,由於叢集通過 Gossip協定傳播叢集範例資訊,所以通訊頻率是限制叢集大小的主要原因,主要可以通過修改 cluster-node-timeout調整頻率。

更多程式設計相關知識,請存取:!!

以上就是什麼是Cluster?Redis中為什麼需要Cluster?的詳細內容,更多請關注TW511.COM其它相關文章!