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