Redis中切片集群詳解
一.切片集群
Redis中,數(shù)據(jù)增多了,是該加內(nèi)存還是加實例?
采用云主機(jī)來運(yùn)行 Redis 實例,那么,該如何選擇云主機(jī)的內(nèi)存容量呢?
用 Redis 保存 5000 萬個鍵值對,每個鍵值對大約是 512B
方案一:大內(nèi)存云主機(jī):選擇一臺 32GB 內(nèi)存的云主機(jī)來部署 Redis。因為 32GB 的內(nèi)存能保存所有數(shù)據(jù),而且還留有 7GB,可以保證系統(tǒng)的正常運(yùn)行。同時,我還采用 RDB 對數(shù)據(jù)做持久化,以確保 Redis 實例故障后,還能從 RDB 恢復(fù)數(shù)據(jù)。
結(jié)果:Redis 的響應(yīng)有時會非常慢,使用 INFO 命令查看 Redis 的 latest_fork_usec 指標(biāo)值(表示最近一次 fork 的耗時),結(jié)果顯示這個指標(biāo)值特別高,快到秒級別了。這跟 Redis 的持久化機(jī)制有關(guān)系。在使用 RDB 進(jìn)行持久化時,Redis 會 fork 子進(jìn)程來完成,fork 操作的用時和 Redis 的數(shù)據(jù)量是正相關(guān)的,而 fork 在執(zhí)行時會阻塞主線程。數(shù)據(jù)量越大,fork 操作造成的主線程阻塞的時間越長。所以,在使用 RDB 對 25GB 的數(shù)據(jù)進(jìn)行持久化時,數(shù)據(jù)量較大,后臺運(yùn)行的子進(jìn)程在 fork 創(chuàng)建時阻塞了主線程,于是就導(dǎo)致 Redis 響應(yīng)變慢了。
方案二:Redis 的切片集群。雖然組建切片集群比較麻煩,但是它可以保存大量數(shù)據(jù),而且對 Redis 主線程的阻塞影響較小。
如果把 25GB 的數(shù)據(jù)平均分成 5 份(當(dāng)然,也可以不做均分),使用 5 個實例來保存,每個實例只需要保存 5GB 數(shù)據(jù)。如下圖所示:
那么,在切片集群中,實例在為 5GB 數(shù)據(jù)生成 RDB 時,數(shù)據(jù)量就小了很多,fork 子進(jìn)程一般不會給主線程帶來較長時間的阻塞。采用多個實例保存數(shù)據(jù)切片后,我們既能保存 25GB 數(shù)據(jù),又避免了 fork 子進(jìn)程阻塞主線程而導(dǎo)致的響應(yīng)突然變慢。
1.什么是切片集群?
切片集群,也叫分片集群,就是指啟動多個 Redis 實例組成一個集群,然后按照一定的規(guī)則,把收到的數(shù)據(jù)劃分成多份,每一份用一個實例來保存。
2.如何保存更多的數(shù)據(jù)?
2.1橫向擴(kuò)展與縱向擴(kuò)展
上邊案例中使用了大內(nèi)存云片機(jī)和切片集群的方法。這兩種方法分別對應(yīng)著Redis應(yīng)對的數(shù)據(jù)量增多的兩種方案:縱向擴(kuò)展(sclae up)和橫向擴(kuò)展(scale out).
- 縱向擴(kuò)展:升級單個 Redis 實例的資源配置,包括增加內(nèi)存容量、增加磁盤容量、使用更高配置的 CPU。就像下圖中,原來的實例內(nèi)存是 8GB,硬盤是 50GB,縱向擴(kuò)展后,內(nèi)存增加到 24GB,磁盤增加到 150GB。
- 橫向擴(kuò)展:橫向增加當(dāng)前 Redis 實例的個數(shù),就像下圖中,原來使用 1 個 8GB 內(nèi)存、50GB 磁盤的實例,現(xiàn)在使用三個相同配置的實例。
2.2橫向擴(kuò)展和縱向擴(kuò)展的優(yōu)缺點
2.2.1縱向擴(kuò)展
好處:實施起來簡單、直接。
潛在問題:
第一個問題是,當(dāng)使用 RDB 對數(shù)據(jù)進(jìn)行持久化時,如果數(shù)據(jù)量增加,需要的內(nèi)存也會增加,主線程 fork 子進(jìn)程時就可能會阻塞(比如剛剛的例子中的情況)。不過,如果你不要求持久化、保存 Redis 數(shù)據(jù),那么,縱向擴(kuò)展會是一個不錯的選擇。
第二個問題:縱向擴(kuò)展會受到硬件和成本的限制。這很容易理解,畢竟,把內(nèi)存從 32GB 擴(kuò)展到 64GB 還算容易,但是,要想擴(kuò)充到 1TB,就會面臨硬件容量和成本上的限制了。
2.2.2橫向擴(kuò)展
橫向擴(kuò)展是一個擴(kuò)展性更好的方案。要想保存更多的數(shù)據(jù),采用這種方案的話,只用增加 Redis 的實例個數(shù)就行了,不用擔(dān)心單個實例的硬件和成本限制。在面向百萬、千萬級別的用戶規(guī)模時,橫向擴(kuò)展的 Redis 切片集群會是一個非常好的選擇。
3.切片集群面臨兩大問題:
數(shù)據(jù)切片后,在多個實例之間如何分布?
客戶端怎么確定想要訪問的數(shù)據(jù)在哪個實例上?
3.1橫向擴(kuò)展:數(shù)據(jù)切片和實例的對應(yīng)分布關(guān)系
在切片集群中,數(shù)據(jù)需要分布在不同實例上,數(shù)據(jù)和實例之間如何對應(yīng)呢?
Redis Cluster 方案
在 Redis 3.0 之前,官方并沒有針對切片集群提供具體的方案。從 3.0 開始,官方提供了一個名為 Redis Cluster 的方案,用于實現(xiàn)切片集群。Redis Cluster 方案中就規(guī)定了數(shù)據(jù)和實例的對應(yīng)規(guī)則。
3.1.1什么是Redis Cluster?
Redis Cluster 方案采用哈希槽(Hash Slot),來處理數(shù)據(jù)和實例之間的映射關(guān)系。在 Redis Cluster 方案中,一個切片集群共有 16384 個哈希槽,這些哈希槽類似于數(shù)據(jù)分區(qū),每個鍵值對都會根據(jù)它的 key,被映射到一個哈希槽中。
具體的映射過程分為兩大步:首先根據(jù)鍵值對的 key,按照CRC16 算法計算一個 16 bit 的值;然后,再用這個 16bit 值對 16384 取模,得到 0~16383 范圍內(nèi)的模數(shù),每個模數(shù)代表一個相應(yīng)編號的哈希槽。
3.1.2哈希槽又是如何被映射到具體的 Redis 實例?
在部署 Redis Cluster 方案時,可以使用 cluster create 命令創(chuàng)建集群,此時,Redis 會自動把這些槽平均分布在集群實例上。例如,如果集群中有 N 個實例,那么,每個實例上的槽個數(shù)為 16384/N 個。
也可以使用 cluster meet 命令手動建立實例間的連接,形成集群,再使用 cluster addslots 命令,指定每個實例上的哈希槽個數(shù)
舉個例子,假設(shè)集群中不同 Redis 實例的內(nèi)存大小配置不一,如果把哈希槽均分在各個實例上,在保存相同數(shù)量的鍵值對時,和內(nèi)存大的實例相比,內(nèi)存小的實例就會有更大的容量壓力。遇到這種情況時,你可以根據(jù)不同實例的資源配置情況,使用 cluster addslots 命令手動分配哈希槽。
示意圖中的切片集群一共有 3 個實例,同時假設(shè)有 5 個哈希槽,我們首先可以通過下面的命令手動分配哈希槽:實例 1 保存哈希槽 0 和 1,實例 2 保存哈希槽 2 和 3,實例 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 計算完 CRC16 值后,對哈希槽總個數(shù) 5 取模,再根據(jù)各自的模數(shù)結(jié)果,就可以被映射到對應(yīng)的實例 1 和實例 3 上了。
注意:在手動分配哈希槽時,需要把 16384 個槽都分配完,否則 Redis 集群無法正常工作。
切片集群就實現(xiàn)了數(shù)據(jù)到哈希槽、哈希槽再到實例的分配
即使實例有了哈希槽的映射信息,客戶端又是怎么知道要訪問的數(shù)據(jù)在哪個實例上呢?
3.2客戶端如何定位數(shù)據(jù)?
在定位鍵值對數(shù)據(jù)時,它所處的哈希槽slots是可以通過計算得到的,這個計算可以在客戶端發(fā)送請求時來執(zhí)行。但是,要進(jìn)一步定位到實例,還需要知道哈希槽分布在哪個實例上。
一般來說,客戶端和集群實例建立連接后,實例就會把哈希槽的分配信息發(fā)給客戶端。但是,在集群剛剛創(chuàng)建的時候,每個實例只知道自己被分配了哪些哈希槽,是不知道其他實例擁有的哈希槽信息。
客戶端為什么可以在訪問任何一個實例時,都能獲得所有的哈希槽信息呢?
Redis 實例會把自己的哈希槽信息發(fā)給和它相連接的其它實例,來完成哈希槽分配信息的擴(kuò)散。當(dāng)實例之間相互連接后,每個實例就有所有哈希槽的映射關(guān)系了??蛻舳耸盏焦2坌畔⒑螅瑫压2坌畔⒕彺嬖诒镜?。當(dāng)客戶端請求鍵值對時,會先計算鍵所對應(yīng)的哈希槽,然后就可以給相應(yīng)的實例發(fā)送請求了。
在集群中,實例和哈希槽的對應(yīng)關(guān)系并不是一成不變的,最常見的變化有兩個:
- 在集群中,實例有新增或刪除,Redis 需要重新分配哈希槽;
- 為了負(fù)載均衡,Redis 需要把哈希槽在所有實例上重新分布一遍。
實例之間還可以通過相互傳遞消息,獲得最新的哈希槽分配信息
客戶端是無法主動感知這些變化的。這就會導(dǎo)致,它緩存的分配信息和最新的分配信息就不一致了,那該怎么辦呢?
Redis Cluster 方案提供了一種重定向機(jī)制,就是指,客戶端給一個實例發(fā)送數(shù)據(jù)讀寫操作時,這個實例上并沒有相應(yīng)的數(shù)據(jù),客戶端要再給一個新實例發(fā)送操作命令。
3.2.1MOVED 重定向命令的使用方法
客戶端怎么知道重定向時新實例訪問地址?客戶端端請求到了一個不包含 key 對應(yīng)的哈希槽,集群將做何響應(yīng)?
當(dāng)客戶端把一個鍵值對的操作請求發(fā)送給一個實例,這個實例上沒有鍵值對映射的哈希槽,這個實例就會給客戶端返回MOVED命令響應(yīng)結(jié)果,結(jié)果中就包含了新實例的訪問地址。
get hello:key (error)MOVED 13320 172.16.19.5:6379
MOVED 命令表示,客戶端請求的鍵值對所在的哈希槽 13320,實際是在 172.16.19.5 這個實例上。通過返回的 MOVED 命令,就相當(dāng)于把哈希槽所在的新實例的信息告訴給客戶端了。這樣一來,客戶端就可以直接和 172.16.19.5 連接,并發(fā)送操作請求了。
由于負(fù)載均衡,Slot 2 中的數(shù)據(jù)已經(jīng)從實例 2 遷移到了實例 3,但是,客戶端緩存仍然記錄著“Slot 2 在實例 2”的信息,所以會給實例 2 發(fā)送命令。實例 2 給客戶端返回一條 MOVED 命令,把 Slot 2 的最新位置(也就是在實例 3 上),返回給客戶端,客戶端就會再次向?qū)嵗?3 發(fā)送請求,同時還會更新本地緩存,把 Slot 2 與實例的對應(yīng)關(guān)系更新過來
3.2.2ASK命令使用方法
可能會出現(xiàn)這樣一種情況:屬于被遷移槽的一部分鍵值對保存在源節(jié)點里面,而另一部分鍵值對則保存在目標(biāo)節(jié)點里面。
例如:客戶端向?qū)嵗?2 發(fā)送請求,但此時,Slot 2 中的數(shù)據(jù)只有一部分遷移到了實例 3,還有部分?jǐn)?shù)據(jù)沒有遷移。在這種遷移部分完成的情況下,客戶端就會收到一條 ASK 報錯信息,如下所示:
Get hello:key (error)ASK 13320 172.16.19.5:6379
這個結(jié)果中的 ASK 命令就表示,客戶端請求的鍵值對所在的哈希槽 13320,在 172.16.19.5 這個實例上,但是這個哈希槽正在遷移。此時,客戶端需要先給 172.16.19.5 這個實例發(fā)送一個 ASKING 命令。這個命令的意思是,讓這個實例允許執(zhí)行客戶端接下來發(fā)送的命令。然后,客戶端再向這個實例發(fā)送 GET 命令,以讀取數(shù)據(jù).
ASK 命令表示兩層含義:第一,表明 Slot 數(shù)據(jù)還在遷移中;第二,ASK 命令把客戶端所請求數(shù)據(jù)的最新實例地址返回給客戶端,此時,客戶端需要給實例 3 發(fā)送 ASKING 命令,然后再發(fā)送操作命令
3.2.3MOVED命令和ASK命令區(qū)別
- MOVED命令會更新客戶端緩存的哈希槽分配信息,ASK不會更新客戶端緩存。如果客戶端再次請求 Slot 2 中的數(shù)據(jù),它還是會給實例 2 發(fā)送請求。
- ASK命令作用只是讓客戶端能給新實例發(fā)送一次請求,而MOVED命令修改本地緩存,讓后續(xù)命令發(fā)往新實例。
3.切片集群總結(jié)
本篇主要講述了,切片集群在保存大量數(shù)據(jù)方面的優(yōu)勢,以及基于哈希槽的數(shù)據(jù)分布機(jī)制和客戶端定位鍵值對的方法。
- 在應(yīng)對數(shù)據(jù)量大的數(shù)據(jù),數(shù)據(jù)擴(kuò)容時,雖然增加內(nèi)存這種縱向擴(kuò)展的方式簡單直接,但是會造成內(nèi)存過大,導(dǎo)致性能變慢。同事也受到硬件和成本的限制。
- Redis切片集群提供了橫向擴(kuò)展的模式,也就是使用多個實例,并給每個實例分配一定的哈希槽,數(shù)據(jù)可以通過鍵的哈希值映射到哈希槽,在通過哈希槽分散分布在不同的實例上。擴(kuò)展性好,通過增加實例可以存儲大量數(shù)據(jù)。
- 集群是實例的增減和為了實現(xiàn)負(fù)載均衡而進(jìn)行的數(shù)據(jù)重新分布,導(dǎo)致哈希槽和實例映射關(guān)系的變化,客戶端請求時,會收到命令執(zhí)行報錯信息。MOVED 和 ASK 命令,讓客戶端獲取最新信息。
- 在 Redis 3.0 之前,Redis 官方并沒有提供切片集群方案,但是,其實當(dāng)時業(yè)界已經(jīng)有了一些切片集群的方案,例如基于客戶端分區(qū)的 ShardedJedis,基于代理的 Codis、Twemproxy 等。這些方案的應(yīng)用早于 Redis Cluster 方案
Redis Cluster 方案通過哈希槽的方式把鍵值對分配到不同的實例上,這個過程需要對鍵值對的 key 做 CRC 計算,然后再和哈希槽做映射,這樣做有什么好處嗎?如果用一個表直接把鍵值對和實例的對應(yīng)關(guān)系記錄下來(例如鍵值對 1 在實例 2 上,鍵值對 2 在實例 1 上),這樣就不用計算 key 和哈希槽的對應(yīng)關(guān)系了,只用查表就行了,Redis 為什么不這么做呢?
1、整個集群存儲key的數(shù)量是無法預(yù)估的,key的數(shù)量非常多時,直接記錄每個key對應(yīng)的實例映射關(guān)系,這個映射表會非常龐大,這個映射表無論是存儲在服務(wù)端還是客戶端都占用了非常大的內(nèi)存空間。
2、Redis Cluster采用無中心化的模式(無proxy,客戶端與服務(wù)端直連),客戶端在某個節(jié)點訪問一個key,如果這個key不在這個節(jié)點上,這個節(jié)點需要有糾正客戶端路由到正確節(jié)點的能力(MOVED響應(yīng)),這就需要節(jié)點之間互相交換路由表,每個節(jié)點擁有整個集群完整的路由關(guān)系。如果存儲的都是key與實例的對應(yīng)關(guān)系,節(jié)點之間交換信息也會變得非常龐大,消耗過多的網(wǎng)絡(luò)資源,而且就算交換完成,相當(dāng)于每個節(jié)點都需要額外存儲其他節(jié)點的路由表,內(nèi)存占用過大造成資源浪費。
3、當(dāng)集群在擴(kuò)容、縮容、數(shù)據(jù)均衡時,節(jié)點之間會發(fā)生數(shù)據(jù)遷移,遷移時需要修改每個key的映射關(guān)系,維護(hù)成本高。
4、而在中間增加一層哈希槽,可以把數(shù)據(jù)和節(jié)點解耦,key通過Hash計算,只需要關(guān)心映射到了哪個哈希槽,然后再通過哈希槽和節(jié)點的映射表找到節(jié)點,相當(dāng)于消耗了很少的CPU資源,不但讓數(shù)據(jù)分布更均勻,還可以讓這個映射表變得很小,利于客戶端和服務(wù)端保存,節(jié)點之間交換信息時也變得輕量。
5、當(dāng)集群在擴(kuò)容、縮容、數(shù)據(jù)均衡時,節(jié)點之間的操作例如數(shù)據(jù)遷移,都以哈希槽為基本單位進(jìn)行操作,簡化了節(jié)點擴(kuò)容、縮容的難度,便于集群的維護(hù)和管理。
請求路由、數(shù)據(jù)遷移
Redis使用集群方案就是為了解決單個節(jié)點數(shù)據(jù)量大、寫入量大產(chǎn)生的性能瓶頸的問題。多個節(jié)點組成一個集群,可以提高集群的性能和可靠性,但隨之而來的就是集群的管理問題,最核心問題有2個:請求路由、數(shù)據(jù)遷移(擴(kuò)容/縮容/數(shù)據(jù)平衡)。
1、請求路由:一般都是采用哈希槽的映射關(guān)系表找到指定節(jié)點,然后在這個節(jié)點上操作的方案。
Redis Cluster在每個節(jié)點記錄完整的映射關(guān)系(便于糾正客戶端的錯誤路由請求),同時也發(fā)給客戶端讓客戶端緩存一份,便于客戶端直接找到指定節(jié)點,客戶端與服務(wù)端配合完成數(shù)據(jù)的路由,這需要業(yè)務(wù)在使用Redis Cluster時,必須升級為集群版的SDK才支持客戶端和服務(wù)端的協(xié)議交互。
其他Redis集群化方案例如Twemproxy、Codis都是中心化模式(增加Proxy層),客戶端通過Proxy對整個集群進(jìn)行操作,Proxy后面可以掛N多個Redis實例,Proxy層維護(hù)了路由的轉(zhuǎn)發(fā)邏輯。操作Proxy就像是操作一個普通Redis一樣,客戶端也不需要更換SDK,而Redis Cluster是把這些路由邏輯做在了SDK中。當(dāng)然,增加一層Proxy也會帶來一定的性能損耗。
2、數(shù)據(jù)遷移:當(dāng)集群節(jié)點不足以支撐業(yè)務(wù)需求時,就需要擴(kuò)容節(jié)點,擴(kuò)容就意味著節(jié)點之間的數(shù)據(jù)需要做遷移,而遷移過程中是否會影響到業(yè)務(wù),這也是判定一個集群方案是否成熟的標(biāo)準(zhǔn)。
Twemproxy不支持在線擴(kuò)容,它只解決了請求路由的問題,擴(kuò)容時需要停機(jī)做數(shù)據(jù)重新分配。而Redis Cluster和Codis都做到了在線擴(kuò)容(不影響業(yè)務(wù)或?qū)I(yè)務(wù)的影響非常?。?,重點就是在數(shù)據(jù)遷移過程中,客戶端對于正在遷移的key進(jìn)行操作時,集群如何處理?還要保證響應(yīng)正確的結(jié)果?
Redis Cluster和Codis都需要服務(wù)端和客戶端/Proxy層互相配合,遷移過程中,服務(wù)端針對正在遷移的key,需要讓客戶端或Proxy去新節(jié)點訪問(重定向),這個過程就是為了保證業(yè)務(wù)在訪問這些key時依舊不受影響,而且可以得到正確的結(jié)果。由于重定向的存在,所以這個期間的訪問延遲會變大。等遷移完成之后,Redis Cluster每個節(jié)點會更新路由映射表,同時也會讓客戶端感知到,更新客戶端緩存。Codis會在Proxy層更新路由表,客戶端在整個過程中無感知。
除了訪問正確的節(jié)點之外,數(shù)據(jù)遷移過程中還需要解決異常情況(遷移超時、遷移失?。?、性能問題(如何讓數(shù)據(jù)遷移更快、bigkey如何處理),這個過程中的細(xì)節(jié)也很多。
Redis Cluster的數(shù)據(jù)遷移是同步的,遷移一個key會同時阻塞源節(jié)點和目標(biāo)節(jié)點,遷移過程中會有性能問題。而Codis提供了異步遷移數(shù)據(jù)的方案,遷移速度更快,對性能影響最小,當(dāng)然,實現(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ù)據(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ù)時,客戶端直接和 codis proxy 建立連接.codis proxy 本身支持 Redis 的 RESP 交互協(xié)議.客戶端訪問 codis proxy 時,和訪問原生的 Redis 實例沒有什么區(qū)別。原本連接單實例的客戶端就可以輕松地和 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 集群中,一個數(shù)據(jù)應(yīng)該保存在哪個 codis server 上,這是通過邏輯槽(Slot)映射來完成的,具體來說,總共分成兩步。
第一步,Codis 集群一共有 1024 個 Slot,編號依次是 0 到 1023。把這些 Slot 手動分配給 codis server,每個 server 上包含一部分 Slot。也可以讓 codis dashboard 進(jìn)行自動分配,例如,dashboard 把 1024 個 Slot 在所有 server 上均分。
第二步,當(dāng)客戶端要讀寫數(shù)據(jù)時,會使用 CRC32 算法計算數(shù)據(jù) key 的哈希值,并把這個哈希值對 1024 取模。而取模后的值,則對應(yīng) Slot 的編號。此時,根據(jù)第一步分配的 Slot 和 server 對應(yīng)關(guān)系,可以知道數(shù)據(jù)保存在哪個 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ù)據(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 計算得到的,而 Slot 和 codis server 的映射關(guān)系是通過分配完成的,所以就需要用一個存儲系統(tǒng)保存下來,否則,如果集群有故障了,映射關(guān)系就會丟失。
把 Slot 和 codis server 的映射關(guān)系稱為數(shù)據(jù)路由表(簡稱路由表)。我們在 codis dashboard 上分配好路由表后,dashboard 會把路由表發(fā)送給 codis proxy,同時,dashboard 也會把路由表保存在 Zookeeper 中。codis-proxy 會把路由表緩存在本地,當(dāng)它接收到客戶端請求后,直接查詢本地的路由表,就可以完成正確的請求轉(zhuǎn)發(fā)了
在數(shù)據(jù)分布的實現(xiàn)方法上,Codis 和 Redis Cluster 很相似,都采用了 key 映射到 Slot、Slot 再分配到實例上的機(jī)制
2.3Codis 和 Redis Cluster數(shù)據(jù)分布上的區(qū)別
- Codis 中的路由表:通過 codis dashboard 分配和修改的,并被保存在 Zookeeper 集群中。一旦數(shù)據(jù)位置發(fā)生變化(例如有實例增減),路由表被修改了,codis dashbaord 就會把修改后的路由表發(fā)送給 codis proxy,proxy 就可以根據(jù)最新的路由信息轉(zhuǎn)發(fā)請求了。
- 在 Redis Cluster 中,數(shù)據(jù)路由表是通過每個實例相互間的通信傳遞的,最后會在每個實例上保存一份。當(dāng)數(shù)據(jù)路由信息發(fā)生變化時,就需要在所有實例間通過網(wǎng)絡(luò)消息進(jìn)行傳遞。所以,如果實例數(shù)量較多的話,就會消耗較多的集群網(wǎng)絡(luò)資源。
3.集群擴(kuò)容和數(shù)據(jù)遷移
Codis 集群擴(kuò)容包括了兩方面:增加 codis server 和增加 codis proxy。
3.1增加codis server
兩步操作:
- 啟動新的 codis server,將它加入集群;
- 把部分?jǐn)?shù)據(jù)遷移到新的 server。
3.1.1數(shù)據(jù)遷移的基本流程
Codis 集群按照 Slot 的粒度進(jìn)行數(shù)據(jù)遷移,數(shù)據(jù)遷移是一個重要的機(jī)制
- 在源 server 上,Codis 從要遷移的 Slot 中隨機(jī)選擇一個數(shù)據(jù),發(fā)送給目的 server。
- 目的 server 確認(rèn)收到數(shù)據(jù)后,會給源 server 返回確認(rèn)消息。這時,源 server 會在本地將剛才遷移的數(shù)據(jù)刪除。
- 第一步和第二步就是單個數(shù)據(jù)的遷移過程。Codis 會不斷重復(fù)這個遷移過程,直到要遷移的 Slot 中的數(shù)據(jù)全部遷移完成。
Codis 實現(xiàn)了兩種遷移模式,分別是同步遷移和異步遷移
3.1.2同步遷移
同步遷移是指,在數(shù)據(jù)從源 server 發(fā)送給目的 server 的過程中,源 server 是阻塞的,無法處理新的請求操作。這種模式很容易實現(xiàn),但是遷移過程中會涉及多個操作(包括數(shù)據(jù)在源 server 序列化、網(wǎng)絡(luò)傳輸、在目的 server 反序列化,以及在源 server 刪除),如果遷移的數(shù)據(jù)是一個 bigkey,源 server 就會阻塞較長時間,無法及時處理用戶請求。
3.1.3異步遷移
為了避免數(shù)據(jù)遷移阻塞源 server,Codis 實現(xiàn)的第二種遷移模式就是異步遷移
異步遷移的兩個關(guān)鍵特點
第一個特點是:
- 當(dāng)源 server 把數(shù)據(jù)發(fā)送給目的 server 后,就可以處理其他請求操作了,不用等到目的 server 的命令執(zhí)行完。而目的 server 會在收到數(shù)據(jù)并反序列化保存到本地后,給源 server 發(fā)送一個 ACK 消息,表明遷移完成。此時,源 server 在本地把剛才遷移的數(shù)據(jù)刪除。
- 在這個過程中,遷移的數(shù)據(jù)會被設(shè)置為只讀,所以,源 server 上的數(shù)據(jù)不會被修改,自然也就不會出現(xiàn)“和目的 server 上的數(shù)據(jù)不一致”的問題了
第二個特點是:
- 于 bigkey,異步遷移采用了拆分指令的方式進(jìn)行遷移。具體來說就是,對 bigkey 中每個元素,用一條指令進(jìn)行遷移,而不是把整個 bigkey 進(jìn)行序列化后再整體傳輸。這種化整為零的方式,就避免了 bigkey 遷移時,因為要序列化大量數(shù)據(jù)而阻塞源 server 的問題。
- 當(dāng) bigkey 遷移了一部分?jǐn)?shù)據(jù)后,如果 Codis 發(fā)生故障,就會導(dǎo)致 bigkey 的一部分元素在源 server,而另一部分元素在目的 server,這就破壞了遷移的原子性。所以,Codis 會在目標(biāo) server 上,給 bigkey 的元素設(shè)置一個臨時過期時間。如果遷移過程中發(fā)生故障,那么,目標(biāo) server 上的 key 會在過期后被刪除,不會影響遷移的原子性。當(dāng)正常完成遷移后,bigkey 元素的臨時過期時間會被刪除。
第二個特點例子:
- 假如要遷移一個有 1 萬個元素的 List 類型數(shù)據(jù),當(dāng)使用異步遷移時,源 server 就會給目的 server 傳輸 1 萬條 RPUSH 命令,每條命令對應(yīng)了 List 中一個元素的插入。在目的 server 上,這 1 萬條命令再被依次執(zhí)行,就可以完成數(shù)據(jù)遷移。
- 為了提升遷移的效率,Codis 在異步遷移 Slot 時,允許每次遷移多個 key。可以通過異步遷移命令 SLOTSMGRTTAGSLOT-ASYNC 的參數(shù) numkeys 設(shè)置每次遷移的 key 數(shù)量
3.2增加codis proxy
Codis 集群中,客戶端是和 codis proxy 直接連接的,所以,當(dāng)客戶端增加時,一個 proxy 無法支撐大量的請求操作,就需要增加 proxy。增加 proxy 比較容易,直接啟動 proxy,再通過 codis dashboard 把 proxy 加入集群就行。
此時,codis proxy 的訪問連接信息都會保存在 Zookeeper 上。所以,當(dāng)新增了 proxy 后,Zookeeper 上會有最新的訪問列表,客戶端也就可以從 Zookeeper 上讀取 proxy 訪問列表,把請求發(fā)送給新增的 proxy。這樣一來,客戶端的訪問壓力就可以在多個 proxy 上分擔(dān)處理了,如下圖所示
4:客戶端能否與集群直接交互
使用 Redis 單實例時,客戶端只要符合 RESP 協(xié)議,就可以和實例進(jìn)行交互和讀寫數(shù)據(jù)。但是,在使用切片集群時,有些功能是和單實例不一樣的,比如集群中的數(shù)據(jù)遷移操作,在單實例上是沒有的,而且遷移過程中,數(shù)據(jù)訪問請求可能要被重定向(例如 Redis Cluster 中的 MOVE 命令)。
客戶端需要增加和集群功能相關(guān)的命令操作的支持。如果原來使用單實例客戶端,想要擴(kuò)容使用集群,就需要使用新客戶端,這對于業(yè)務(wù)應(yīng)用的兼容性來說,并不是特別友好。
Codis 集群在設(shè)計時,就充分考慮了對現(xiàn)有單實例客戶端的兼容性。
Codis 使用 codis proxy 直接和客戶端連接,codis proxy 是和單實例客戶端兼容的。而和集群相關(guān)的管理工作(例如請求轉(zhuǎn)發(fā)、數(shù)據(jù)遷移等),都由 codis proxy、codis dashboard 這些組件來完成,不需要客戶端參與。
業(yè)務(wù)應(yīng)用使用 Codis 集群時,就不用修改客戶端了,可以復(fù)用和單實例連接的客戶端,既能利用集群讀寫大容量數(shù)據(jù),又避免了修改客戶端增加復(fù)雜的操作邏輯,保證了業(yè)務(wù)代碼的穩(wěn)定性和兼容性。
5.怎么保證集群可靠性?
可靠性是實際業(yè)務(wù)應(yīng)用的一個核心要求。對于一個分布式系統(tǒng)來說,它的可靠性和系統(tǒng)中的組件個數(shù)有關(guān):組件越多,潛在的風(fēng)險點也就越多。
和 Redis Cluster 只包含 Redis 實例不一樣,Codis 集群包含的組件有 4 類
Codis 不同組件的可靠性保證方法。
5.1codis server保證可靠性方法
- codis server 其實就是 Redis 實例,只不過增加了和集群操作相關(guān)的命令。Redis 的主從復(fù)制機(jī)制和哨兵機(jī)制在 codis server 上都是可以使用的,所以,Codis 就使用主從集群來保證 codis server 的可靠性。簡單來說就是,Codis 給每個 server 配置從庫,并使用哨兵機(jī)制進(jìn)行監(jiān)控,當(dāng)發(fā)生故障時,主從庫可以進(jìn)行切換,從而保證了 server 的可靠性。
- 在這種配置情況下,每個 server 就成為了一個 server group,每個 group 中是一主多從的 server。數(shù)據(jù)分布使用的 Slot,也是按照 group 的粒度進(jìn)行分配的。同時,codis proxy 在轉(zhuǎn)發(fā)請求時,也是按照數(shù)據(jù)所在的 Slot 和 group 的對應(yīng)關(guān)系,把寫請求發(fā)到相應(yīng) group 的主庫,讀請求發(fā)到 group 中的主庫或從庫上。
下圖展示的是配置了 server group 的 Codis 集群架構(gòu)。在 Codis 集群中,我們通過部署 server group 和哨兵集群,實現(xiàn) codis server 的主從切換,提升集群可靠性。
5.2codis proxy 和 Zookeeper可靠性
- 在 Codis 集群設(shè)計時,proxy 上的信息源頭都是來自 Zookeeper(例如路由表)。而 Zookeeper 集群使用多個實例來保存數(shù)據(jù),只要有超過半數(shù)的 Zookeeper 實例可以正常工作, 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è)計,也降低了 Codis 集群本身的開發(fā)復(fù)雜度。
5.3codis dashboard 和 codis fe可靠性
它們主要提供配置管理和管理員手工操作,負(fù)載壓力不大,所以,它們的可靠性可以不用額外進(jìn)行保證了
6.切片集群方案選擇建議
6.1Codis 和 Redis Cluster 區(qū)別
6.2實際應(yīng)用中的兩種方案
- 從穩(wěn)定性和成熟度來看,Codis 應(yīng)用得比較早,在業(yè)界已經(jīng)有了成熟的生產(chǎn)部署。雖然 Codis 引入了 proxy 和 Zookeeper,增加了集群復(fù)雜度,但是,proxy 的無狀態(tài)設(shè)計和 Zookeeper 自身的穩(wěn)定性,也給 Codis 的穩(wěn)定使用提供了保證。而 Redis Cluster 的推出時間晚于 Codis,相對來說,成熟度要弱于 Codis,如果你想選擇一個成熟穩(wěn)定的方案,Codis 更加合適些。
- 從業(yè)務(wù)應(yīng)用客戶端兼容性來看,連接單實例的客戶端可以直接連接 codis proxy,而原本連接單實例的客戶端要想連接 Redis Cluster 的話,就需要開發(fā)新功能。所以,如果你的業(yè)務(wù)應(yīng)用中大量使用了單實例的客戶端,而現(xiàn)在想應(yīng)用切片集群的話,建議你選擇 Codis,這樣可以避免修改業(yè)務(wù)應(yīng)用中的客戶端。
- 從使用 Redis 新命令和新特性來看,Codis server 是基于開源的 Redis 3.2.8 開發(fā)的,所以,Codis 并不支持 Redis 后續(xù)的開源版本中的新增命令和數(shù)據(jù)類型。另外,Codis 并沒有實現(xiàn)開源 Redis 版本的所有命令,比如 BITOP、BLPOP、BRPOP,以及和與事務(wù)相關(guān)的 MUTLI、EXEC 等命令。Codis 官網(wǎng)上列出了不被支持的命令列表,你在使用時記得去核查一下。所以,如果你想使用開源 Redis 版本的新特性,Redis Cluster 是一個合適的選擇。
- 從數(shù)據(jù)遷移性能維度來看,Codis 能支持異步遷移,異步遷移對集群處理正常請求的性能影響要比使用同步遷移的小。所以,如果你在應(yīng)用集群時,數(shù)據(jù)遷移比較頻繁的話,Codis 是個更合適的選擇。
7.Codis 和 Redis Cluster總結(jié)
Codis 集群包含 codis server、codis proxy、Zookeeper、codis dashboard 和 codis fe 這四大類組件。
- codis proxy 和 codis server 負(fù)責(zé)處理數(shù)據(jù)讀寫請求,其中,codis proxy 和客戶端連接,接收請求,并轉(zhuǎn)發(fā)請求給 codis server,而 codis server 負(fù)責(zé)具體處理請求。
- codis dashboard 和 codis fe 負(fù)責(zé)集群管理,其中,codis dashboard 執(zhí)行管理操作,而 codis fe 提供 Web 管理界面。
- Zookeeper 集群負(fù)責(zé)保存集群的所有元數(shù)據(jù)信息,包括路由表、proxy 實例信息等。這里,有個地方需要你注意,除了使用 Zookeeper,Codis 還可以使用 etcd 或本地文件系統(tǒng)保存元數(shù)據(jù)信息。
Codis 使用上的小建議:當(dāng)你有多條業(yè)務(wù)線要使用 Codis 時,可以啟動多個 codis dashboard,每個 dashboard 管理一部分 codis server,同時,再用一個 dashboard 對應(yīng)負(fù)責(zé)一個業(yè)務(wù)線的集群管理,這樣,就可以做到用一個 Codis 集群實現(xiàn)多條業(yè)務(wù)線的隔離管理了。
假設(shè) Codis 集群中保存的 80% 的鍵值對都是 Hash 類型,每個 Hash 集合的元素數(shù)量在 10 萬~20 萬個,每個集合元素的大小是 2KB。你覺得,遷移一個這樣的 Hash 集合數(shù)據(jù),會對 Codis 的性能造成影響嗎?
Codis 在遷移數(shù)據(jù)時,設(shè)計的方案可以保證遷移性能不受影響。
- 1、異步遷移:源節(jié)點把遷移的數(shù)據(jù)發(fā)送給目標(biāo)節(jié)點后就返回,之后接著處理客戶端請求,這個階段不會長時間阻塞源節(jié)點。目標(biāo)節(jié)點加載遷移的數(shù)據(jù)成功后,向源節(jié)點發(fā)送 ACK 命令,告知其遷移成功。
- 2、源節(jié)點異步釋放 key:源節(jié)點收到目標(biāo)節(jié)點 ACK 后,在源實例刪除這個 key,釋放 key 內(nèi)存的操作,會放到后臺線程中執(zhí)行,不會阻塞源實例。(沒錯,Codis 比 Redis 更早地支持了 lazy-free,只不過只用在了數(shù)據(jù)遷移中)。
- 3、小對象序列化傳輸:小對象依舊采用序列化方式遷移,節(jié)省網(wǎng)絡(luò)流量。
- 4、bigkey 分批遷移:bigkey 拆分成一條條命令,打包分批遷移(利用了 Pipeline 的優(yōu)勢),提升遷移速度。
- 5、一次遷移多個 key:一次發(fā)送多個 key 進(jìn)行遷移,提升遷移效率。
- 6、遷移流量控制:遷移時會控制緩沖區(qū)大小,避免占滿網(wǎng)絡(luò)帶寬。
- 7、bigkey 遷移原子性保證(兼容遷移失敗情況):遷移前先發(fā)一個 DEL 命令到目標(biāo)節(jié)點(重試可保證冪等性),然后把 bigkey 拆分成一條條命令,并設(shè)置一個臨時過期時間(防止遷移失敗在目標(biāo)節(jié)點遺留垃圾數(shù)據(jù)),遷移成功后在目標(biāo)節(jié)點設(shè)置真實的過期時間。 Codis 在數(shù)據(jù)遷移方面要比 Redis Cluster 做得更優(yōu)秀,而且 Codis 還帶了一個非常友好的運(yùn)維界面,方便 DBA 執(zhí)行增刪節(jié)點、主從切換、數(shù)據(jù)遷移等操作。
三.通信開銷:限制Redis Cluster規(guī)模的關(guān)鍵因素
1:為什么要限定集群規(guī)模呢?
Redis Cluster 能保存的數(shù)據(jù)量以及支撐的吞吐量,跟集群的實例規(guī)模密切相關(guān)。Redis 官方給出了 Redis Cluster 的規(guī)模上限,就是一個集群運(yùn)行 1000 個實例
這里的一個關(guān)鍵因素就是,實例間的通信開銷會隨著實例規(guī)模增加而增大,在集群超過一定規(guī)模時(比如 800 節(jié)點),集群吞吐量反而會下降。所以,集群的實際規(guī)模會受到限制。
2:實例通信方法和對集群規(guī)模的影響
Redis Cluster 在運(yùn)行時,每個實例上都會保存 Slot 和實例的對應(yīng)關(guān)系(也就是 Slot 映射表),以及自身的狀態(tài)信息。
為了讓集群中的每個實例都知道其它所有實例的狀態(tài)信息,實例之間會按照一定的規(guī)則進(jìn)行通信。這個規(guī)則就是 Gossip 協(xié)議
2.1Gossip 協(xié)議
Gossip 協(xié)議的工作原理可以概括成兩點。:檢測實例時候在線\給發(fā)送PING命令實例返回PONG消息
- 一是,每個實例之間會按照一定的頻率,從集群中隨機(jī)挑選一些實例,把 PING 消息發(fā)送給挑選出來的實例,用來檢測這些實例是否在線,并交換彼此的狀態(tài)信息。PING 消息中封裝了發(fā)送消息的實例自身的狀態(tài)信息、部分其它實例的狀態(tài)信息,以及 Slot 映射表。
- 二是,一個實例在接收到 PING 消息后,會給發(fā)送 PING 消息的實例,發(fā)送一個 PONG 消息。PONG 消息包含的內(nèi)容和 PING 消息一樣。
Gossip 協(xié)議可以保證在一段時間后,集群中的每一個實例都能獲得其它所有實例的狀態(tài)信息。
這樣一來,即使有新節(jié)點加入、節(jié)點故障、Slot 變更等事件發(fā)生,實例間也可以通過 PING、PONG 消息的傳遞,完成集群狀態(tài)在每個實例上的同步
3.通信的影響
實例間使用 Gossip 協(xié)議進(jìn)行通信時,通信開銷受到通信消息大小和通信頻率這兩方面的影響。消息越大、頻率越高,相應(yīng)的通信開銷也就越大。如果想要實現(xiàn)高效的通信,可以從這兩方面入手去調(diào)優(yōu)
3.1Gossip 消息大小
Redis 實例發(fā)送的 PING 消息的消息體是由 clusterMsgDataGossip 結(jié)構(gòu)體組成的,這個結(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 這兩個字節(jié)數(shù)組的長度是 40 字節(jié)和 46 字節(jié),我們再把結(jié)構(gòu)體中其它信息的大小加起來,就可以得到一個 Gossip 消息的大小了,即 104 字節(jié)。
每個實例在發(fā)送一個 Gossip 消息時,除了會傳遞自身的狀態(tài)信息,默認(rèn)還會傳遞集群十分之一實例的狀態(tài)信息。
例子:
所以,對于一個包含了 1000 個實例的集群來說,每個實例發(fā)送一個 PING 消息時,會包含 100 個實例的狀態(tài)信息,總的數(shù)據(jù)量是 10400 字節(jié),再加上發(fā)送實例自身的信息,一個 Gossip 消息大約是 10KB。為了讓 Slot 映射表能夠在不同實例間傳播,PING 消息中還帶有一個長度為 16,384 bit 的 Bitmap,這個 Bitmap 的每一位對應(yīng)了一個 Slot,如果某一位為 1,就表示這個 Slot 屬于當(dāng)前實例。這個 Bitmap 大小換算成字節(jié)后,是 2KB實例狀態(tài)信息和 Slot 分配信息相加,就可以得到一個 PING 消息的大小了,大約是 12KB。
PONG 消息和 PING 消息的內(nèi)容一樣,它的大小大約是 12KB。每個實例發(fā)送了 PING 消息后,還會收到返回的 PONG 消息,兩個消息加起來有 24KB。
從絕對值上來看,24KB 并不算很大,但是,如果實例正常處理的單個請求只有幾 KB 的話,那么,實例為了維護(hù)集群狀態(tài)一致傳輸?shù)?PING/PONG 消息,就要比單個業(yè)務(wù)請求大了。而且,每個實例都會給其它實例發(fā)送 PING/PONG 消息。隨著集群規(guī)模增加,這些心跳消息的數(shù)量也會越多,會占據(jù)一部分集群的網(wǎng)絡(luò)通信帶寬,進(jìn)而會降低集群服務(wù)正常客戶端請求的吞吐量。
3.2實例間通信頻率
Redis Cluster 的實例啟動后,默認(rèn)會每秒從本地的實例列表中隨機(jī)選出 5 個實例,再從這 5 個實例中找出一個最久沒有通信的實例,把 PING 消息發(fā)送給該實例。這是實例周期性發(fā)送 PING 消息的基本做法
這里有一個問題:實例選出來的這個最久沒有通信的實例,畢竟是從隨機(jī)選出的 5 個實例中挑選的,這并不能保證這個實例就一定是整個集群中最久沒有通信的實例。可能會出現(xiàn),有些實例一直沒有被發(fā)送 PING 消息,導(dǎo)致它們維護(hù)的集群狀態(tài)已經(jīng)過期。
為了避免這種情況,Redis Cluster 的實例會按照每 100ms 一次的頻率,掃描本地的實例列表,如果發(fā)現(xiàn)有實例最近一次接收 PONG 消息的時間,已經(jīng)大于配置項 cluster-node-timeout 的一半了(cluster-node-timeout/2),就會立刻給該實例發(fā)送 PING 消息,更新這個實例上的集群狀態(tài)信息
當(dāng)集群規(guī)模擴(kuò)大之后,因為網(wǎng)絡(luò)擁塞或是不同服務(wù)器間的流量競爭,會導(dǎo)致實例間的網(wǎng)絡(luò)通信延遲增加。如果有部分實例無法收到其它實例發(fā)送的 PONG 消息,就會引起實例之間頻繁地發(fā)送 PING 消息,這又會對集群網(wǎng)絡(luò)通信帶來額外的開銷
總結(jié)下單實例每秒會發(fā)送的 PING 消息數(shù)量,如下所示:
PING 消息發(fā)送數(shù)量 = 1 + 10 * 實例數(shù)(最近一次接收 PONG 消息的時間超出 cluster-node-timeout/2)
其中,1 是指單實例常規(guī)按照每 1 秒發(fā)送一個 PING 消息,10 是指每 1 秒內(nèi)實例會執(zhí)行 10 次檢查,每次檢查后會給 PONG 消息超時的實例發(fā)送消息。
例子:
假設(shè)單個實例檢測發(fā)現(xiàn),每 100 毫秒有 10 個實例的 PONG 消息接收超時,那么,這個實例每秒就會發(fā)送 101 個 PING 消息,約占 1.2MB/s 帶寬。如果集群中有 30 個實例按照這種頻率發(fā)送消息,就會占用 36MB/s 帶寬,這就會擠占集群中用于服務(wù)正常請求的帶寬
4.如何降低實例間的通信開銷?
4.1減小實例傳輸?shù)南⒋笮?/h4>
為了降低實例間的通信開銷,從原理上說,可以減小實例傳輸?shù)南⒋笮。≒ING/PONG 消息、Slot 分配信息),但是,因為集群實例依賴 PING、PONG 消息和 Slot 分配信息,來維持集群狀態(tài)的統(tǒng)一,一旦減小了傳遞的消息大小,就會導(dǎo)致實例間的通信信息減少,不利于集群維護(hù),所以,減小實例傳輸?shù)南⒋笮〔荒懿捎眠@種方式。
4.2降低實例間發(fā)送消息的頻率:
實例間發(fā)送消息的頻率有兩個。
- 每個實例每 1 秒發(fā)送一條 PING 消息。這個頻率不算高,如果再降低該頻率的話,集群中各實例的狀態(tài)可能就沒辦法及時傳播了。
- 每個實例每 100 毫秒會做一次檢測,給 PONG 消息接收超過 cluster-node-timeout/2 的節(jié)點發(fā)送 PING 消息。實例按照每 100 毫秒進(jìn)行檢測的頻率,是 Redis 實例默認(rèn)的周期性檢查任務(wù)的統(tǒng)一頻率,我們一般不需要修改它。
就只有 cluster-node-timeout 這個配置項可以修改
配置項 cluster-node-timeout 定義了集群實例被判斷為故障的心跳超時時間,默認(rèn)是 15 秒。如果 cluster-node-timeout 值比較小,那么,在大規(guī)模集群中,就會比較頻繁地出現(xiàn) PONG 消息接收超時的情況,從而導(dǎo)致實例每秒要執(zhí)行 10 次“給 PONG 消息超時的實例發(fā)送 PING 消息”這個操作
所以,為了避免過多的心跳消息擠占集群帶寬,可以調(diào)大 cluster-node-timeout 值,比如說調(diào)大到 20 秒或 25 秒。這樣一來, PONG 消息接收超時的情況就會有所緩解,單實例也不用頻繁地每秒執(zhí)行 10 次心跳發(fā)送操作了。
也不要把 cluster-node-timeout 調(diào)得太大,否則,如果實例真的發(fā)生了故障,需要等待 cluster-node-timeout 時長后,才能檢測出這個故障,這又會導(dǎo)致實際的故障恢復(fù)時間被延長,會影響到集群服務(wù)的正常使用
為了驗證調(diào)整 cluster-node-timeout 值后,是否能減少心跳消息占用的集群網(wǎng)絡(luò)帶寬,建議:可以在調(diào)整 cluster-node-timeout 值的前后,使用 tcpdump 命令抓取實例發(fā)送心跳信息網(wǎng)絡(luò)包的情況。
執(zhí)行下面的命令后,可以抓取到 192.168.10.3 機(jī)器上的實例從 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 實例間以 Gossip 協(xié)議進(jìn)行通信的機(jī)制。Redis Cluster 運(yùn)行時,各實例間需要通過 PING、PONG 消息進(jìn)行信息交換,這些心跳消息包含了當(dāng)前實例和部分其它實例的狀態(tài)信息,以及 Slot 分配信息。這種通信機(jī)制有助于 Redis Cluster 中的所有實例都擁有完整的集群狀態(tài)信息。
但是,隨著集群規(guī)模的增加,實例間的通信量也會增加。如果盲目地對 Redis Cluster 進(jìn)行擴(kuò)容,就可能會遇到集群性能變慢的情況。這是因為,集群中大規(guī)模的實例間心跳消息會擠占集群處理正常請求的帶寬。而且,有些實例可能因為網(wǎng)絡(luò)擁塞導(dǎo)致無法及時收到 PONG 消息,每個實例在運(yùn)行時會周期性地(每秒 10 次)檢測是否有這種情況發(fā)生,一旦發(fā)生,就會立即給這些 PONG 消息超時的實例發(fā)送心跳消息。
集群規(guī)模越大,網(wǎng)絡(luò)擁塞的概率就越高,相應(yīng)的,PONG 消息超時的發(fā)生概率就越高,這就會導(dǎo)致集群中有大量的心跳消息,影響集群服務(wù)正常請求??梢酝ㄟ^調(diào)整 cluster-node-timeout 配置項減少心跳消息的占用帶寬情況,但是,在實際應(yīng)用中,如果不是特別需要大容量集群,建議把 Redis Cluster 的規(guī)模控制在 400~500 個實例。
設(shè)單個實例每秒能支撐 8 萬請求操作(8 萬 QPS),每個主實例配置 1 個從實例,那么,400~ 500 個實例可支持 1600 萬~2000 萬 QPS(200/250 個主實例 *8 萬 QPS=1600/2000 萬 QPS),這個吞吐量性能可以滿足不少業(yè)務(wù)應(yīng)用的需求
如果我們采用跟 Codis 保存 Slot 分配信息相類似的方法,把集群實例狀態(tài)信息和 Slot 分配信息保存在第三方的存儲系統(tǒng)上(例如 Zookeeper),這種方法會對集群規(guī)模產(chǎn)生什么影響嗎?
答案:假設(shè)我們將 Zookeeper 作為第三方存儲系統(tǒng),保存集群實例狀態(tài)信息和 Slot 分配信息,那么,實例只需要和 Zookeeper 通信交互信息,實例之間就不需要發(fā)送大量的心跳消息來同步集群狀態(tài)了。這種做法可以減少實例之間用于心跳的網(wǎng)絡(luò)通信量,有助于實現(xiàn)大規(guī)模集群。
而且,網(wǎng)絡(luò)帶寬可以集中用在服務(wù)客戶端請求上。不過,在這種情況下,實例獲取或更新集群狀態(tài)信息時,都需要和 Zookeeper 交互,Zookeeper 的網(wǎng)絡(luò)通信帶寬需求會增加。所以,采用這種方法的時候,需要給 Zookeeper 保證一定的網(wǎng)絡(luò)帶寬,避免 Zookeeper 受限于帶寬而無法和實例快速通信。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Redisson如何解決redis分布式鎖過期時間到了業(yè)務(wù)沒執(zhí)行完問題
這篇文章主要介紹了Redisson如何解決redis分布式鎖過期時間到了業(yè)務(wù)沒執(zhí)行完問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01Redis中Redisson布隆過濾器的學(xué)習(xí)
布隆過濾器是一個非常長的二進(jìn)制向量和一系列隨機(jī)哈希函數(shù)的組合,可用于檢索一個元素是否存在,本文就詳細(xì)的介紹一下Redisson布隆過濾器,具有一定的參考價值,感興趣的可以了解一下2022-05-05Redis 安裝 redistimeseries.so(時間序列數(shù)據(jù)類型)的配置步驟
這篇文章主要介紹了Redis 安裝 redistimeseries.so(時間序列數(shù)據(jù)類型)詳細(xì)教程,配置步驟需要先下載redistimeseries.so 文件,文中介紹了啟動失敗問題排查,需要的朋友可以參考下2024-01-01Redis?數(shù)據(jù)恢復(fù)及持久化策略分析
本文將詳細(xì)分析Redis的數(shù)據(jù)恢復(fù)機(jī)制,持久化策略及其特點,并討論選擇持久化策略時需要考慮的因素,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06Redis全文搜索教程之創(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項目中Session的詳細(xì)步驟
在開發(fā)Spring Boot項目時,我們通常會遇到如何高效管理Session的問題,默認(rèn)情況下,Spring Boot會將Session存儲在內(nèi)存中,今天,我們將學(xué)習(xí)如何將Session存儲從內(nèi)存切換到Redis,并驗證配置是否成功,需要的朋友可以參考下2024-06-06