欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Redis中切片集群詳解

 更新時(shí)間:2025年01月15日 11:07:45   作者:Recently?祝祝  
切片集群Redis中,數(shù)據(jù)增多了,是該加內(nèi)存還是加實(shí)例?采用云主機(jī)來運(yùn)行Redis實(shí)例,那么,該如何選擇云主機(jī)的內(nèi)存容量呢?用Redis保存5000萬個(gè)鍵值對,每個(gè)鍵值對大約是512B方案一:大內(nèi)存云主機(jī):選擇一臺32GB內(nèi)存的云主機(jī)來部署Redis

一.切片集群

Redis中,數(shù)據(jù)增多了,是該加內(nèi)存還是加實(shí)例?

采用云主機(jī)來運(yùn)行 Redis 實(shí)例,那么,該如何選擇云主機(jī)的內(nèi)存容量呢?

用 Redis 保存 5000 萬個(gè)鍵值對,每個(gè)鍵值對大約是 512B

方案一:大內(nèi)存云主機(jī):選擇一臺 32GB 內(nèi)存的云主機(jī)來部署 Redis。因?yàn)?32GB 的內(nèi)存能保存所有數(shù)據(jù),而且還留有 7GB,可以保證系統(tǒng)的正常運(yùn)行。同時(shí),我還采用 RDB 對數(shù)據(jù)做持久化,以確保 Redis 實(shí)例故障后,還能從 RDB 恢復(fù)數(shù)據(jù)。

結(jié)果:Redis 的響應(yīng)有時(shí)會非常慢,使用 INFO 命令查看 Redis 的 latest_fork_usec 指標(biāo)值(表示最近一次 fork 的耗時(shí)),結(jié)果顯示這個(gè)指標(biāo)值特別高,快到秒級別了。這跟 Redis 的持久化機(jī)制有關(guān)系。在使用 RDB 進(jìn)行持久化時(shí),Redis 會 fork 子進(jìn)程來完成,fork 操作的用時(shí)和 Redis 的數(shù)據(jù)量是正相關(guān)的,而 fork 在執(zhí)行時(shí)會阻塞主線程。數(shù)據(jù)量越大,fork 操作造成的主線程阻塞的時(shí)間越長。所以,在使用 RDB 對 25GB 的數(shù)據(jù)進(jìn)行持久化時(shí),數(shù)據(jù)量較大,后臺運(yùn)行的子進(jìn)程在 fork 創(chuàng)建時(shí)阻塞了主線程,于是就導(dǎo)致 Redis 響應(yīng)變慢了。

方案二:Redis 的切片集群。雖然組建切片集群比較麻煩,但是它可以保存大量數(shù)據(jù),而且對 Redis 主線程的阻塞影響較小。

如果把 25GB 的數(shù)據(jù)平均分成 5 份(當(dāng)然,也可以不做均分),使用 5 個(gè)實(shí)例來保存,每個(gè)實(shí)例只需要保存 5GB 數(shù)據(jù)。如下圖所示:

那么,在切片集群中,實(shí)例在為 5GB 數(shù)據(jù)生成 RDB 時(shí),數(shù)據(jù)量就小了很多,fork 子進(jìn)程一般不會給主線程帶來較長時(shí)間的阻塞。采用多個(gè)實(shí)例保存數(shù)據(jù)切片后,我們既能保存 25GB 數(shù)據(jù),又避免了 fork 子進(jìn)程阻塞主線程而導(dǎo)致的響應(yīng)突然變慢。

1.什么是切片集群?

切片集群,也叫分片集群,就是指啟動多個(gè) Redis 實(shí)例組成一個(gè)集群,然后按照一定的規(guī)則,把收到的數(shù)據(jù)劃分成多份,每一份用一個(gè)實(shí)例來保存。

2.如何保存更多的數(shù)據(jù)?

2.1橫向擴(kuò)展與縱向擴(kuò)展

上邊案例中使用了大內(nèi)存云片機(jī)和切片集群的方法。這兩種方法分別對應(yīng)著Redis應(yīng)對的數(shù)據(jù)量增多的兩種方案:縱向擴(kuò)展(sclae up)和橫向擴(kuò)展(scale out).

  • 縱向擴(kuò)展:升級單個(gè) Redis 實(shí)例的資源配置,包括增加內(nèi)存容量、增加磁盤容量、使用更高配置的 CPU。就像下圖中,原來的實(shí)例內(nèi)存是 8GB,硬盤是 50GB,縱向擴(kuò)展后,內(nèi)存增加到 24GB,磁盤增加到 150GB。
  • 橫向擴(kuò)展:橫向增加當(dāng)前 Redis 實(shí)例的個(gè)數(shù),就像下圖中,原來使用 1 個(gè) 8GB 內(nèi)存、50GB 磁盤的實(shí)例,現(xiàn)在使用三個(gè)相同配置的實(shí)例。

2.2橫向擴(kuò)展和縱向擴(kuò)展的優(yōu)缺點(diǎn)

2.2.1縱向擴(kuò)展

好處:實(shí)施起來簡單、直接。

潛在問題:

第一個(gè)問題是,當(dāng)使用 RDB 對數(shù)據(jù)進(jìn)行持久化時(shí),如果數(shù)據(jù)量增加,需要的內(nèi)存也會增加,主線程 fork 子進(jìn)程時(shí)就可能會阻塞(比如剛剛的例子中的情況)。不過,如果你不要求持久化、保存 Redis 數(shù)據(jù),那么,縱向擴(kuò)展會是一個(gè)不錯(cuò)的選擇。

第二個(gè)問題:縱向擴(kuò)展會受到硬件和成本的限制。這很容易理解,畢竟,把內(nèi)存從 32GB 擴(kuò)展到 64GB 還算容易,但是,要想擴(kuò)充到 1TB,就會面臨硬件容量和成本上的限制了。

2.2.2橫向擴(kuò)展

橫向擴(kuò)展是一個(gè)擴(kuò)展性更好的方案。要想保存更多的數(shù)據(jù),采用這種方案的話,只用增加 Redis 的實(shí)例個(gè)數(shù)就行了,不用擔(dān)心單個(gè)實(shí)例的硬件和成本限制。在面向百萬、千萬級別的用戶規(guī)模時(shí),橫向擴(kuò)展的 Redis 切片集群會是一個(gè)非常好的選擇。

3.切片集群面臨兩大問題:

數(shù)據(jù)切片后,在多個(gè)實(shí)例之間如何分布?

客戶端怎么確定想要訪問的數(shù)據(jù)在哪個(gè)實(shí)例上?

3.1橫向擴(kuò)展:數(shù)據(jù)切片和實(shí)例的對應(yīng)分布關(guān)系

在切片集群中,數(shù)據(jù)需要分布在不同實(shí)例上,數(shù)據(jù)和實(shí)例之間如何對應(yīng)呢?

Redis Cluster 方案

在 Redis 3.0 之前,官方并沒有針對切片集群提供具體的方案。從 3.0 開始,官方提供了一個(gè)名為 Redis Cluster 的方案,用于實(shí)現(xiàn)切片集群。Redis Cluster 方案中就規(guī)定了數(shù)據(jù)和實(shí)例的對應(yīng)規(guī)則。

3.1.1什么是Redis Cluster?

Redis Cluster 方案采用哈希槽(Hash Slot),來處理數(shù)據(jù)和實(shí)例之間的映射關(guān)系。在 Redis Cluster 方案中,一個(gè)切片集群共有 16384 個(gè)哈希槽,這些哈希槽類似于數(shù)據(jù)分區(qū),每個(gè)鍵值對都會根據(jù)它的 key,被映射到一個(gè)哈希槽中。

具體的映射過程分為兩大步:首先根據(jù)鍵值對的 key,按照CRC16 算法計(jì)算一個(gè) 16 bit 的值;然后,再用這個(gè) 16bit 值對 16384 取模,得到 0~16383 范圍內(nèi)的模數(shù),每個(gè)模數(shù)代表一個(gè)相應(yīng)編號的哈希槽。

3.1.2哈希槽又是如何被映射到具體的 Redis 實(shí)例?

在部署 Redis Cluster 方案時(shí),可以使用 cluster create 命令創(chuàng)建集群,此時(shí),Redis 會自動把這些槽平均分布在集群實(shí)例上。例如,如果集群中有 N 個(gè)實(shí)例,那么,每個(gè)實(shí)例上的槽個(gè)數(shù)為 16384/N 個(gè)。

可以使用 cluster meet 命令手動建立實(shí)例間的連接,形成集群,再使用 cluster addslots 命令,指定每個(gè)實(shí)例上的哈希槽個(gè)數(shù)

