首頁  >  文章  >  資料庫  >  什麼是Cluster? Redis中為什麼需要Cluster?

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

青灯夜游
青灯夜游轉載
2021-09-24 19:55:423169瀏覽

什麼是Cluster? Redis中為什麼需要Cluster?這篇文章就來帶大家詳細了解Cluster 集群,聊聊Cluster集群能支援的資料量,希望對大家有幫助!

什麼是Cluster? Redis中為什麼需要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 的伺服器資源,有以下兩種方案:

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

在面向百萬、千萬等級的使用者規模時,橫向擴展的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></port></ip>

向一個節點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 實例之間的對應關係如下:

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

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 演算法來實現的,流程如圖所示:

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

##用表保存鍵值對和實例的關聯關係可行麼

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?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除