我曾遇到過(guò)這么一個(gè)需求:要用 Redis 保存 5000 萬(wàn)個(gè)鍵值對(duì),每個(gè)鍵值對(duì)大約是 512B,為了能快速部署并對(duì)外提供服務(wù),我們采用云主機(jī)來(lái)運(yùn)行 Redis 實(shí)例,那么,該如何選擇云主機(jī)的內(nèi)存容量呢?
我粗略地計(jì)算了一下,這些鍵值對(duì)所占的內(nèi)存空間大約是 25GB(5000 萬(wàn) *512B)。所以,當(dāng)時(shí),我想到的第一個(gè)方案就是:選擇一臺(tái) 32GB 內(nèi)存的云主機(jī)來(lái)部署 Redis。因?yàn)?32GB 的內(nèi)存能保存所有數(shù)據(jù),而且還留有 7GB,可以保證系統(tǒng)的正常運(yùn)行。同時(shí),我還采用 RDB 對(duì)數(shù)據(jù)做持久化,以確保 Redis 實(shí)例故障后,還能從 RDB 恢復(fù)數(shù)據(jù)。
但是,在使用的過(guò)程中,我發(fā)現(xiàn),Redis 的響應(yīng)有時(shí)會(huì)非常慢。后來(lái),我們使用 INFO 命令查看 Redis 的 latest_fork_usec 指標(biāo)值(表示最近一次 fork 的耗時(shí)),結(jié)果顯示這個(gè)指標(biāo)值特別高,快到秒級(jí)別了。
這跟 Redis 的持久化機(jī)制有關(guān)系。在使用 RDB 進(jìn)行持久化時(shí),Redis 會(huì) fork 子進(jìn)程來(lái)完成,fork 操作的用時(shí)和 Redis 的數(shù)據(jù)量是正相關(guān)的,而 fork 在執(zhí)行時(shí)會(huì)阻塞主線程。數(shù)據(jù)量越大,fork 操作造成的主線程阻塞的時(shí)間越長(zhǎng)。所以,在使用 RDB 對(duì) 25GB 的數(shù)據(jù)進(jìn)行持久化時(shí),數(shù)據(jù)量較大,后臺(tái)運(yùn)行的子進(jìn)程在 fork 創(chuàng)建時(shí)阻塞了主線程,于是就導(dǎo)致 Redis 響應(yīng)變慢了。
看來(lái),第一個(gè)方案顯然是不可行的,我們必須要尋找其他的方案。這個(gè)時(shí)候,我們注意到了 Redis 的切片集群。雖然組建切片集群比較麻煩,但是它可以保存大量數(shù)據(jù),而且對(duì) Redis 主線程的阻塞影響較小。
切片集群,也叫分片集群,就是指啟動(dòng)多個(gè) Redis 實(shí)例組成一個(gè)集群,然后按照一定的規(guī)則,把收到的數(shù)據(jù)劃分成多份,每一份用一個(gè)實(shí)例來(lái)保存。回到我們剛剛的場(chǎng)景中,如果把 25GB 的數(shù)據(jù)平均分成 5 份(當(dāng)然,也可以不做均分),使用 5 個(gè)實(shí)例來(lái)保存,每個(gè)實(shí)例只需要保存 5GB 數(shù)據(jù)。如下圖所示:
那么,在切片集群中,實(shí)例在為 5GB 數(shù)據(jù)生成 RDB 時(shí),數(shù)據(jù)量就小了很多,fork 子進(jìn)程一般不會(huì)給主線程帶來(lái)較長(zhǎng)時(shí)間的阻塞。采用多個(gè)實(shí)例保存數(shù)據(jù)切片后,我們既能保存 25GB 數(shù)據(jù),又避免了 fork 子進(jìn)程阻塞主線程而導(dǎo)致的響應(yīng)突然變慢。
在實(shí)際應(yīng)用 Redis 時(shí),隨著用戶或業(yè)務(wù)規(guī)模的擴(kuò)展,保存大量數(shù)據(jù)的情況通常是無(wú)法避免的。而切片集群,就是一個(gè)非常好的解決方案。這節(jié)課,我們就來(lái)學(xué)習(xí)一下。
如何保存更多數(shù)據(jù)?
在剛剛的案例里,為了保存大量數(shù)據(jù),我們使用了大內(nèi)存云主機(jī)和切片集群兩種方法。實(shí)際上,這兩種方法分別對(duì)應(yīng)著 Redis 應(yīng)對(duì)數(shù)據(jù)量增多的兩種方案:縱向擴(kuò)展(scale up)和橫向擴(kuò)展(scale out)。
縱向擴(kuò)展:升級(jí)單個(gè) Redis 實(shí)例的資源配置,包括增加內(nèi)存容量、增加磁盤(pán)容量、使用更高配置的 CPU。就像下圖中,原來(lái)的實(shí)例內(nèi)存是 8GB,硬盤(pán)是 50GB,縱向擴(kuò)展后,內(nèi)存增加到 24GB,磁盤(pán)增加到 150GB。
橫向擴(kuò)展:橫向增加當(dāng)前 Redis 實(shí)例的個(gè)數(shù),就像下圖中,原來(lái)使用 1 個(gè) 8GB 內(nèi)存、50GB 磁盤(pán)的實(shí)例,現(xiàn)在使用三個(gè)相同配置的實(shí)例。
那么,這兩種方式的優(yōu)缺點(diǎn)分別是什么呢?
首先,縱向擴(kuò)展的好處是,實(shí)施起來(lái)簡(jiǎn)單、直接。不過(guò),這個(gè)方案也面臨兩個(gè)潛在的問(wèn)題。
第一個(gè)問(wèn)題是,當(dāng)使用 RDB 對(duì)數(shù)據(jù)進(jìn)行持久化時(shí),如果數(shù)據(jù)量增加,需要的內(nèi)存也會(huì)增加,主線程 fork 子進(jìn)程時(shí)就可能會(huì)阻塞(比如剛剛的例子中的情況)。不過(guò),如果你不要求持久化保存 Redis 數(shù)據(jù),那么,縱向擴(kuò)展會(huì)是一個(gè)不錯(cuò)的選擇。
不過(guò),這時(shí),你還要面對(duì)第二個(gè)問(wèn)題:縱向擴(kuò)展會(huì)受到硬件和成本的限制。這很容易理解,畢竟,把內(nèi)存從 32GB 擴(kuò)展到 64GB 還算容易,但是,要想擴(kuò)充到 1TB,就會(huì)面臨硬件容量和成本上的限制了。
與縱向擴(kuò)展相比,橫向擴(kuò)展是一個(gè)擴(kuò)展性更好的方案。這是因?yàn)椋氡4娓嗟臄?shù)據(jù),采用這種方案的話,只用增加 Redis 的實(shí)例個(gè)數(shù)就行了,不用擔(dān)心單個(gè)實(shí)例的硬件和成本限制。在面向百萬(wàn)、千萬(wàn)級(jí)別的用戶規(guī)模時(shí),橫向擴(kuò)展的 Redis 切片集群會(huì)是一個(gè)非常好的選擇。
不過(guò),在只使用單個(gè)實(shí)例的時(shí)候,數(shù)據(jù)存在哪兒,客戶端訪問(wèn)哪兒,都是非常明確的,但是,切片集群不可避免地涉及到多個(gè)實(shí)例的分布式管理問(wèn)題。要想把切片集群用起來(lái),我們就需要解決兩大問(wèn)題:
數(shù)據(jù)切片后,在多個(gè)實(shí)例之間如何分布?
客戶端怎么確定想要訪問(wèn)的數(shù)據(jù)在哪個(gè)實(shí)例上?
接下來(lái),我們就一個(gè)個(gè)地解決。
數(shù)據(jù)切片和實(shí)例的對(duì)應(yīng)分布關(guān)系
在切片集群中,數(shù)據(jù)需要分布在不同實(shí)例上,那么,數(shù)據(jù)和實(shí)例之間如何對(duì)應(yīng)呢?這就和接下來(lái)我要講的 Redis Cluster 方案有關(guān)了。不過(guò),我們要先弄明白切片集群和 Redis Cluster 的聯(lián)系與區(qū)別。
實(shí)際上,切片集群是一種保存大量數(shù)據(jù)的通用機(jī)制,這個(gè)機(jī)制可以有不同的實(shí)現(xiàn)方案。在 Redis 3.0 之前,官方并沒(méi)有針對(duì)切片集群提供具體的方案。從 3.0 開(kāi)始,官方提供了一個(gè)名為 Redis Cluster 的方案,用于實(shí)現(xiàn)切片集群。Redis Cluster 方案中就規(guī)定了數(shù)據(jù)和實(shí)例的對(duì)應(yīng)規(guī)則。
具體來(lái)說(shuō),Redis Cluster 方案采用哈希槽(Hash Slot,接下來(lái)我會(huì)直接稱(chēng)之為 Slot),來(lái)處理數(shù)據(jù)和實(shí)例之間的映射關(guān)系。在 Redis Cluster 方案中,一個(gè)切片集群共有 16384 個(gè)哈希槽,這些哈希槽類(lèi)似于數(shù)據(jù)分區(qū),每個(gè)鍵值對(duì)都會(huì)根據(jù)它的 key,被映射到一個(gè)哈希槽中。
具體的映射過(guò)程分為兩大步:
首先根據(jù)鍵值對(duì)的 key,按照CRC16 算法計(jì)算一個(gè) 16 bit 的值;
然后,再用這個(gè) 16bit 值對(duì) 16384 取模,得到 0~16383 范圍內(nèi)的模數(shù),每個(gè)模數(shù)代表一個(gè)相應(yīng)編號(hào)的哈希槽。
關(guān)于CRC16 算法,如果感興趣!可以自行Googel查詢
那么,這些哈希槽又是如何被映射到具體的 Redis 實(shí)例上的呢?
我們?cè)诓渴?Redis Cluster 方案時(shí),可以使用 cluster create 命令創(chuàng)建集群,此時(shí),Redis 會(huì)自動(dòng)把這些槽平均分布在集群實(shí)例上。例如,如果集群中有 N 個(gè)實(shí)例,那么,每個(gè)實(shí)例上的槽個(gè)數(shù)為 16384/N 個(gè)。
當(dāng)然, 我們也可以使用 cluster meet 命令手動(dòng)建立實(shí)例間的連接,形成集群,再使用 cluster addslots 命令,指定每個(gè)實(shí)例上的哈希槽個(gè)數(shù)。
客戶端如何定位數(shù)據(jù)?
在定位鍵值對(duì)數(shù)據(jù)時(shí),它所處的哈希槽是可以通過(guò)計(jì)算得到的,這個(gè)計(jì)算可以在客戶端發(fā)送請(qǐng)求時(shí)來(lái)執(zhí)行。但是,要進(jìn)一步定位到實(shí)例,還需要知道哈希槽分布在哪個(gè)實(shí)例上。
一般來(lái)說(shuō),客戶端和集群實(shí)例建立連接后,實(shí)例就會(huì)把哈希槽的分配信息發(fā)給客戶端。但是,在集群剛剛創(chuàng)建的時(shí)候,每個(gè)實(shí)例只知道自己被分配了哪些哈希槽,是不知道其他實(shí)例擁有的哈希槽信息的。
那么,客戶端為什么可以在訪問(wèn)任何一個(gè)實(shí)例時(shí),都能獲得所有的哈希槽信息呢?這是因?yàn)?,Redis 實(shí)例會(huì)把自己的哈希槽信息發(fā)給和它相連接的其它實(shí)例,來(lái)完成哈希槽分配信息的擴(kuò)散。當(dāng)實(shí)例之間相互連接后,每個(gè)實(shí)例就有所有哈希槽的映射關(guān)系了。
客戶端收到哈希槽信息后,會(huì)把哈希槽信息緩存在本地。當(dāng)客戶端請(qǐng)求鍵值對(duì)時(shí),會(huì)先計(jì)算鍵所對(duì)應(yīng)的哈希槽,然后就可以給相應(yīng)的實(shí)例發(fā)送請(qǐng)求了。
總結(jié)
上述講述切片集群在保存大量數(shù)據(jù)方面的優(yōu)勢(shì),以及基于哈希槽的數(shù)據(jù)分布機(jī)制和客戶端定位鍵值對(duì)的方法
在應(yīng)對(duì)數(shù)據(jù)量擴(kuò)容時(shí),雖然增加內(nèi)存這種縱向擴(kuò)展的方法簡(jiǎn)單直接,但是會(huì)造成數(shù)據(jù)庫(kù)的內(nèi)存過(guò)大,導(dǎo)致性能變慢。Redis 切片集群提供了橫向擴(kuò)展的模式,也就是使用多個(gè)實(shí)例,并給每個(gè)實(shí)例配置一定數(shù)量的哈希槽,數(shù)據(jù)可以通過(guò)鍵的哈希值映射到哈希槽,再通過(guò)哈希槽分散保存到不同的實(shí)例上。這樣做的好處是擴(kuò)展性好,不管有多少數(shù)據(jù),切片集群都能應(yīng)對(duì)。
責(zé)編AJX
-
數(shù)據(jù)
+關(guān)注
關(guān)注
8文章
7249瀏覽量
91396 -
磁盤(pán)
+關(guān)注
關(guān)注
1文章
389瀏覽量
25695 -
Redis
+關(guān)注
關(guān)注
0文章
385瀏覽量
11357
發(fā)布評(píng)論請(qǐng)先 登錄
STM32操作矩陣鍵盤(pán)的兩種方法
關(guān)斷檢流放大器的兩種方法
關(guān)斷檢流放大器的兩種方法
AODV協(xié)議中解決斷鏈問(wèn)題的兩種方法

使用jdbc連接上oracle的兩種方法
STM32操作矩陣鍵盤(pán)的兩種方法——掃描和中斷

關(guān)斷檢流放大器的兩種方法

簡(jiǎn)述安裝打印機(jī)驅(qū)動(dòng)的兩種方法

圖騰柱TCM之兩相變頻錯(cuò)相的兩種方法

評(píng)論