舉個(gè)例子,假設(shè)集群中不同 Redis 實(shí)例的內(nèi)存大小配置不一,如果把哈希槽均分在各個(gè)實(shí)例上,在保存相同數(shù)量的鍵值對時(shí),和內(nèi)存大的實(shí)例相比,內(nèi)存小的實(shí)例就會有更大的容量壓力。遇到這種情況時(shí),你可以根據(jù)不同實(shí)例的資源配置情況,使用 cluster addslots 命令手動分配哈希槽。

示意圖中的切片集群一共有 3 個(gè)實(shí)例,同時(shí)假設(shè)有 5 個(gè)哈希槽,我們首先可以通過下面的命令手動分配哈希槽:實(shí)例 1 保存哈希槽 0 和 1,實(shí)例 2 保存哈希槽 2 和 3,實(shí)例 3 保存哈希槽 4。

redis-cli -h 172.16.19.3 –p 6379 cluster addslots 0,1
redis-cli -h 172.16.19.4 –p 6379 cluster addslots 2,3
redis-cli -h 172.16.19.5 –p 6379 cluster addslots 4

在集群運(yùn)行的過程中,key1 和 key2 計(jì)算完 CRC16 值后,對哈希槽總個(gè)數(shù) 5 取模,再根據(jù)各自的模數(shù)結(jié)果,就可以被映射到對應(yīng)的實(shí)例 1 和實(shí)例 3 上了。

注意:在手動分配哈希槽時(shí),需要把 16384 個(gè)槽都分配完,否則 Redis 集群無法正常工作。

切片集群就實(shí)現(xiàn)了數(shù)據(jù)到哈希槽、哈希槽再到實(shí)例的分配

即使實(shí)例有了哈希槽的映射信息,客戶端又是怎么知道要訪問的數(shù)據(jù)在哪個(gè)實(shí)例上呢?

3.2客戶端如何定位數(shù)據(jù)?

在定位鍵值對數(shù)據(jù)時(shí),它所處的哈希槽slots是可以通過計(jì)算得到的,這個(gè)計(jì)算可以在客戶端發(fā)送請求時(shí)來執(zhí)行。但是,要進(jìn)一步定位到實(shí)例,還需要知道哈希槽分布在哪個(gè)實(shí)例上。

一般來說,客戶端和集群實(shí)例建立連接后,實(shí)例就會把哈希槽的分配信息發(fā)給客戶端。但是,在集群剛剛創(chuàng)建的時(shí)候,每個(gè)實(shí)例只知道自己被分配了哪些哈希槽,是不知道其他實(shí)例擁有的哈希槽信息。

客戶端為什么可以在訪問任何一個(gè)實(shí)例時(shí),都能獲得所有的哈希槽信息呢?

Redis 實(shí)例會把自己的哈希槽信息發(fā)給和它相連接的其它實(shí)例,來完成哈希槽分配信息的擴(kuò)散。當(dāng)實(shí)例之間相互連接后,每個(gè)實(shí)例就有所有哈希槽的映射關(guān)系了??蛻舳耸盏焦2坌畔⒑螅瑫压2坌畔⒕彺嬖诒镜?。當(dāng)客戶端請求鍵值對時(shí),會先計(jì)算鍵所對應(yīng)的哈希槽,然后就可以給相應(yīng)的實(shí)例發(fā)送請求了。

在集群中,實(shí)例和哈希槽的對應(yīng)關(guān)系并不是一成不變的,最常見的變化有兩個(gè):

  • 在集群中,實(shí)例有新增或刪除,Redis 需要重新分配哈希槽;
  • 為了負(fù)載均衡,Redis 需要把哈希槽在所有實(shí)例上重新分布一遍。

實(shí)例之間還可以通過相互傳遞消息,獲得最新的哈希槽分配信息

客戶端是無法主動感知這些變化的。這就會導(dǎo)致,它緩存的分配信息和最新的分配信息就不一致了,那該怎么辦呢?

Redis Cluster 方案提供了一種重定向機(jī)制,就是指,客戶端給一個(gè)實(shí)例發(fā)送數(shù)據(jù)讀寫操作時(shí),這個(gè)實(shí)例上并沒有相應(yīng)的數(shù)據(jù),客戶端要再給一個(gè)新實(shí)例發(fā)送操作命令。

3.2.1MOVED 重定向命令的使用方法

客戶端怎么知道重定向時(shí)新實(shí)例訪問地址?客戶端端請求到了一個(gè)不包含 key 對應(yīng)的哈希槽,集群將做何響應(yīng)?

當(dāng)客戶端把一個(gè)鍵值對的操作請求發(fā)送給一個(gè)實(shí)例,這個(gè)實(shí)例上沒有鍵值對映射的哈希槽,這個(gè)實(shí)例就會給客戶端返回MOVED命令響應(yīng)結(jié)果,結(jié)果中就包含了新實(shí)例的訪問地址。

get hello:key
(error)MOVED 13320 172.16.19.5:6379

MOVED 命令表示,客戶端請求的鍵值對所在的哈希槽 13320,實(shí)際是在 172.16.19.5 這個(gè)實(shí)例上。通過返回的 MOVED 命令,就相當(dāng)于把哈希槽所在的新實(shí)例的信息告訴給客戶端了。這樣一來,客戶端就可以直接和 172.16.19.5 連接,并發(fā)送操作請求了。

由于負(fù)載均衡,Slot 2 中的數(shù)據(jù)已經(jīng)從實(shí)例 2 遷移到了實(shí)例 3,但是,客戶端緩存仍然記錄著“Slot 2 在實(shí)例 2”的信息,所以會給實(shí)例 2 發(fā)送命令。實(shí)例 2 給客戶端返回一條 MOVED 命令,把 Slot 2 的最新位置(也就是在實(shí)例 3 上),返回給客戶端,客戶端就會再次向?qū)嵗?3 發(fā)送請求,同時(shí)還會更新本地緩存,把 Slot 2 與實(shí)例的對應(yīng)關(guān)系更新過來

3.2.2ASK命令使用方法

可能會出現(xiàn)這樣一種情況:屬于被遷移槽的一部分鍵值對保存在源節(jié)點(diǎn)里面,而另一部分鍵值對則保存在目標(biāo)節(jié)點(diǎn)里面。

例如:客戶端向?qū)嵗?2 發(fā)送請求,但此時(shí),Slot 2 中的數(shù)據(jù)只有一部分遷移到了實(shí)例 3,還有部分?jǐn)?shù)據(jù)沒有遷移。在這種遷移部分完成的情況下,客戶端就會收到一條 ASK 報(bào)錯(cuò)信息,如下所示:

Get hello:key
(error)ASK 13320 172.16.19.5:6379

這個(gè)結(jié)果中的 ASK 命令就表示,客戶端請求的鍵值對所在的哈希槽 13320,在 172.16.19.5 這個(gè)實(shí)例上,但是這個(gè)哈希槽正在遷移。此時(shí),客戶端需要先給 172.16.19.5 這個(gè)實(shí)例發(fā)送一個(gè) ASKING 命令。這個(gè)命令的意思是,讓這個(gè)實(shí)例允許執(zhí)行客戶端接下來發(fā)送的命令。然后,客戶端再向這個(gè)實(shí)例發(fā)送 GET 命令,以讀取數(shù)據(jù).

ASK 命令表示兩層含義:第一,表明 Slot 數(shù)據(jù)還在遷移中;第二,ASK 命令把客戶端所請求數(shù)據(jù)的最新實(shí)例地址返回給客戶端,此時(shí),客戶端需要給實(shí)例 3 發(fā)送 ASKING 命令,然后再發(fā)送操作命令

3.2.3MOVED命令和ASK命令區(qū)別

  • MOVED命令會更新客戶端緩存的哈希槽分配信息,ASK不會更新客戶端緩存。如果客戶端再次請求 Slot 2 中的數(shù)據(jù),它還是會給實(shí)例 2 發(fā)送請求。
  • ASK命令作用只是讓客戶端能給新實(shí)例發(fā)送一次請求,而MOVED命令修改本地緩存,讓后續(xù)命令發(fā)往新實(shí)例。

3.切片集群總結(jié)

