Redis 高可用篇:Cluster 集群能支撐的數(shù)據(jù)有多大?
本文將對集群的節(jié)點(diǎn)、槽指派、命令執(zhí)行、重新分片、轉(zhuǎn)向、故障轉(zhuǎn)移、消息等各個(gè)方面進(jìn)行深入拆解。
目的在于掌握什么是 Cluster ?Cluster 分片原理,客戶端定位數(shù)據(jù)原理、故障切換,選主,什么場景使用 Cluster,如何部署集群 …...
將數(shù)據(jù)分成多份存在不同實(shí)例上
哈希槽與 Redis 實(shí)例映射
復(fù)制與故障轉(zhuǎn)移
故障檢測
故障轉(zhuǎn)移
選主流程
用表保存鍵值對和實(shí)例的關(guān)聯(lián)關(guān)系可行么
重新分配哈希槽
MOVED 錯(cuò)誤
ASK 錯(cuò)誤
Gossip 消息
實(shí)例的通信頻率
降低實(shí)例間的通信開銷
為什么需要 Cluster
“最近遇到一個(gè)糟心的問題,Redis 需要保存 800 萬個(gè)鍵值對,占用 20 GB 的內(nèi)存。
我就使用了一臺(tái) 32G 的內(nèi)存主機(jī)部署,但是 Redis 響應(yīng)有時(shí)候非常慢,使用 INFO 命令查看 latest_fork_usec 指標(biāo)(最近一次 fork 耗時(shí)),發(fā)現(xiàn)特別高。
”
主要是 Redis RDB 持久化機(jī)制導(dǎo)致的,Redis 會(huì) Fork 子進(jìn)程完成 RDB 持久化操作,fork 執(zhí)行的耗時(shí)與 Redis 數(shù)據(jù)量成正相關(guān)。
而 Fork 執(zhí)行的時(shí)候會(huì)阻塞主線程,由于數(shù)據(jù)量過大導(dǎo)致阻塞主線程過長,所以出現(xiàn)了 Redis 響應(yīng)慢的表象。
“65 哥:隨著業(yè)務(wù)規(guī)模的拓展,數(shù)據(jù)量越來越大。主從架構(gòu)升級單個(gè)實(shí)例硬件難以拓展,且保存大數(shù)據(jù)量會(huì)導(dǎo)致響應(yīng)慢問題,有什么辦法可以解決么?
”
保存大量數(shù)據(jù),除了使用大內(nèi)存主機(jī)的方式,我們還可以使用切片集群。俗話說「眾人拾材火焰高」,一臺(tái)機(jī)器無法保存所有數(shù)據(jù),那就多臺(tái)分擔(dān)。
使用 Redis Cluster 集群,主要解決了大數(shù)據(jù)量存儲(chǔ)導(dǎo)致的各種慢問題,同時(shí)也便于橫向拓展。
兩種方案對應(yīng)著 Redis 數(shù)據(jù)增多的兩種拓展方案:垂直擴(kuò)展(scale up)、水平擴(kuò)展(scale out)。
垂直拓展:升級單個(gè) Redis 的硬件配置,比如增加內(nèi)存容量、磁盤容量、使用更強(qiáng)大的 CPU。 水平拓展:橫向增加 Redis 實(shí)例個(gè)數(shù),每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分?jǐn)?shù)據(jù)。
比如需要一個(gè)內(nèi)存 24 GB 磁盤 150 GB 的服務(wù)器資源,有以下兩種方案:
在面向百萬、千萬級別的用戶規(guī)模時(shí),橫向擴(kuò)展的 Redis 切片集群會(huì)是一個(gè)非常好的選擇。
“65 哥:那這兩種方案都有什么優(yōu)缺點(diǎn)呢?
”
垂直拓展部署簡單,但是當(dāng)數(shù)據(jù)量大并且使用 RDB 實(shí)現(xiàn)持久化,會(huì)造成阻塞導(dǎo)致響應(yīng)慢。另外受限于硬件和成本,拓展內(nèi)存的成本太大,比如拓展到 1T 內(nèi)存。 水平拓展便于拓展,同時(shí)不需要擔(dān)心單個(gè)實(shí)例的硬件和成本的限制。但是,切片集群會(huì)涉及多個(gè)實(shí)例的分布式管理問題,需要解決如何將數(shù)據(jù)合理分布到不同實(shí)例,同時(shí)還要讓客戶端能正確訪問到實(shí)例上的數(shù)據(jù)。
什么是 Cluster 集群
Redis 集群是一種分布式數(shù)據(jù)庫方案,集群通過分片(sharding)來進(jìn)行數(shù)據(jù)管理(「分治思想」的一種實(shí)踐),并提供復(fù)制和故障轉(zhuǎn)移功能。
將數(shù)據(jù)劃分為 16384 的 slots,每個(gè)節(jié)點(diǎn)負(fù)責(zé)一部分槽位。槽位的信息存儲(chǔ)于每個(gè)節(jié)點(diǎn)中。
它是去中心化的,如圖所示,該集群有三個(gè) Redis 節(jié)點(diǎn)組成,每個(gè)節(jié)點(diǎn)負(fù)責(zé)整個(gè)集群的一部分?jǐn)?shù)據(jù),每個(gè)節(jié)點(diǎn)負(fù)責(zé)的數(shù)據(jù)多少可能不一樣。
三個(gè)節(jié)點(diǎn)相互連接組成一個(gè)對等的集群,它們之間通過 Gossip協(xié)議相互交互集群信息,最后每個(gè)節(jié)點(diǎn)都保存著其他節(jié)點(diǎn)的 slots 分配情況。
開篇寄語
“技術(shù)不是萬能的,程序員也不是最厲害的,一定要搞清楚,不要覺得「老子天下第一」。一旦有了這個(gè)意識(shí),可能會(huì)耽誤我們的成長。
技術(shù)是為了解決問題的,如果說一個(gè)技術(shù)不能解決問題,那這個(gè)技術(shù)就一文不值。
不要去炫技,沒有意義。”
一個(gè) Redis 集群通常由多個(gè)節(jié)點(diǎn)(node)組成,在剛開始的時(shí)候,每個(gè)節(jié)點(diǎn)都是相互獨(dú)立的,它們都處于一個(gè)只包含自己的集群當(dāng)中,要組建一個(gè)真正可工作的集群,我們必須將各個(gè)獨(dú)立的節(jié)點(diǎn)連接起來,構(gòu)成一個(gè)包含多個(gè)節(jié)點(diǎn)的集群。
連接各個(gè)節(jié)點(diǎn)的工作可以通過 CLUSTER MEET 命令完成:CLUSTER MEET <ip> <port> 。
向一個(gè)節(jié)點(diǎn) node 發(fā)送 CLUSTER MEET 命令,可以讓 node 節(jié)點(diǎn)與 ip 和 port 所指定的節(jié)點(diǎn)進(jìn)行握手(handshake),當(dāng)握手成功時(shí),node 節(jié)點(diǎn)就會(huì)將 ip 和 port 所指定的節(jié)點(diǎn)添加到 node 節(jié)點(diǎn)當(dāng)前所在的集群中。
Cluster 實(shí)現(xiàn)原理
“65 哥:數(shù)據(jù)切片后,需要將數(shù)據(jù)分布在不同實(shí)例上,數(shù)據(jù)和實(shí)例之間如何對應(yīng)上呢?
”
Redis 3.0 開始,官方提供了 Redis Cluster 方案實(shí)現(xiàn)了切片集群,該方案就實(shí)現(xiàn)了數(shù)據(jù)和實(shí)例的規(guī)則。Redis Cluster 方案采用哈希槽(Hash Slot,接下來我會(huì)直接稱之為 Slot),來處理數(shù)據(jù)和實(shí)例之間的映射關(guān)系。
跟著「碼哥字節(jié)」一起進(jìn)入 Cluster 實(shí)現(xiàn)原理探索之旅…...
將數(shù)據(jù)分成多份存在不同實(shí)例上
集群的整個(gè)數(shù)據(jù)庫被分為 16384 個(gè)槽(slot),數(shù)據(jù)庫中的每個(gè)鍵都屬于這 16384 個(gè)槽的其中一個(gè),集群中的每個(gè)節(jié)點(diǎn)可以處理 0 個(gè)或最多 16384 個(gè)槽。
Key 與哈希槽映射過程可以分為兩大步驟:
根據(jù)鍵值對的 key,使用 CRC16 算法,計(jì)算出一個(gè) 16 bit 的值; 將 16 bit 的值對 16384 執(zhí)行取模,得到 0 ~ 16383 的數(shù)表示 key 對應(yīng)的哈希槽。
Cluster 還允許用戶強(qiáng)制某個(gè) key 掛在特定槽位上,通過在 key 字符串里面嵌入 tag 標(biāo)記,這就可以強(qiáng)制 key 所掛在的槽位等于 tag 所在的槽位。
哈希槽與 Redis 實(shí)例映射
“65 哥:哈希槽又是如何映射到 Redis 實(shí)例上呢?
”
在 部署集群的樣例中通過 cluster create 創(chuàng)建,Redis 會(huì)自動(dòng)將 16384 個(gè) 哈希槽平均分布在集群實(shí)例上,比如 N 個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)上的哈希槽數(shù) = 16384 / N 個(gè)。
除此之外,可以通過 CLUSTER MEET 命令將 7000、7001、7002 三個(gè)節(jié)點(diǎn)連在一個(gè)集群,但是集群目前依然處于下線狀態(tài),因?yàn)槿齻€(gè)實(shí)例都沒有處理任何哈希槽。
可以使用 cluster addslots 命令,指定每個(gè)實(shí)例上的哈希槽個(gè)數(shù)。
“65 哥:為啥要手動(dòng)制定呢?
”
能者多勞嘛,加入集群中的 Redis 實(shí)例配置不一樣,如果承擔(dān)一樣的壓力,對于垃圾機(jī)器來說就太難了,讓牛逼的機(jī)器多支持一點(diǎn)。
三個(gè)實(shí)例的集群,通過下面的指令為每個(gè)實(shí)例分配哈希槽:實(shí)例 1負(fù)責(zé) 0 ~ 5460 哈希槽,實(shí)例 2 負(fù)責(zé) 5461~10922 哈希槽,實(shí)例 3 負(fù)責(zé) 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
鍵值對數(shù)據(jù)、哈希槽、Redis 實(shí)例之間的映射關(guān)系如下:
Redis 鍵值對的 key 「碼哥字節(jié)」「牛逼」經(jīng)過 CRC16 計(jì)算后再對哈希槽總個(gè)數(shù) 16394 取模,模數(shù)結(jié)果分別映射到實(shí)例 1 與實(shí)例 2 上。
切記,當(dāng) 16384 個(gè)槽都分配完全,Redis 集群才能正常工作。
復(fù)制與故障轉(zhuǎn)移
“65 哥:Redis 集群如何實(shí)現(xiàn)高可用呢?Master 與 Slave 還是讀寫分離么?
”
Master 用于處理槽,Slave 節(jié)點(diǎn)則通過《Redis 主從架構(gòu)數(shù)據(jù)同步》方式同步主節(jié)點(diǎn)數(shù)據(jù)。
當(dāng) Master 下線,Slave 代替主節(jié)點(diǎn)繼續(xù)處理請求。主從節(jié)點(diǎn)之間并沒有讀寫分離, Slave 只用作 Master 宕機(jī)的高可用備份。
Redis Cluster 可以為每個(gè)主節(jié)點(diǎn)設(shè)置若干個(gè)從節(jié)點(diǎn),單主節(jié)點(diǎn)故障時(shí),集群會(huì)自動(dòng)將其中某個(gè)從節(jié)點(diǎn)提升為主節(jié)點(diǎn)。
如果某個(gè)主節(jié)點(diǎn)沒有從節(jié)點(diǎn),那么當(dāng)它發(fā)生故障時(shí),集群將完全處于不可用狀態(tài)。
不過 Redis 也提供了一個(gè)參數(shù)cluster-require-full-coverage可以允許部分節(jié)點(diǎn)故障,其它節(jié)點(diǎn)還可以繼續(xù)提供對外訪問。
比如 7000 主節(jié)點(diǎn)宕機(jī),作為 slave 的 7003 成為 Master 節(jié)點(diǎn)繼續(xù)提供服務(wù)。當(dāng)下線的節(jié)點(diǎn) 7000 重新上線,它將成為當(dāng)前 70003 的從節(jié)點(diǎn)。
故障檢測
”
一個(gè)節(jié)點(diǎn)認(rèn)為某個(gè)節(jié)點(diǎn)失聯(lián)了并不代表所有的節(jié)點(diǎn)都認(rèn)為它失聯(lián)了。只有當(dāng)大多數(shù)負(fù)責(zé)處理 slot 節(jié)點(diǎn)都認(rèn)定了某個(gè)節(jié)點(diǎn)下線了,集群才認(rèn)為該節(jié)點(diǎn)需要進(jìn)行主從切換。
Redis 集群節(jié)點(diǎn)采用 Gossip 協(xié)議來廣播自己的狀態(tài)以及自己對整個(gè)集群認(rèn)知的改變。比如一個(gè)節(jié)點(diǎn)發(fā)現(xiàn)某個(gè)節(jié)點(diǎn)失聯(lián)了 (PFail),它會(huì)將這條信息向整個(gè)集群廣播,其它節(jié)點(diǎn)也就可以收到這點(diǎn)失聯(lián)信息。
關(guān)于 Gossip 協(xié)議可閱讀悟空哥的一篇文章:《病毒入侵,全靠分布式》
如果一個(gè)節(jié)點(diǎn)收到了某個(gè)節(jié)點(diǎn)失聯(lián)的數(shù)量 (PFail Count) 已經(jīng)達(dá)到了集群的大多數(shù),就可以標(biāo)記該節(jié)點(diǎn)為確定下線狀態(tài) (Fail),然后向整個(gè)集群廣播,強(qiáng)迫其它節(jié)點(diǎn)也接收該節(jié)點(diǎn)已經(jīng)下線的事實(shí),并立即對該失聯(lián)節(jié)點(diǎn)進(jìn)行主從切換。
故障轉(zhuǎn)移
當(dāng)一個(gè) Slave 發(fā)現(xiàn)自己的主節(jié)點(diǎn)進(jìn)入已下線狀態(tài)后,從節(jié)點(diǎn)將開始對下線的主節(jié)點(diǎn)進(jìn)行故障轉(zhuǎn)移。
從下線的 Master 及節(jié)點(diǎn)的 Slave 節(jié)點(diǎn)列表選擇一個(gè)節(jié)點(diǎn)成為新主節(jié)點(diǎn)。 新主節(jié)點(diǎn)會(huì)撤銷所有對已下線主節(jié)點(diǎn)的 slot 指派,并將這些 slots 指派給自己。 新的主節(jié)點(diǎn)向集群廣播一條 PONG 消息,這條 PONG 消息可以讓集群中的其他節(jié)點(diǎn)立即知道這個(gè)節(jié)點(diǎn)已經(jīng)由從節(jié)點(diǎn)變成了主節(jié)點(diǎn),并且這個(gè)主節(jié)點(diǎn)已經(jīng)接管了原本由已下線節(jié)點(diǎn)負(fù)責(zé)處理的槽。 新的主節(jié)點(diǎn)開始接收處理槽有關(guān)的命令請求,故障轉(zhuǎn)移完成。
選主流程
“65 哥:新的主節(jié)點(diǎn)如何選舉產(chǎn)生的?
”
集群的配置紀(jì)元 +1,是一個(gè)自曾計(jì)數(shù)器,初始值 0 ,每次執(zhí)行故障轉(zhuǎn)移都會(huì) +1。 檢測到主節(jié)點(diǎn)下線的從節(jié)點(diǎn)向集群廣播一條 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息、并且具有投票權(quán)的主節(jié)點(diǎn)向這個(gè)從節(jié)點(diǎn)投票。這個(gè)主節(jié)點(diǎn)尚未投票給其他從節(jié)點(diǎn),那么主節(jié)點(diǎn)將向要求投票的從節(jié)點(diǎn)返回一條 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個(gè)主節(jié)點(diǎn)支持從節(jié)點(diǎn)成為新的主節(jié)點(diǎn)。參與選舉的從節(jié)點(diǎn)都會(huì)接收 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,如果收集到的票 >= (N/2) + 1 支持,那么這個(gè)從節(jié)點(diǎn)就被選舉為新主節(jié)點(diǎn)。如果在一個(gè)配置紀(jì)元里面沒有從節(jié)點(diǎn)能收集到足夠多的支持票,那么集群進(jìn)入一個(gè)新的配置紀(jì)元,并再次進(jìn)行選舉,直到選出新的主節(jié)點(diǎn)為止。
跟哨兵類似,兩者都是基于 Raft 算法來實(shí)現(xiàn)的,流程如圖所示:
用表保存鍵值對和實(shí)例的關(guān)聯(lián)關(guān)系可行么
“65 哥,我來考考你:“Redis Cluster 方案通過哈希槽的方式把鍵值對分配到不同的實(shí)例上,這個(gè)過程需要對鍵值對的 key 做 CRC 計(jì)算并對 哈希槽總數(shù)取模映射到實(shí)例上。如果用一個(gè)表直接把鍵值對和實(shí)例的對應(yīng)關(guān)系記錄下來(例如鍵值對 1 在實(shí)例 2 上,鍵值對 2 在實(shí)例 1 上),這樣就不用計(jì)算 key 和哈希槽的對應(yīng)關(guān)系了,只用查表就行了,Redis 為什么不這么做呢?”
”
使用一個(gè)全局表記錄的話,假如鍵值對和實(shí)例之間的關(guān)系改變(重新分片、實(shí)例增減),需要修改表。如果是單線程操作,所有操作都要串行,性能太慢。
多線程的話,就涉及到加鎖,另外,如果鍵值對數(shù)據(jù)量非常大,保存鍵值對與實(shí)例關(guān)系的表數(shù)據(jù)所需要的存儲(chǔ)空間也會(huì)很大。
而哈希槽計(jì)算,雖然也要記錄哈希槽與實(shí)例時(shí)間的關(guān)系,但是哈希槽的數(shù)量少得多,只有 16384 個(gè),開銷很小。
客戶端如何定位數(shù)據(jù)所在實(shí)例
“65 哥:客戶端又怎么確定訪問的數(shù)據(jù)到底分布在哪個(gè)實(shí)例上呢?
”
Redis 實(shí)例會(huì)將自己的哈希槽信息通過 Gossip 協(xié)議發(fā)送給集群中其他的實(shí)例,實(shí)現(xiàn)了哈希槽分配信息的擴(kuò)散。
這樣,集群中的每個(gè)實(shí)例都有所有哈希槽與實(shí)例之間的映射關(guān)系信息。
在切片數(shù)據(jù)的時(shí)候是將 key 通過 CRC16 計(jì)算出一個(gè)值再對 16384 取模得到對應(yīng)的 Slot,這個(gè)計(jì)算任務(wù)可以在客戶端上執(zhí)行發(fā)送請求的時(shí)候執(zhí)行。
但是,定位到槽以后還需要進(jìn)一步定位到該 Slot 所在 Redis 實(shí)例。
當(dāng)客戶端連接任何一個(gè)實(shí)例,實(shí)例就將哈希槽與實(shí)例的映射關(guān)系響應(yīng)給客戶端,客戶端就會(huì)將哈希槽與實(shí)例映射信息緩存在本地。
當(dāng)客戶端請求時(shí),會(huì)計(jì)算出鍵所對應(yīng)的哈希槽,在通過本地緩存的哈希槽實(shí)例映射信息定位到數(shù)據(jù)所在實(shí)例上,再將請求發(fā)送給對應(yīng)的實(shí)例。
重新分配哈希槽
“65 哥:哈希槽與實(shí)例之間的映射關(guān)系由于新增實(shí)例或者負(fù)載均衡重新分配導(dǎo)致改變了咋辦?
”
集群中的實(shí)例通過 Gossip 協(xié)議互相傳遞消息獲取最新的哈希槽分配信息,但是,客戶端無法感知。
Redis Cluster 提供了重定向機(jī)制:客戶端將請求發(fā)送到實(shí)例上,這個(gè)實(shí)例沒有相應(yīng)的數(shù)據(jù),該 Redis 實(shí)例會(huì)告訴客戶端將請求發(fā)送到其他的實(shí)例上。
“65 哥:Redis 如何告知客戶端重定向訪問新實(shí)例呢?
”
分為兩種情況:MOVED 錯(cuò)誤、ASK 錯(cuò)誤。
MOVED 錯(cuò)誤
MOVED 錯(cuò)誤(負(fù)載均衡,數(shù)據(jù)已經(jīng)遷移到其他實(shí)例上):當(dāng)客戶端將一個(gè)鍵值對操作請求發(fā)送給某個(gè)實(shí)例,而這個(gè)鍵所在的槽并非由自己負(fù)責(zé)的時(shí)候,該實(shí)例會(huì)返回一個(gè) MOVED 錯(cuò)誤指引轉(zhuǎn)向正在負(fù)責(zé)該槽的節(jié)點(diǎn)。
GET 公眾號(hào):碼哥字節(jié)
(error) MOVED 16330 172.17.18.2:6379
該響應(yīng)表示客戶端請求的鍵值對所在的哈希槽 16330 遷移到了 172.17.18.2 這個(gè)實(shí)例上,端口是 6379。這樣客戶端就與 172.17.18.2:6379 建立連接,并發(fā)送 GET 請求。
同時(shí),客戶端還會(huì)更新本地緩存,將該 slot 與 Redis 實(shí)例對應(yīng)關(guān)系更新正確。
ASK 錯(cuò)誤
“65 哥:如果某個(gè) slot 的數(shù)據(jù)比較多,部分遷移到新實(shí)例,還有一部分沒有遷移咋辦?
”
如果請求的 key 在當(dāng)前節(jié)點(diǎn)找到就直接執(zhí)行命令,否則時(shí)候就需要 ASK 錯(cuò)誤響應(yīng)了,槽部分遷移未完成的情況下,如果需要訪問的 key 所在 Slot 正在從從 實(shí)例 1 遷移到 實(shí)例 2,實(shí)例 1 會(huì)返回客戶端一條 ASK 報(bào)錯(cuò)信息:客戶端請求的 key 所在的哈希槽正在遷移到實(shí)例 2 上,你先給實(shí)例 2 發(fā)送一個(gè) ASKING 命令,接著發(fā)發(fā)送操作命令。
GET 公眾號(hào):碼哥字節(jié)
(error) ASK 16330 172.17.18.2:6379
比如客戶端請求定位到 key = 「xxx」的槽 16330 在實(shí)例 172.17.18.1 上,節(jié)點(diǎn) 1 如果找得到就直接執(zhí)行命令,否則響應(yīng) ASK 錯(cuò)誤信息,并指引客戶端轉(zhuǎn)向正在遷移的目標(biāo)節(jié)點(diǎn) 172.17.18.2。
注意:ASK 錯(cuò)誤指令并不會(huì)更新客戶端緩存的哈希槽分配信息。
所以客戶端再次請求 Slot 16330 的數(shù)據(jù),還是會(huì)先給 172.17.18.1 實(shí)例發(fā)送請求,只不過節(jié)點(diǎn)會(huì)響應(yīng) ASK 命令讓客戶端給新實(shí)例發(fā)送一次請求。
MOVED指令則更新客戶端本地緩存,讓后續(xù)指令都發(fā)往新實(shí)例。
集群可以設(shè)置多大?
“65 哥:有了 Redis Cluster,再也不怕大數(shù)據(jù)量了,我可以無限水平拓展么?
”
答案是否定的,Redis 官方給的 Redis Cluster 的規(guī)模上線是 1000 個(gè)實(shí)例。
“65 哥:到底是什么限制了集群規(guī)模呢?
”
關(guān)鍵在于實(shí)例間的通信開銷,Cluster 集群中的每個(gè)實(shí)例都保存所有哈希槽與實(shí)例對應(yīng)關(guān)系信息(Slot 映射到節(jié)點(diǎn)的表),以及自身的狀態(tài)信息。
在集群之間每個(gè)實(shí)例通過 Gossip協(xié)議傳播節(jié)點(diǎn)的數(shù)據(jù),Gossip 協(xié)議工作原理大概如下:
從集群中隨機(jī)選擇一些實(shí)例按照一定的頻率發(fā)送 PING消息發(fā)送給挑選出來的實(shí)例,用于檢測實(shí)例狀態(tài)以及交換彼此的信息。PING消息中封裝了發(fā)送者自身的狀態(tài)信息、部分其他實(shí)例的狀態(tài)信息、Slot 與實(shí)例映射表信息。實(shí)例接收到 PING消息后,響應(yīng)PONG消息,消息包含的信息跟PING消息一樣。
集群之間通過 Gossip協(xié)議可以在一段時(shí)間之后每個(gè)實(shí)例都能獲取其他所有實(shí)例的狀態(tài)信息。
所以在有新節(jié)點(diǎn)加入,節(jié)點(diǎn)故障,Slot 映射變更都可以通過 PING,PONG 的消息傳播完成集群狀態(tài)在每個(gè)實(shí)例的傳播同步。
Gossip 消息
發(fā)送的消息結(jié)構(gòu)是 clusterMsgDataGossip結(jié)構(gòu)體組成:
typedef struct {
char nodename[CLUSTER_NAMELEN]; //40字節(jié)
uint32_t ping_sent; //4字節(jié)
uint32_t pong_received; //4字節(jié)
char ip[NET_IP_STR_LEN]; //46字節(jié)
uint16_t port; //2字節(jié)
uint16_t cport; //2字節(jié)
uint16_t flags; //2字節(jié)
uint32_t notused1; //4字節(jié)
} clusterMsgDataGossip;
所以每個(gè)實(shí)例發(fā)送一個(gè) Gossip消息,就需要發(fā)送 104 字節(jié)。如果集群是 1000 個(gè)實(shí)例,那么每個(gè)實(shí)例發(fā)送一個(gè) PING 消息則會(huì)占用 大約 10KB。
除此之外,實(shí)例間在傳播 Slot 映射表的時(shí)候,每個(gè)消息還包含了 一個(gè)長度為 16384 bit 的 Bitmap。
每一位對應(yīng)一個(gè) Slot,如果值 = 1 則表示這個(gè) Slot 屬于當(dāng)前實(shí)例,這個(gè) Bitmap 占用 2KB,所以一個(gè) PING 消息大約 12KB。
PONG與PING 消息一樣,一發(fā)一回兩個(gè)消息加起來就是 24 KB。集群規(guī)模的增加,心跳消息越來越多就會(huì)占據(jù)集群的網(wǎng)絡(luò)通信帶寬,降低了集群吞吐量。
實(shí)例的通信頻率
“65 哥:碼哥,發(fā)送 PING 消息的頻率也會(huì)影響集群帶寬吧?
”
Redis Cluster 的實(shí)例啟動(dòng)后,默認(rèn)會(huì)每秒從本地的實(shí)例列表中隨機(jī)選出 5 個(gè)實(shí)例,再從這 5 個(gè)實(shí)例中找出一個(gè)最久沒有收到 PING 消息的實(shí)例,把 PING 消息發(fā)送給該實(shí)例。
“65 哥:隨機(jī)選擇 5 個(gè),但是無法保證選中的是整個(gè)集群最久沒有收到 PING 通信的實(shí)例,有的實(shí)例可能一直沒有收到消息,導(dǎo)致他們維護(hù)的集群信息早就過期了,咋辦呢?
”
這個(gè)問題問的好,Redis Cluster 的實(shí)例每 100 ms 就會(huì)掃描本地實(shí)例列表,當(dāng)發(fā)現(xiàn)有實(shí)例最近一次收到 PONG 消息的時(shí)間 > cluster-node-timeout / 2。那么就立刻給這個(gè)實(shí)例發(fā)送 PING 消息,更新這個(gè)節(jié)點(diǎn)的集群狀態(tài)信息。
當(dāng)集群規(guī)模變大,就會(huì)進(jìn)一步導(dǎo)致實(shí)例間網(wǎng)絡(luò)通信延遲怎加。可能會(huì)引起更多的 PING 消息頻繁發(fā)送。
降低實(shí)例間的通信開銷
每個(gè)實(shí)例每秒發(fā)送一條 PING消息,降低這個(gè)頻率可能會(huì)導(dǎo)致集群每個(gè)實(shí)例的狀態(tài)信息無法及時(shí)傳播。每 100 ms 檢測實(shí)例 PONG消息接收是否超過cluster-node-timeout / 2,這個(gè)是 Redis 實(shí)例默認(rèn)的周期性檢測任務(wù)頻率,我們不會(huì)輕易修改。
所以,只能修改 cluster-node-timeout的值:集群中判斷實(shí)例是否故障的心跳時(shí)間,默認(rèn) 15 S。
所以,為了避免過多的心跳消息占用集群寬帶,將 cluster-node-timeout調(diào)成 20 秒或者 30 秒,這樣 PONG 消息接收超時(shí)的情況就會(huì)緩解。
但是,也不能設(shè)置的太大。都則就會(huì)導(dǎo)致實(shí)例發(fā)生故障了,卻要等待 cluster-node-timeout時(shí)長才能檢測出這個(gè)故障,影響集群正常服務(wù)、
推薦閱讀:
論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?
企業(yè)10大管理流程圖,數(shù)字化轉(zhuǎn)型從業(yè)者必備!
世界的真實(shí)格局分析,地球人類社會(huì)底層運(yùn)行原理
不是你需要中臺(tái),而是一名合格的架構(gòu)師(附各大廠中臺(tái)建設(shè)PPT)









