現在有一個場景:要用Redis儲存5000萬個鍵值對,每個鍵值對大約是512B,要怎麼部署Redis服務呢?
第一個方案,也是最容易想到的,需要儲存5000萬個鍵值對,每個鍵值對約為512B,一共需要25GB空間,選擇一臺32GB記憶體的用品來部署Redis,還剩餘7GB空間,可以採用RDB對資料做持續久。
但是Redis服務使用不久後出現Redis的響應有時會非常慢。原因是採用了RDB持久化,在前面介紹RDB原理時,我們知道fork子程式的瞬間會阻塞主執行緒,而且記憶體越大,阻塞越長。
第一個方案不太適合,那麼有更好的方案嗎?Redis提供切片叢集機制,多個Redis例項組成一個叢集,按照一定的規則,把收到資料劃分成多份,每一份用一個例項來儲存。這樣一來,在生成RDB時,資料量就小了,fork就不會阻塞主執行緒太長時間。
這裡就引出一個問題:該如何儲存更多的資料?
如何儲存更多資料
通常有兩種方案,分別是縱向擴充套件和橫向擴充套件。
縱向擴充套件,指通過增加硬體配置來擴充套件,採用更大的記憶體,更多的CPU。好處是實施簡單,但缺點是受到硬體和成本的限制,不可能無限擴充套件。
橫向擴充套件,指通過增加機器來組成更大的叢集,這也是分散式方案常用的方式。好處是擴充套件性好,但缺點是管理複雜。
在面向百萬、千萬級別的使用者規模時,橫向擴充套件的Redis切片叢集會是一個非常好的選擇。
在使用單個例項時,資料儲存在哪裡,客戶端訪問哪裡,都是非常明確的。但是切片叢集不可避免要解決多個例項分散式管理的問題,需要解決兩大問題:
- 資料切片後,在多個例項之間如何分佈?
- 客戶端怎麼確定想要訪問的資料在哪個例項上?
資料切片和例項的對應分佈關係
在Redis 3.0之前,官方沒有切片叢集的方案,從3.0開始,官方提供了一個名為Redis Cluster的方案,用於實現切片叢集。
Redis Cluster方案採用雜湊槽來處理資料和例項之間的對映關係。這裡有兩個對映關係:鍵值對與雜湊槽的對映關係和雜湊槽與例項的對映關係。下面我們來介紹一下這兩個對映關係的對映過程。
鍵值對與雜湊槽的對映過程
根據鍵值對的key,按照CRC16演算法計算一個16bit的值。
再用這個16bit值對16384取模,得到0~16383範圍內的模數,每個模數代表一個相應編號的雜湊槽。
說明:Redis切片叢集最多提供16384個雜湊槽。
雜湊槽與例項的對映過程
雜湊槽與例項的對映關係有兩個方案設定,分為自動和手動。
自動對映:使用cluster create
命令建立叢集,Redis會自動把這些槽平均分佈在叢集例項上。
手動對映:使用cluster meet
命令搬運建立例項間的連線,形成叢集,再使用cluster addslots
命令,指定每個例項上的雜湊槽個數。
說明:在手動分配雜湊槽時,需要把16384個槽都分配完,否則Redis叢集無法正常工作。
客戶端如何定位資料
客戶端和叢集例項建立連線後,例項就會把雜湊槽的分配資訊發給客戶端。
叢集剛建立時,例項如何互相知道雜湊槽資訊?Redis例項會擴充套件雜湊槽資訊,每個Redis例項都擁有完整的雜湊槽資訊。
另外,客戶端收到雜湊槽資訊後,會快取在本地,以便在客戶端後續請求直接訪問例項。
但在叢集中,例項和雜湊槽的對應關係不是一成不變的。最常見的變化:
- 在叢集中,例項有新增或刪除,Redis需要重新分配雜湊槽;
- 為了負載均衡,Redis需要把雜湊槽在所有例項上重新分佈一遍。
Redis Cluster提供一種重定向機制,類似於HTTP協議的重定向。
客戶端把一個鍵值對操作請求發給一個例項,如果這個例項沒有這個鍵值對對映的雜湊槽,這個例項就會給客戶端返回MOVED命令的響應結果,包含新例項的訪問地址。
GET hello:key (error)
MOVED 13320 172.16.19.5:6379
其中,MOVED命令表示,客戶端請求的鍵值對所在的雜湊槽13320,實際是在172.16.19.5這個例項上。
如果雜湊槽沒有完成遷移,客戶端請求的資料並不在雜湊槽時,客戶端就會收到一條ASK報錯資訊,如下所示:
GET hello:key (error)
ASK 13320 172.16.19.5:6379
這個結果中的ASK命令就表示,客戶端請求的鍵值對所在的雜湊槽13320,在172.16.19.5這個例項上,但是這個雜湊槽正在遷移。
和MOVED命令不同,ASK命令並不會更新客戶端快取的雜湊槽分配資訊。
Redis Cluster為什麼不採用把key直接對映到例項的方式
整個叢集儲存key的數量是無法預估的,key的數量非常多時,直接記錄每個key對應的例項對映關係,這個對映表會非常龐大,這個對映表無論是儲存在服務端還是客戶端都佔用了非常大的記憶體空間。
Redis Cluster採用無中心化的模式(無proxy,客戶端與服務端直連),客戶端在某個節點訪問一個key,如果這個key不在這個節點上,這個節點需要有糾正客戶端路由到正確節點的能力(MOVED響應),這就需要節點之間互相交換路由表,每個節點擁有整個叢集完整的路由關係。如果儲存的都是key與例項的對應關係,節點之間交換資訊也會變得非常龐大,消耗過多的網路資源,而且就算交換完成,相當於每個節點都需要額外儲存其他節點的路由表,記憶體佔用過大造成資源浪費。
當叢集在擴容、縮容、資料均衡時,節點之間會發生資料遷移,遷移時需要修改每個key的對映關係,維護成本高。
而在中間增加一層雜湊槽,可以把資料和節點解耦,key通過Hash計算,只需要關心對映到了哪個雜湊槽,然後再通過雜湊槽和節點的對映表找到節點,相當於消耗了很少的CPU資源,不但讓資料分佈更均勻,還可以讓這個對映表變得很小,利於客戶端和服務端儲存,節點之間交換資訊時也變得輕量。
當叢集在擴容、縮容、資料均衡時,節點之間的操作例如資料遷移,都以雜湊槽為基本單位進行操作,簡化了節點擴容、縮容的難度,便於叢集的維護和管理。
小結
- 資料擴容有兩種方式:縱向擴充套件和橫向擴充套件。Redis切片叢集提供了橫向擴充套件的模式。
- 叢集的例項增減或者資料重新分佈,會導致雜湊槽和例項的對映關係發生變化。當客戶端傳送請求時,會收到命令執行報錯資訊。
- 在Redis3.0之前,Redis官方並沒有提供切片叢集方案。業界提供了一些成熟的方案,例如基於客戶端分割槽的ShardedJedis,基於代理的Codis、Twemproxy等。