本篇主要講述了,切片集群在保存大量數(shù)據(jù)方面的優(yōu)勢,以及基于哈希槽的數(shù)據(jù)分布機(jī)制和客戶端定位鍵值對的方法。

  • 在應(yīng)對數(shù)據(jù)量大的數(shù)據(jù),數(shù)據(jù)擴(kuò)容時(shí),雖然增加內(nèi)存這種縱向擴(kuò)展的方式簡單直接,但是會造成內(nèi)存過大,導(dǎo)致性能變慢。同事也受到硬件和成本的限制。
  • Redis切片集群提供了橫向擴(kuò)展的模式,也就是使用多個(gè)實(shí)例,并給每個(gè)實(shí)例分配一定的哈希槽,數(shù)據(jù)可以通過鍵的哈希值映射到哈希槽,在通過哈希槽分散分布在不同的實(shí)例上。擴(kuò)展性好,通過增加實(shí)例可以存儲大量數(shù)據(jù)。
  • 集群是實(shí)例的增減和為了實(shí)現(xiàn)負(fù)載均衡而進(jìn)行的數(shù)據(jù)重新分布,導(dǎo)致哈希槽和實(shí)例映射關(guān)系的變化,客戶端請求時(shí),會收到命令執(zhí)行報(bào)錯(cuò)信息。MOVED 和 ASK 命令,讓客戶端獲取最新信息。
  • 在 Redis 3.0 之前,Redis 官方并沒有提供切片集群方案,但是,其實(shí)當(dāng)時(shí)業(yè)界已經(jīng)有了一些切片集群的方案,例如基于客戶端分區(qū)的 ShardedJedis,基于代理的 Codis、Twemproxy 等。這些方案的應(yīng)用早于 Redis Cluster 方案

Redis Cluster 方案通過哈希槽的方式把鍵值對分配到不同的實(shí)例上,這個(gè)過程需要對鍵值對的 key 做 CRC 計(jì)算,然后再和哈希槽做映射,這樣做有什么好處嗎?如果用一個(gè)表直接把鍵值對和實(shí)例的對應(yīng)關(guān)系記錄下來(例如鍵值對 1 在實(shí)例 2 上,鍵值對 2 在實(shí)例 1 上),這樣就不用計(jì)算 key 和哈希槽的對應(yīng)關(guān)系了,只用查表就行了,Redis 為什么不這么做呢?

1、整個(gè)集群存儲key的數(shù)量是無法預(yù)估的,key的數(shù)量非常多時(shí),直接記錄每個(gè)key對應(yīng)的實(shí)例映射關(guān)系,這個(gè)映射表會非常龐大,這個(gè)映射表無論是存儲在服務(wù)端還是客戶端都占用了非常大的內(nèi)存空間。

2、Redis Cluster采用無中心化的模式(無proxy,客戶端與服務(wù)端直連),客戶端在某個(gè)節(jié)點(diǎn)訪問一個(gè)key,如果這個(gè)key不在這個(gè)節(jié)點(diǎn)上,這個(gè)節(jié)點(diǎn)需要有糾正客戶端路由到正確節(jié)點(diǎn)的能力(MOVED響應(yīng)),這就需要節(jié)點(diǎn)之間互相交換路由表,每個(gè)節(jié)點(diǎn)擁有整個(gè)集群完整的路由關(guān)系。如果存儲的都是key與實(shí)例的對應(yīng)關(guān)系,節(jié)點(diǎn)之間交換信息也會變得非常龐大,消耗過多的網(wǎng)絡(luò)資源,而且就算交換完成,相當(dāng)于每個(gè)節(jié)點(diǎn)都需要額外存儲其他節(jié)點(diǎn)的路由表,內(nèi)存占用過大造成資源浪費(fèi)。

3、當(dāng)集群在擴(kuò)容、縮容、數(shù)據(jù)均衡時(shí),節(jié)點(diǎn)之間會發(fā)生數(shù)據(jù)遷移,遷移時(shí)需要修改每個(gè)key的映射關(guān)系,維護(hù)成本高。

4、而在中間增加一層哈希槽,可以把數(shù)據(jù)和節(jié)點(diǎn)解耦,key通過Hash計(jì)算,只需要關(guān)心映射到了哪個(gè)哈希槽,然后再通過哈希槽和節(jié)點(diǎn)的映射表找到節(jié)點(diǎn),相當(dāng)于消耗了很少的CPU資源,不但讓數(shù)據(jù)分布更均勻,還可以讓這個(gè)映射表變得很小,利于客戶端和服務(wù)端保存,節(jié)點(diǎn)之間交換信息時(shí)也變得輕量。

5、當(dāng)集群在擴(kuò)容、縮容、數(shù)據(jù)均衡時(shí),節(jié)點(diǎn)之間的操作例如數(shù)據(jù)遷移,都以哈希槽為基本單位進(jìn)行操作,簡化了節(jié)點(diǎn)擴(kuò)容、縮容的難度,便于集群的維護(hù)和管理。

請求路由、數(shù)據(jù)遷移

Redis使用集群方案就是為了解決單個(gè)節(jié)點(diǎn)數(shù)據(jù)量大、寫入量大產(chǎn)生的性能瓶頸的問題。多個(gè)節(jié)點(diǎn)組成一個(gè)集群,可以提高集群的性能和可靠性,但隨之而來的就是集群的管理問題,最核心問題有2個(gè):請求路由、數(shù)據(jù)遷移(擴(kuò)容/縮容/數(shù)據(jù)平衡)。

1、請求路由:一般都是采用哈希槽的映射關(guān)系表找到指定節(jié)點(diǎn),然后在這個(gè)節(jié)點(diǎn)上操作的方案。

Redis Cluster在每個(gè)節(jié)點(diǎn)記錄完整的映射關(guān)系(便于糾正客戶端的錯(cuò)誤路由請求),同時(shí)也發(fā)給客戶端讓客戶端緩存一份,便于客戶端直接找到指定節(jié)點(diǎn),客戶端與服務(wù)端配合完成數(shù)據(jù)的路由,這需要業(yè)務(wù)在使用Redis Cluster時(shí),必須升級為集群版的SDK才支持客戶端和服務(wù)端的協(xié)議交互。

其他Redis集群化方案例如Twemproxy、Codis都是中心化模式(增加Proxy層),客戶端通過Proxy對整個(gè)集群進(jìn)行操作,Proxy后面可以掛N多個(gè)Redis實(shí)例,Proxy層維護(hù)了路由的轉(zhuǎn)發(fā)邏輯。操作Proxy就像是操作一個(gè)普通Redis一樣,客戶端也不需要更換SDK,而Redis Cluster是把這些路由邏輯做在了SDK中。當(dāng)然,增加一層Proxy也會帶來一定的性能損耗。

2、數(shù)據(jù)遷移:當(dāng)集群節(jié)點(diǎn)不足以支撐業(yè)務(wù)需求時(shí),就需要擴(kuò)容節(jié)點(diǎn),擴(kuò)容就意味著節(jié)點(diǎn)之間的數(shù)據(jù)需要做遷移,而遷移過程中是否會影響到業(yè)務(wù),這也是判定一個(gè)集群方案是否成熟的標(biāo)準(zhǔn)。

Twemproxy不支持在線擴(kuò)容,它只解決了請求路由的問題,擴(kuò)容時(shí)需要停機(jī)做數(shù)據(jù)重新分配。而Redis Cluster和Codis都做到了在線擴(kuò)容(不影響業(yè)務(wù)或?qū)I(yè)務(wù)的影響非常?。攸c(diǎn)就是在數(shù)據(jù)遷移過程中,客戶端對于正在遷移的key進(jìn)行操作時(shí),集群如何處理?還要保證響應(yīng)正確的結(jié)果?

Redis Cluster和Codis都需要服務(wù)端和客戶端/Proxy層互相配合,遷移過程中,服務(wù)端針對正在遷移的key,需要讓客戶端或Proxy去新節(jié)點(diǎn)訪問(重定向),這個(gè)過程就是為了保證業(yè)務(wù)在訪問這些key時(shí)依舊不受影響,而且可以得到正確的結(jié)果。由于重定向的存在,所以這個(gè)期間的訪問延遲會變大。等遷移完成之后,Redis Cluster每個(gè)節(jié)點(diǎn)會更新路由映射表,同時(shí)也會讓客戶端感知到,更新客戶端緩存。Codis會在Proxy層更新路由表,客戶端在整個(gè)過程中無感知。

除了訪問正確的節(jié)點(diǎn)之外,數(shù)據(jù)遷移過程中還需要解決異常情況(遷移超時(shí)、遷移失敗)、性能問題(如何讓數(shù)據(jù)遷移更快、bigkey如何處理),這個(gè)過程中的細(xì)節(jié)也很多。

Redis Cluster的數(shù)據(jù)遷移是同步的,遷移一個(gè)key會同時(shí)阻塞源節(jié)點(diǎn)和目標(biāo)節(jié)點(diǎn),遷移過程中會有性能問題。而Codis提供了異步遷移數(shù)據(jù)的方案,遷移速度更快,對性能影響最小,當(dāng)然,實(shí)現(xiàn)方案也比較復(fù)雜。

二:切片集群方案:Codis \ Redis Cluster

Redis 官方提供的切片集群方案 Redis Cluster。但是Redis Cluster 方案正式發(fā)布前,業(yè)界已經(jīng)廣泛使用的 Codis值得關(guān)注

1.Codis 的整體架構(gòu)和基本流程

Codis 集群中包含了 4 類關(guān)鍵組件。

  • codis server:這是進(jìn)行了二次開發(fā)的 Redis 實(shí)例,其中增加了額外的數(shù)據(jù)結(jié)構(gòu),支持?jǐn)?shù)據(jù)遷移操作,主要負(fù)責(zé)處理具體的數(shù)據(jù)讀寫請求。
  • codis proxy:接收客戶端請求,并把請求轉(zhuǎn)發(fā)給 codis server。
  • Zookeeper 集群:保存集群元數(shù)據(jù),例如數(shù)據(jù)位置信息和 codis proxy 信息。
  • codis dashboard 和 codis fe:共同組成了集群管理工具。其中,codis dashboard 負(fù)責(zé)執(zhí)行集群管理工作,包括增刪 codis server、codis proxy 和進(jìn)行數(shù)據(jù)遷移。而 codis fe 負(fù)責(zé)提供 dashboard 的 Web 操作界面,便于我們直接在 Web 界面上進(jìn)行集群管理。

1.1Codis 是如何處理請求的

首先--》為了讓集群能接收并處理請求:先使用 codis dashboard 設(shè)置 codis server 和 codis proxy 的訪問地址,完成設(shè)置后,codis server 和 codis proxy 才會開始接收連接。

其次--》當(dāng)客戶端要讀寫數(shù)據(jù)時(shí),客戶端直接和 codis proxy 建立連接.codis proxy 本身支持 Redis 的 RESP 交互協(xié)議.客戶端訪問 codis proxy 時(shí),和訪問原生的 Redis 實(shí)例沒有什么區(qū)別。原本連接單實(shí)例的客戶端就可以輕松地和 Codis 集群建立起連接。

最后--》codis proxy 接收到請求,就會查詢請求數(shù)據(jù)和 codis server 的映射關(guān)系,并把請求轉(zhuǎn)發(fā)給相應(yīng)的 codis server 進(jìn)行處理。當(dāng) codis server 處理完請求后,會把結(jié)果返回給 codis proxy,proxy 再把數(shù)據(jù)返回給客戶端。

2.Codis的關(guān)鍵技術(shù)原理

2.1數(shù)據(jù)如何在集群里分布

在 Codis 集群中,一個(gè)數(shù)據(jù)應(yīng)該保存在哪個(gè) codis server 上,這是通過邏輯槽(Slot)映射來完成的,具體來說,總共分成兩步。

第一步,Codis 集群一共有 1024 個(gè) Slot,編號依次是 0 到 1023。把這些 Slot 手動分配給 codis server,每個(gè) server 上包含一部分 Slot。也可以讓 codis dashboard 進(jìn)行自動分配,例如,dashboard 把 1024 個(gè) Slot 在所有 server 上均分。

第二步,當(dāng)客戶端要讀寫數(shù)據(jù)時(shí),會使用 CRC32 算法計(jì)算數(shù)據(jù) key 的哈希值,并把這個(gè)哈希值對 1024 取模。而取模后的值,則對應(yīng) Slot 的編號。此時(shí),根據(jù)第一步分配的 Slot 和 server 對應(yīng)關(guān)系,可以知道數(shù)據(jù)保存在哪個(gè) server 上了。

2.2例子

下圖顯示的就是數(shù)據(jù)、Slot 和 codis server 的映射保存關(guān)系。其中,Slot 0 和 1 被分配到了 server1,Slot 2 分配到 server2,Slot 1022 和 1023 被分配到 server8。當(dāng)客戶端訪問 key 1 和 key 2 時(shí),這兩個(gè)數(shù)據(jù)的 CRC32 值對 1024 取模后,分別是 1 和 1022。因此,它們會被保存在 Slot 1 和 Slot 1022 上,而 Slot 1 和 Slot 1022 已經(jīng)被分配到 codis server 1 和 8 上了。key 1 和 key 2 的保存位置就很清楚。

數(shù)據(jù) key 和 Slot 的映射關(guān)系是客戶端在讀寫數(shù)據(jù)前直接通過 CRC32 計(jì)算得到的,而 Slot 和 codis server 的映射關(guān)系是通過分配完成的,所以就需要用一個(gè)存儲系統(tǒng)保存下來,否則,如果集群有故障了,映射關(guān)系就會丟失。

把 Slot 和 codis server 的映射關(guān)系稱為數(shù)據(jù)路由表(簡稱路由表)。我們在 codis dashboard 上分配好路由表后,dashboard 會把路由表發(fā)送給 codis proxy,同時(shí),dashboard 也會把路由表保存在 Zookeeper 中。codis-proxy 會把路由表緩存在本地,當(dāng)它接收到客戶端請求后,直接查詢本地的路由表,就可以完成正確的請求轉(zhuǎn)發(fā)了

在數(shù)據(jù)分布的實(shí)現(xiàn)方法上,Codis 和 Redis Cluster 很相似,都采用了 key 映射到 Slot、Slot 再分配到實(shí)例上的機(jī)制

2.3Codis 和 Redis Cluster數(shù)據(jù)分布上的區(qū)別

  • Codis 中的路由表:通過 codis dashboard 分配和修改的,并被保存在 Zookeeper 集群中。一旦數(shù)據(jù)位置發(fā)生變化(例如有實(shí)例增減),路由表被修改了,codis dashbaord 就會把修改后的路由表發(fā)送給 codis proxy,proxy 就可以根據(jù)最新的路由信息轉(zhuǎn)發(fā)請求了。
  • 在 Redis Cluster 中,數(shù)據(jù)路由表是通過每個(gè)實(shí)例相互間的通信傳遞的,最后會在每個(gè)實(shí)例上保存一份。當(dāng)數(shù)據(jù)路由信息發(fā)生變化時(shí),就需要在所有實(shí)例間通過網(wǎng)絡(luò)消息進(jìn)行傳遞。所以,如果實(shí)例數(shù)量較多的話,就會消耗較多的集群網(wǎng)絡(luò)資源。

3.集群擴(kuò)容和數(shù)據(jù)遷移

Codis 集群擴(kuò)容包括了兩方面:增加 codis server 和增加 codis proxy。

3.1增加codis server

兩步操作:

  1. 啟動新的 codis server,將它加入集群;
  2. 把部分?jǐn)?shù)據(jù)遷移到新的 server。

3.1.1數(shù)據(jù)遷移的基本流程

Codis 集群按照 Slot 的粒度進(jìn)行數(shù)據(jù)遷移,數(shù)據(jù)遷移是一個(gè)重要的機(jī)制

  1. 在源 server 上,Codis 從要遷移的 Slot 中隨機(jī)選擇一個(gè)數(shù)據(jù),發(fā)送給目的 server。
  2. 目的 server 確認(rèn)收到數(shù)據(jù)后,會給源 server 返回確認(rèn)消息。這時(shí),源 server 會在本地將剛才遷移的數(shù)據(jù)刪除。
  3. 第一步和第二步就是單個(gè)數(shù)據(jù)的遷移過程。Codis 會不斷重復(fù)這個(gè)遷移過程,直到要遷移的 Slot 中的數(shù)據(jù)全部遷移完成。

Codis 實(shí)現(xiàn)了兩種遷移模式,分別是同步遷移和異步遷移

3.1.2同步遷移

同步遷移是指,在數(shù)據(jù)從源 server 發(fā)送給目的 server 的過程中,源 server 是阻塞的,無法處理新的請求操作。這種模式很容易實(shí)現(xiàn),但是遷移過程中會涉及多個(gè)操作(包括數(shù)據(jù)在源 server 序列化、網(wǎng)絡(luò)傳輸、在目的 server 反序列化,以及在源 server 刪除),如果遷移的數(shù)據(jù)是一個(gè) bigkey,源 server 就會阻塞較長時(shí)間,無法及時(shí)處理用戶請求。

3.1.3異步遷移

為了避免數(shù)據(jù)遷移阻塞源 server,Codis 實(shí)現(xiàn)的第二種遷移模式就是異步遷移

異步遷移的兩個(gè)關(guān)鍵特點(diǎn)

第一個(gè)特點(diǎn)是:

  • 當(dāng)源 server 把數(shù)據(jù)發(fā)送給目的 server 后,就可以處理其他請求操作了,不用等到目的 server 的命令執(zhí)行完。而目的 server 會在收到數(shù)據(jù)并反序列化保存到本地后,給源 server 發(fā)送一個(gè) ACK 消息,表明遷移完成。此時(shí),源 server 在本地把剛才遷移的數(shù)據(jù)刪除。
  • 在這個(gè)過程中,遷移的數(shù)據(jù)會被設(shè)置為只讀,所以,源 server 上的數(shù)據(jù)不會被修改,自然也就不會出現(xiàn)“和目的 server 上的數(shù)據(jù)不一致”的問題了

第二個(gè)特點(diǎn)是:

  • 于 bigkey,異步遷移采用了拆分指令的方式進(jìn)行遷移。具體來說就是,對 bigkey 中每個(gè)元素,用一條指令進(jìn)行遷移,而不是把整個(gè) bigkey 進(jìn)行序列化后再整體傳輸。這種化整為零的方式,就避免了 bigkey 遷移時(shí),因?yàn)橐蛄谢罅繑?shù)據(jù)而阻塞源 server 的問題。
  • 當(dāng) bigkey 遷移了一部分?jǐn)?shù)據(jù)后,如果 Codis 發(fā)生故障,就會導(dǎo)致 bigkey 的一部分元素在源 server,而另一部分元素在目的 server,這就破壞了遷移的原子性。所以,Codis 會在目標(biāo) server 上,給 bigkey 的元素設(shè)置一個(gè)臨時(shí)過期時(shí)間。如果遷移過程中發(fā)生故障,那么,目標(biāo) server 上的 key 會在過期后被刪除,不會影響遷移的原子性。當(dāng)正常完成遷移后,bigkey 元素的臨時(shí)過期時(shí)間會被刪除。

第二個(gè)特點(diǎn)例子:

  • 假如要遷移一個(gè)有 1 萬個(gè)元素的 List 類型數(shù)據(jù),當(dāng)使用異步遷移時(shí),源 server 就會給目的 server 傳輸 1 萬條 RPUSH 命令,每條命令對應(yīng)了 List 中一個(gè)元素的插入。在目的 server 上,這 1 萬條命令再被依次執(zhí)行,就可以完成數(shù)據(jù)遷移。
  • 為了提升遷移的效率,Codis 在異步遷移 Slot 時(shí),允許每次遷移多個(gè) key。可以通過異步遷移命令 SLOTSMGRTTAGSLOT-ASYNC 的參數(shù) numkeys 設(shè)置每次遷移的 key 數(shù)量

3.2增加codis proxy

Codis 集群中,客戶端是和 codis proxy 直接連接的,所以,當(dāng)客戶端增加時(shí),一個(gè) proxy 無法支撐大量的請求操作,就需要增加 proxy。增加 proxy 比較容易,直接啟動 proxy,再通過 codis dashboard 把 proxy 加入集群就行。

此時(shí),codis proxy 的訪問連接信息都會保存在 Zookeeper 上。所以,當(dāng)新增了 proxy 后,Zookeeper 上會有最新的訪問列表,客戶端也就可以從 Zookeeper 上讀取 proxy 訪問列表,把請求發(fā)送給新增的 proxy。這樣一來,客戶端的訪問壓力就可以在多個(gè) proxy 上分擔(dān)處理了,如下圖所示

4:客戶端能否與集群直接交互

使用 Redis 單實(shí)例時(shí),客戶端只要符合 RESP 協(xié)議,就可以和實(shí)例進(jìn)行交互和讀寫數(shù)據(jù)。但是,在使用切片集群時(shí),有些功能是和單實(shí)例不一樣的,比如集群中的數(shù)據(jù)遷移操作,在單實(shí)例上是沒有的,而且遷移過程中,數(shù)據(jù)訪問請求可能要被重定向(例如 Redis Cluster 中的 MOVE 命令)。

客戶端需要增加和集群功能相關(guān)的命令操作的支持。如果原來使用單實(shí)例客戶端,想要擴(kuò)容使用集群,就需要使用新客戶端,這對于業(yè)務(wù)應(yīng)用的兼容性來說,并不是特別友好。

Codis 集群在設(shè)計(jì)時(shí),就充分考慮了對現(xiàn)有單實(shí)例客戶端的兼容性。

Codis 使用 codis proxy 直接和客戶端連接,codis proxy 是和單實(shí)例客戶端兼容的。而和集群相關(guān)的管理工作(例如請求轉(zhuǎn)發(fā)、數(shù)據(jù)遷移等),都由 codis proxy、codis dashboard 這些組件來完成,不需要客戶端參與。

業(yè)務(wù)應(yīng)用使用 Codis 集群時(shí),就不用修改客戶端了,可以復(fù)用和單實(shí)例連接的客戶端,既能利用集群讀寫大容量數(shù)據(jù),又避免了修改客戶端增加復(fù)雜的操作邏輯,保證了業(yè)務(wù)代碼的穩(wěn)定性和兼容性。

5.怎么保證集群可靠性?

可靠性是實(shí)際業(yè)務(wù)應(yīng)用的一個(gè)核心要求。對于一個(gè)分布式系統(tǒng)來說,它的可靠性和系統(tǒng)中的組件個(gè)數(shù)有關(guān):組件越多,潛在的風(fēng)險(xiǎn)點(diǎn)也就越多。

和 Redis Cluster 只包含 Redis 實(shí)例不一樣,Codis 集群包含的組件有 4 類

Codis 不同組件的可靠性保證方法。

5.1codis server保證可靠性方法

  1. codis server 其實(shí)就是 Redis 實(shí)例,只不過增加了和集群操作相關(guān)的命令。Redis 的主從復(fù)制機(jī)制和哨兵機(jī)制在 codis server 上都是可以使用的,所以,Codis 就使用主從集群來保證 codis server 的可靠性。簡單來說就是,Codis 給每個(gè) server 配置從庫,并使用哨兵機(jī)制進(jìn)行監(jiān)控,當(dāng)發(fā)生故障時(shí),主從庫可以進(jìn)行切換,從而保證了 server 的可靠性。
  2. 在這種配置情況下,每個(gè) server 就成為了一個(gè) server group,每個(gè) group 中是一主多從的 server。數(shù)據(jù)分布使用的 Slot,也是按照 group 的粒度進(jìn)行分配的。同時(shí),codis proxy 在轉(zhuǎn)發(fā)請求時(shí),也是按照數(shù)據(jù)所在的 Slot 和 group 的對應(yīng)關(guān)系,把寫請求發(fā)到相應(yīng) group 的主庫,讀請求發(fā)到 group 中的主庫或從庫上。

下圖展示的是配置了 server group 的 Codis 集群架構(gòu)。在 Codis 集群中,我們通過部署 server group 和哨兵集群,實(shí)現(xiàn) codis server 的主從切換,提升集群可靠性。

5.2codis proxy 和 Zookeeper可靠性

  • 在 Codis 集群設(shè)計(jì)時(shí),proxy 上的信息源頭都是來自 Zookeeper(例如路由表)。而 Zookeeper 集群使用多個(gè)實(shí)例來保存數(shù)據(jù),只要有超過半數(shù)的 Zookeeper 實(shí)例可以正常工作, Zookeeper 集群就可以提供服務(wù),也可以保證這些數(shù)據(jù)的可靠性。
  • 所以,codis proxy 使用 Zookeeper 集群保存路由表,可以充分利用 Zookeeper 的高可靠性保證來確保 codis proxy 的可靠性,不用再做額外的工作了。當(dāng) codis proxy 發(fā)生故障后,直接重啟 proxy 就行。重啟后的 proxy,可以通過 codis dashboard 從 Zookeeper 集群上獲取路由表,然后,就可以接收客戶端請求進(jìn)行轉(zhuǎn)發(fā)了。這樣的設(shè)計(jì),也降低了 Codis 集群本身的開發(fā)復(fù)雜度。

5.3codis dashboard 和 codis fe可靠性

它們主要提供配置管理和管理員手工操作,負(fù)載壓力不大,所以,它們的可靠性可以不用額外進(jìn)行保證了

6.切片集群方案選擇建議

6.1Codis 和 Redis Cluster 區(qū)別

6.2實(shí)際應(yīng)用中的兩種方案

  • 從穩(wěn)定性和成熟度來看,Codis 應(yīng)用得比較早,在業(yè)界已經(jīng)有了成熟的生產(chǎn)部署。雖然 Codis 引入了 proxy 和 Zookeeper,增加了集群復(fù)雜度,但是,proxy 的無狀態(tài)設(shè)計(jì)和 Zookeeper 自身的穩(wěn)定性,也給 Codis 的穩(wěn)定使用提供了保證。而 Redis Cluster 的推出時(shí)間晚于 Codis,相對來說,成熟度要弱于 Codis,如果你想選擇一個(gè)成熟穩(wěn)定的方案,Codis 更加合適些。
  • 從業(yè)務(wù)應(yīng)用客戶端兼容性來看,連接單實(shí)例的客戶端可以直接連接 codis proxy,而原本連接單實(shí)例的客戶端要想連接 Redis Cluster 的話,就需要開發(fā)新功能。所以,如果你的業(yè)務(wù)應(yīng)用中大量使用了單實(shí)例的客戶端,而現(xiàn)在想應(yīng)用切片集群的話,建議你選擇 Codis,這樣可以避免修改業(yè)務(wù)應(yīng)用中的客戶端。
  • 從使用 Redis 新命令和新特性來看,Codis server 是基于開源的 Redis 3.2.8 開發(fā)的,所以,Codis 并不支持 Redis 后續(xù)的開源版本中的新增命令和數(shù)據(jù)類型。另外,Codis 并沒有實(shí)現(xiàn)開源 Redis 版本的所有命令,比如 BITOP、BLPOP、BRPOP,以及和與事務(wù)相關(guān)的 MUTLI、EXEC 等命令。Codis 官網(wǎng)上列出了不被支持的命令列表,你在使用時(shí)記得去核查一下。所以,如果你想使用開源 Redis 版本的新特性,Redis Cluster 是一個(gè)合適的選擇。
  • 從數(shù)據(jù)遷移性能維度來看,Codis 能支持異步遷移,異步遷移對集群處理正常請求的性能影響要比使用同步遷移的小。所以,如果你在應(yīng)用集群時(shí),數(shù)據(jù)遷移比較頻繁的話,Codis 是個(gè)更合適的選擇。

7.Codis 和 Redis Cluster總結(jié)

Codis 集群包含 codis server、codis proxy、Zookeeper、codis dashboard 和 codis fe 這四大類組件。

  1. codis proxy 和 codis server 負(fù)責(zé)處理數(shù)據(jù)讀寫請求,其中,codis proxy 和客戶端連接,接收請求,并轉(zhuǎn)發(fā)請求給 codis server,而 codis server 負(fù)責(zé)具體處理請求。
  2. codis dashboard 和 codis fe 負(fù)責(zé)集群管理,其中,codis dashboard 執(zhí)行管理操作,而 codis fe 提供 Web 管理界面。
  3. Zookeeper 集群負(fù)責(zé)保存集群的所有元數(shù)據(jù)信息,包括路由表、proxy 實(shí)例信息等。這里,有個(gè)地方需要你注意,除了使用 Zookeeper,Codis 還可以使用 etcd 或本地文件系統(tǒng)保存元數(shù)據(jù)信息。

Codis 使用上的小建議:當(dāng)你有多條業(yè)務(wù)線要使用 Codis 時(shí),可以啟動多個(gè) codis dashboard,每個(gè) dashboard 管理一部分 codis server,同時(shí),再用一個(gè) dashboard 對應(yīng)負(fù)責(zé)一個(gè)業(yè)務(wù)線的集群管理,這樣,就可以做到用一個(gè) Codis 集群實(shí)現(xiàn)多條業(yè)務(wù)線的隔離管理了。

假設(shè) Codis 集群中保存的 80% 的鍵值對都是 Hash 類型,每個(gè) Hash 集合的元素?cái)?shù)量在 10 萬~20 萬個(gè),每個(gè)集合元素的大小是 2KB。你覺得,遷移一個(gè)這樣的 Hash 集合數(shù)據(jù),會對 Codis 的性能造成影響嗎?

Codis 在遷移數(shù)據(jù)時(shí),設(shè)計(jì)的方案可以保證遷移性能不受影響。

  • 1、異步遷移:源節(jié)點(diǎn)把遷移的數(shù)據(jù)發(fā)送給目標(biāo)節(jié)點(diǎn)后就返回,之后接著處理客戶端請求,這個(gè)階段不會長時(shí)間阻塞源節(jié)點(diǎn)。目標(biāo)節(jié)點(diǎn)加載遷移的數(shù)據(jù)成功后,向源節(jié)點(diǎn)發(fā)送 ACK 命令,告知其遷移成功。
  • 2、源節(jié)點(diǎn)異步釋放 key:源節(jié)點(diǎn)收到目標(biāo)節(jié)點(diǎn) ACK 后,在源實(shí)例刪除這個(gè) key,釋放 key 內(nèi)存的操作,會放到后臺線程中執(zhí)行,不會阻塞源實(shí)例。(沒錯(cuò),Codis 比 Redis 更早地支持了 lazy-free,只不過只用在了數(shù)據(jù)遷移中)。
  • 3、小對象序列化傳輸:小對象依舊采用序列化方式遷移,節(jié)省網(wǎng)絡(luò)流量。
  • 4、bigkey 分批遷移:bigkey 拆分成一條條命令,打包分批遷移(利用了 Pipeline 的優(yōu)勢),提升遷移速度。
  • 5、一次遷移多個(gè) key:一次發(fā)送多個(gè) key 進(jìn)行遷移,提升遷移效率。
  • 6、遷移流量控制:遷移時(shí)會控制緩沖區(qū)大小,避免占滿網(wǎng)絡(luò)帶寬。
  • 7、bigkey 遷移原子性保證(兼容遷移失敗情況):遷移前先發(fā)一個(gè) DEL 命令到目標(biāo)節(jié)點(diǎn)(重試可保證冪等性),然后把 bigkey 拆分成一條條命令,并設(shè)置一個(gè)臨時(shí)過期時(shí)間(防止遷移失敗在目標(biāo)節(jié)點(diǎn)遺留垃圾數(shù)據(jù)),遷移成功后在目標(biāo)節(jié)點(diǎn)設(shè)置真實(shí)的過期時(shí)間。 Codis 在數(shù)據(jù)遷移方面要比 Redis Cluster 做得更優(yōu)秀,而且 Codis 還帶了一個(gè)非常友好的運(yùn)維界面,方便 DBA 執(zhí)行增刪節(jié)點(diǎn)、主從切換、數(shù)據(jù)遷移等操作。

三.通信開銷:限制Redis Cluster規(guī)模的關(guān)鍵因素

1:為什么要限定集群規(guī)模呢?

Redis Cluster 能保存的數(shù)據(jù)量以及支撐的吞吐量,跟集群的實(shí)例規(guī)模密切相關(guān)。Redis 官方給出了 Redis Cluster 的規(guī)模上限,就是一個(gè)集群運(yùn)行 1000 個(gè)實(shí)例

這里的一個(gè)關(guān)鍵因素就是,實(shí)例間的通信開銷會隨著實(shí)例規(guī)模增加而增大,在集群超過一定規(guī)模時(shí)(比如 800 節(jié)點(diǎn)),集群吞吐量反而會下降。所以,集群的實(shí)際規(guī)模會受到限制。

2:實(shí)例通信方法和對集群規(guī)模的影響

Redis Cluster 在運(yùn)行時(shí),每個(gè)實(shí)例上都會保存 Slot 和實(shí)例的對應(yīng)關(guān)系(也就是 Slot 映射表),以及自身的狀態(tài)信息。

為了讓集群中的每個(gè)實(shí)例都知道其它所有實(shí)例的狀態(tài)信息,實(shí)例之間會按照一定的規(guī)則進(jìn)行通信。這個(gè)規(guī)則就是 Gossip 協(xié)議

2.1Gossip 協(xié)議

Gossip 協(xié)議的工作原理可以概括成兩點(diǎn)。:檢測實(shí)例時(shí)候在線\給發(fā)送PING命令實(shí)例返回PONG消息

  1. 一是,每個(gè)實(shí)例之間會按照一定的頻率,從集群中隨機(jī)挑選一些實(shí)例,把 PING 消息發(fā)送給挑選出來的實(shí)例,用來檢測這些實(shí)例是否在線,并交換彼此的狀態(tài)信息。PING 消息中封裝了發(fā)送消息的實(shí)例自身的狀態(tài)信息、部分其它實(shí)例的狀態(tài)信息,以及 Slot 映射表。
  2. 二是,一個(gè)實(shí)例在接收到 PING 消息后,會給發(fā)送 PING 消息的實(shí)例,發(fā)送一個(gè) PONG 消息。PONG 消息包含的內(nèi)容和 PING 消息一樣。

Gossip 協(xié)議可以保證在一段時(shí)間后,集群中的每一個(gè)實(shí)例都能獲得其它所有實(shí)例的狀態(tài)信息。

這樣一來,即使有新節(jié)點(diǎn)加入、節(jié)點(diǎn)故障、Slot 變更等事件發(fā)生,實(shí)例間也可以通過 PING、PONG 消息的傳遞,完成集群狀態(tài)在每個(gè)實(shí)例上的同步

3.通信的影響

實(shí)例間使用 Gossip 協(xié)議進(jìn)行通信時(shí),通信開銷受到通信消息大小和通信頻率這兩方面的影響。消息越大、頻率越高,相應(yīng)的通信開銷也就越大。如果想要實(shí)現(xiàn)高效的通信,可以從這兩方面入手去調(diào)優(yōu)

3.1Gossip 消息大小

Redis 實(shí)例發(fā)送的 PING 消息的消息體是由 clusterMsgDataGossip 結(jié)構(gòu)體組成的,這個(gè)結(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;

其中,CLUSTER_NAMELEN 和 NET_IP_STR_LEN 的值分別是 40 和 46,分別表示,nodename 和 ip 這兩個(gè)字節(jié)數(shù)組的長度是 40 字節(jié)和 46 字節(jié),我們再把結(jié)構(gòu)體中其它信息的大小加起來,就可以得到一個(gè) Gossip 消息的大小了,即 104 字節(jié)。

每個(gè)實(shí)例在發(fā)送一個(gè) Gossip 消息時(shí),除了會傳遞自身的狀態(tài)信息,默認(rèn)還會傳遞集群十分之一實(shí)例的狀態(tài)信息。

例子:

所以,對于一個(gè)包含了 1000 個(gè)實(shí)例的集群來說,每個(gè)實(shí)例發(fā)送一個(gè) PING 消息時(shí),會包含 100 個(gè)實(shí)例的狀態(tài)信息,總的數(shù)據(jù)量是 10400 字節(jié),再加上發(fā)送實(shí)例自身的信息,一個(gè) Gossip 消息大約是 10KB。為了讓 Slot 映射表能夠在不同實(shí)例間傳播,PING 消息中還帶有一個(gè)長度為 16,384 bit 的 Bitmap,這個(gè) Bitmap 的每一位對應(yīng)了一個(gè) Slot,如果某一位為 1,就表示這個(gè) Slot 屬于當(dāng)前實(shí)例。這個(gè) Bitmap 大小換算成字節(jié)后,是 2KB實(shí)例狀態(tài)信息和 Slot 分配信息相加,就可以得到一個(gè) PING 消息的大小了,大約是 12KB。

PONG 消息和 PING 消息的內(nèi)容一樣,它的大小大約是 12KB。每個(gè)實(shí)例發(fā)送了 PING 消息后,還會收到返回的 PONG 消息,兩個(gè)消息加起來有 24KB。

從絕對值上來看,24KB 并不算很大,但是,如果實(shí)例正常處理的單個(gè)請求只有幾 KB 的話,那么,實(shí)例為了維護(hù)集群狀態(tài)一致傳輸?shù)?PING/PONG 消息,就要比單個(gè)業(yè)務(wù)請求大了。而且,每個(gè)實(shí)例都會給其它實(shí)例發(fā)送 PING/PONG 消息。隨著集群規(guī)模增加,這些心跳消息的數(shù)量也會越多,會占據(jù)一部分集群的網(wǎng)絡(luò)通信帶寬,進(jìn)而會降低集群服務(wù)正??蛻舳苏埱蟮耐掏铝?。

3.2實(shí)例間通信頻率

Redis Cluster 的實(shí)例啟動后,默認(rèn)會每秒從本地的實(shí)例列表中隨機(jī)選出 5 個(gè)實(shí)例,再從這 5 個(gè)實(shí)例中找出一個(gè)最久沒有通信的實(shí)例,把 PING 消息發(fā)送給該實(shí)例。這是實(shí)例周期性發(fā)送 PING 消息的基本做法

這里有一個(gè)問題:實(shí)例選出來的這個(gè)最久沒有通信的實(shí)例,畢竟是從隨機(jī)選出的 5 個(gè)實(shí)例中挑選的,這并不能保證這個(gè)實(shí)例就一定是整個(gè)集群中最久沒有通信的實(shí)例??赡軙霈F(xiàn),有些實(shí)例一直沒有被發(fā)送 PING 消息,導(dǎo)致它們維護(hù)的集群狀態(tài)已經(jīng)過期。

為了避免這種情況,Redis Cluster 的實(shí)例會按照每 100ms 一次的頻率,掃描本地的實(shí)例列表,如果發(fā)現(xiàn)有實(shí)例最近一次接收 PONG 消息的時(shí)間,已經(jīng)大于配置項(xiàng) cluster-node-timeout 的一半了(cluster-node-timeout/2),就會立刻給該實(shí)例發(fā)送 PING 消息,更新這個(gè)實(shí)例上的集群狀態(tài)信息

當(dāng)集群規(guī)模擴(kuò)大之后,因?yàn)榫W(wǎng)絡(luò)擁塞或是不同服務(wù)器間的流量競爭,會導(dǎo)致實(shí)例間的網(wǎng)絡(luò)通信延遲增加。如果有部分實(shí)例無法收到其它實(shí)例發(fā)送的 PONG 消息,就會引起實(shí)例之間頻繁地發(fā)送 PING 消息,這又會對集群網(wǎng)絡(luò)通信帶來額外的開銷

總結(jié)下單實(shí)例每秒會發(fā)送的 PING 消息數(shù)量,如下所示:

PING 消息發(fā)送數(shù)量 = 1 + 10 * 實(shí)例數(shù)(最近一次接收 PONG 消息的時(shí)間超出 cluster-node-timeout/2)

其中,1 是指單實(shí)例常規(guī)按照每 1 秒發(fā)送一個(gè) PING 消息,10 是指每 1 秒內(nèi)實(shí)例會執(zhí)行 10 次檢查,每次檢查后會給 PONG 消息超時(shí)的實(shí)例發(fā)送消息。

例子:

假設(shè)單個(gè)實(shí)例檢測發(fā)現(xiàn),每 100 毫秒有 10 個(gè)實(shí)例的 PONG 消息接收超時(shí),那么,這個(gè)實(shí)例每秒就會發(fā)送 101 個(gè) PING 消息,約占 1.2MB/s 帶寬。如果集群中有 30 個(gè)實(shí)例按照這種頻率發(fā)送消息,就會占用 36MB/s 帶寬,這就會擠占集群中用于服務(wù)正常請求的帶寬

4.如何降低實(shí)例間的通信開銷?

4.1減小實(shí)例傳輸?shù)南⒋笮?/h4>

為了降低實(shí)例間的通信開銷,從原理上說,可以減小實(shí)例傳輸?shù)南⒋笮。≒ING/PONG 消息、Slot 分配信息),但是,因?yàn)榧簩?shí)例依賴 PING、PONG 消息和 Slot 分配信息,來維持集群狀態(tài)的統(tǒng)一,一旦減小了傳遞的消息大小,就會導(dǎo)致實(shí)例間的通信信息減少,不利于集群維護(hù),所以,減小實(shí)例傳輸?shù)南⒋笮〔荒懿捎眠@種方式。

4.2降低實(shí)例間發(fā)送消息的頻率:

實(shí)例間發(fā)送消息的頻率有兩個(gè)。

  1. 每個(gè)實(shí)例每 1 秒發(fā)送一條 PING 消息。這個(gè)頻率不算高,如果再降低該頻率的話,集群中各實(shí)例的狀態(tài)可能就沒辦法及時(shí)傳播了。
  2. 每個(gè)實(shí)例每 100 毫秒會做一次檢測,給 PONG 消息接收超過 cluster-node-timeout/2 的節(jié)點(diǎn)發(fā)送 PING 消息。實(shí)例按照每 100 毫秒進(jìn)行檢測的頻率,是 Redis 實(shí)例默認(rèn)的周期性檢查任務(wù)的統(tǒng)一頻率,我們一般不需要修改它。

就只有 cluster-node-timeout 這個(gè)配置項(xiàng)可以修改

配置項(xiàng) cluster-node-timeout 定義了集群實(shí)例被判斷為故障的心跳超時(shí)時(shí)間,默認(rèn)是 15 秒。如果 cluster-node-timeout 值比較小,那么,在大規(guī)模集群中,就會比較頻繁地出現(xiàn) PONG 消息接收超時(shí)的情況,從而導(dǎo)致實(shí)例每秒要執(zhí)行 10 次“給 PONG 消息超時(shí)的實(shí)例發(fā)送 PING 消息”這個(gè)操作

所以,為了避免過多的心跳消息擠占集群帶寬,可以調(diào)大 cluster-node-timeout 值,比如說調(diào)大到 20 秒或 25 秒。這樣一來, PONG 消息接收超時(shí)的情況就會有所緩解,單實(shí)例也不用頻繁地每秒執(zhí)行 10 次心跳發(fā)送操作了。

也不要把 cluster-node-timeout 調(diào)得太大,否則,如果實(shí)例真的發(fā)生了故障,需要等待 cluster-node-timeout 時(shí)長后,才能檢測出這個(gè)故障,這又會導(dǎo)致實(shí)際的故障恢復(fù)時(shí)間被延長,會影響到集群服務(wù)的正常使用

為了驗(yàn)證調(diào)整 cluster-node-timeout 值后,是否能減少心跳消息占用的集群網(wǎng)絡(luò)帶寬,建議:可以在調(diào)整 cluster-node-timeout 值的前后,使用 tcpdump 命令抓取實(shí)例發(fā)送心跳信息網(wǎng)絡(luò)包的情況。

執(zhí)行下面的命令后,可以抓取到 192.168.10.3 機(jī)器上的實(shí)例從 16379 端口發(fā)送的心跳網(wǎng)絡(luò)包,并把網(wǎng)絡(luò)包的內(nèi)容保存到 r1.cap 文件中:

tcpdump host 192.168.10.3 port 16379 -i 網(wǎng)卡名 -w /tmp/r1.cap

通過分析網(wǎng)絡(luò)包的數(shù)量和大小,就可以判斷調(diào)整 cluster-node-timeout 值前后,心跳消息占用的帶寬情況了。

5.通信開銷總結(jié)

Redis Cluster 實(shí)例間以 Gossip 協(xié)議進(jìn)行通信的機(jī)制。Redis Cluster 運(yùn)行時(shí),各實(shí)例間需要通過 PING、PONG 消息進(jìn)行信息交換,這些心跳消息包含了當(dāng)前實(shí)例和部分其它實(shí)例的狀態(tài)信息,以及 Slot 分配信息。這種通信機(jī)制有助于 Redis Cluster 中的所有實(shí)例都擁有完整的集群狀態(tài)信息。

但是,隨著集群規(guī)模的增加,實(shí)例間的通信量也會增加。如果盲目地對 Redis Cluster 進(jìn)行擴(kuò)容,就可能會遇到集群性能變慢的情況。這是因?yàn)?,集群中大?guī)模的實(shí)例間心跳消息會擠占集群處理正常請求的帶寬。而且,有些實(shí)例可能因?yàn)榫W(wǎng)絡(luò)擁塞導(dǎo)致無法及時(shí)收到 PONG 消息,每個(gè)實(shí)例在運(yùn)行時(shí)會周期性地(每秒 10 次)檢測是否有這種情況發(fā)生,一旦發(fā)生,就會立即給這些 PONG 消息超時(shí)的實(shí)例發(fā)送心跳消息。

集群規(guī)模越大,網(wǎng)絡(luò)擁塞的概率就越高,相應(yīng)的,PONG 消息超時(shí)的發(fā)生概率就越高,這就會導(dǎo)致集群中有大量的心跳消息,影響集群服務(wù)正常請求。可以通過調(diào)整 cluster-node-timeout 配置項(xiàng)減少心跳消息的占用帶寬情況,但是,在實(shí)際應(yīng)用中,如果不是特別需要大容量集群,建議把 Redis Cluster 的規(guī)??刂圃?400~500 個(gè)實(shí)例。

設(shè)單個(gè)實(shí)例每秒能支撐 8 萬請求操作(8 萬 QPS),每個(gè)主實(shí)例配置 1 個(gè)從實(shí)例,那么,400~ 500 個(gè)實(shí)例可支持 1600 萬~2000 萬 QPS(200/250 個(gè)主實(shí)例 *8 萬 QPS=1600/2000 萬 QPS),這個(gè)吞吐量性能可以滿足不少業(yè)務(wù)應(yīng)用的需求

如果我們采用跟 Codis 保存 Slot 分配信息相類似的方法,把集群實(shí)例狀態(tài)信息和 Slot 分配信息保存在第三方的存儲系統(tǒng)上(例如 Zookeeper),這種方法會對集群規(guī)模產(chǎn)生什么影響嗎?

答案:假設(shè)我們將 Zookeeper 作為第三方存儲系統(tǒng),保存集群實(shí)例狀態(tài)信息和 Slot 分配信息,那么,實(shí)例只需要和 Zookeeper 通信交互信息,實(shí)例之間就不需要發(fā)送大量的心跳消息來同步集群狀態(tài)了。這種做法可以減少實(shí)例之間用于心跳的網(wǎng)絡(luò)通信量,有助于實(shí)現(xiàn)大規(guī)模集群。

而且,網(wǎng)絡(luò)帶寬可以集中用在服務(wù)客戶端請求上。不過,在這種情況下,實(shí)例獲取或更新集群狀態(tài)信息時(shí),都需要和 Zookeeper 交互,Zookeeper 的網(wǎng)絡(luò)通信帶寬需求會增加。所以,采用這種方法的時(shí)候,需要給 Zookeeper 保證一定的網(wǎng)絡(luò)帶寬,避免 Zookeeper 受限于帶寬而無法和實(shí)例快速通信。

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。 

相關(guān)文章

  • 控制Redis的hash的field中的過期時(shí)間

    控制Redis的hash的field中的過期時(shí)間

    這篇文章主要介紹了控制Redis的hash的field中的過期時(shí)間問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Redisson如何解決redis分布式鎖過期時(shí)間到了業(yè)務(wù)沒執(zhí)行完問題

    Redisson如何解決redis分布式鎖過期時(shí)間到了業(yè)務(wù)沒執(zhí)行完問題

    這篇文章主要介紹了Redisson如何解決redis分布式鎖過期時(shí)間到了業(yè)務(wù)沒執(zhí)行完問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • Redis中Redisson布隆過濾器的學(xué)習(xí)

    Redis中Redisson布隆過濾器的學(xué)習(xí)

    布隆過濾器是一個(gè)非常長的二進(jìn)制向量和一系列隨機(jī)哈希函數(shù)的組合,可用于檢索一個(gè)元素是否存在,本文就詳細(xì)的介紹一下Redisson布隆過濾器,具有一定的參考價(jià)值,感興趣的可以了解一下
    2022-05-05
  • Redis 安裝 redistimeseries.so(時(shí)間序列數(shù)據(jù)類型)的配置步驟

    Redis 安裝 redistimeseries.so(時(shí)間序列數(shù)據(jù)類型)的配置步驟

    這篇文章主要介紹了Redis 安裝 redistimeseries.so(時(shí)間序列數(shù)據(jù)類型)詳細(xì)教程,配置步驟需要先下載redistimeseries.so 文件,文中介紹了啟動失敗問題排查,需要的朋友可以參考下
    2024-01-01
  • 詳解Redis集群搭建的三種方式

    詳解Redis集群搭建的三種方式

    Redis是一個(gè)開源的key-value存儲系統(tǒng),大部分互聯(lián)網(wǎng)企業(yè)都用來做服務(wù)器端緩存。Redis在3.0版本前只支持單實(shí)例模式,雖然支持主從模式、哨兵模式部署來解決單點(diǎn)故障,但是現(xiàn)在互聯(lián)網(wǎng)企業(yè)動輒大幾百G的數(shù)據(jù),沒法滿足業(yè)務(wù)的需求,所以Redis在3.0版本以后就推出了集群模式。
    2021-05-05
  • Redis獲取某個(gè)大key值的腳本實(shí)例

    Redis獲取某個(gè)大key值的腳本實(shí)例

    這篇文章主要給大家分享介紹了關(guān)于Redis獲取某個(gè)大key值的一個(gè)腳本實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-04-04
  • Redis?數(shù)據(jù)恢復(fù)及持久化策略分析

    Redis?數(shù)據(jù)恢復(fù)及持久化策略分析

    本文將詳細(xì)分析Redis的數(shù)據(jù)恢復(fù)機(jī)制,持久化策略及其特點(diǎn),并討論選擇持久化策略時(shí)需要考慮的因素,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-06-06
  • Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù)的教程

    Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù)的教程

    RediSearch提供了一種簡單快速的方法對 hash 或者 json 類型數(shù)據(jù)的任何字段建立二級索引,然后就可以對被索引的 hash 或者 json 類型數(shù)據(jù)字段進(jìn)行搜索和聚合操作,這篇文章主要介紹了Redis全文搜索教程之創(chuàng)建索引并關(guān)聯(lián)源數(shù)據(jù),需要的朋友可以參考下
    2023-12-12
  • 使用Redis存儲SpringBoot項(xiàng)目中Session的詳細(xì)步驟

    使用Redis存儲SpringBoot項(xiàng)目中Session的詳細(xì)步驟

    在開發(fā)Spring Boot項(xiàng)目時(shí),我們通常會遇到如何高效管理Session的問題,默認(rèn)情況下,Spring Boot會將Session存儲在內(nèi)存中,今天,我們將學(xué)習(xí)如何將Session存儲從內(nèi)存切換到Redis,并驗(yàn)證配置是否成功,需要的朋友可以參考下
    2024-06-06
  • Windows下安裝Redis服務(wù)的圖文教程

    Windows下安裝Redis服務(wù)的圖文教程

    Redis是有名的NoSql數(shù)據(jù)庫,一般Linux都會默認(rèn)支持。但在Windows環(huán)境中,可能需要手動安裝設(shè)置才能有效使用。下面通過本文給大家介紹Windows下安裝Redis服務(wù)的圖文教程,感興趣的朋友一起看看吧
    2018-08-08

最新評論