進階的Redis之雜湊分片原理與叢集實戰
如果Redis只用複製功能做主從,那麼當資料量巨大的情況下,單機情況下可能已經承受不下一份資料,更不用說是主從都要各自儲存一份完整的資料。在這種情況下,資料分片是一個非常好的解決辦法。
Redis的Cluster正是用於解決該問題。它主要提供兩個功能:
- 自動對資料分片,落到各個節點上
- 即使叢集部分節點失效或者連線不上,依然可以繼續處理命令
對於第二點,它的功能有點類似於Sentienl的故障轉移(可以瞭解下之前Sentinel的文章),在這裡不細說。下面詳細瞭解下Redis的槽位分片原理,在此之前,先了解下分散式簡單雜湊演算法和一致性雜湊演算法,以幫助理解槽位的作用。
簡單雜湊演算法
假設有三臺機,資料落在哪臺機的演算法為
c = Hash(key) % 3 複製程式碼
例如key A的雜湊值為4,4%3=1,則落在第二臺機。Key ABC雜湊值為11,11%3=2,則落在第三臺機上。
利用這樣的演算法,假設現在資料量太大了,需要增加一臺機器。A原本落在第二臺上,現在根據演算法4%4=0,落到了第一臺機器上了,但是第一臺機器上根本沒有A的值。這樣的演算法會導致增加機器或減少機器的時候,引起大量的快取穿透,造成雪崩。
一致性雜湊演算法
在1997年,麻省理工學院的Karger等人提出了 一致性雜湊演算法 ,為的就是解決分散式快取的問題。
在 一致性雜湊演算法 中,整個雜湊空間是一個 虛擬圓環

假設有四個節點Node A、B、C、D,經過ip地址的雜湊計算,它們的位置如下

有4個儲存物件Object A、B、C、D,經過對Key的雜湊計算後,它們的位置如下

對於各個Object,它所真正的儲存位置是按順時針找到的第一個儲存節點。例如Object A順時針找到的第一個節點是Node A,所以Node A負責儲存Object A,Object B儲存在Node B。
一致性雜湊演算法大概如此,那麼它的 容錯性 和 擴充套件性 如何呢?
假設Node C節點掛掉了,Object C的儲存丟失,那麼它順時針找到的最新節點是Node D。也就是說Node C掛掉了,受影響僅僅包括Node B到Node C區間的資料,並且這些資料會轉移到Node D進行儲存。

同理,假設現在資料量大了,需要增加一臺節點Node X。Node X的位置在Node B到Node C直接,那麼受到影響的僅僅是Node B到Node X間的資料,它們要重新落到Node X上。
所以一致性雜湊演算法對於容錯性和擴充套件性有非常好的支援。但一致性雜湊演算法也有一個嚴重的問題,就是 資料傾斜 。
如果在分片的叢集中,節點太少,並且分佈不均,一致性雜湊演算法就會出現部分節點資料太多,部分節點資料太少。也就是說無法控制節點儲存資料的分配。如下圖,大部分資料都在A上了,B的資料比較少。

雜湊槽
Redis叢集(Cluster)並沒有選用上面一致性雜湊,而是採用了 雜湊槽 (SLOT)的這種概念。主要的原因就是上面所說的,一致性雜湊演算法對於資料分佈、節點位置的控制並不是很友好。
首先 雜湊槽 其實是兩個概念,第一個是 雜湊演算法 。Redis Cluster的hash演算法不是簡單的hash(),而是crc16演算法,一種校驗演算法。
另外一個就是 槽位 的概念,空間分配的規則。其實雜湊槽的本質和一致性雜湊演算法非常相似,不同點就是對於雜湊空間的定義。一致性雜湊的空間是一個圓環,節點分佈是基於圓環的,無法很好的控制資料分佈。而Redis Cluster的槽位空間是自定義分配的,類似於Windows盤分割槽的概念。這種分割槽是可以自定義大小,自定義位置的。
Redis Cluster包含了16384個雜湊槽,每個Key通過計算後都會落在具體一個槽位上,而這個槽位是屬於哪個儲存節點的,則由使用者自己定義分配。例如機器硬碟小的,可以分配少一點槽位,硬碟大的可以分配多一點。如果節點硬碟都差不多則可以平均分配。所以雜湊槽這種概念很好地解決了一致性雜湊的弊端。
另外在 容錯性 和 擴充套件性 上,表象與一致性雜湊一樣,都是對受影響的資料進行轉移。而雜湊槽本質上是對槽位的轉移,把故障節點負責的槽位轉移到其他正常的節點上。擴充套件節點也是一樣,把其他節點上的槽位轉移到新的節點上。
但一定要注意的是,對於槽位的轉移和分派,Redis叢集是不會自動進行的,而是需要人工配置的。所以Redis叢集的高可用是依賴於節點的主從複製與主從間的自動故障轉移。
叢集搭建
下面以最簡單的例子,拋開高可用主從複製級轉移的內容,來重點介紹下Redis叢集是如何搭建,槽位是如何分配的,以加深對Redis叢集原理及概念的理解。
redis.conf配置
先找到redis.conf,啟用cluster功能。

cluster-enabled yes
預設是關閉的,要啟用cluster,讓redis成為叢集的一部分,需要手動開啟才行。
然後配置cluster的配置檔案

每一個cluster節點都有一個cluster的配置檔案,這個檔案主要用於記錄節點資訊,用程式自動生成和管理,不需要人工干預。唯一要注意的是,如果在同一臺機器上執行多個節點,需要修改這個配置為不同的名字。
本次為了方便搭建,所有Redis例項都在同一臺機器上,所以修改不同的cluster config名字後,複製三份redis.conf配置,以用於啟動三個叢集例項(cluster至少要三個主節點才能進行)。
叢集關聯
> redis-server /usr/local/etc/redis/redis-6379.conf --port 6379 & > redis-server /usr/local/etc/redis/redis-6380.conf --port 6380 & > redis-server /usr/local/etc/redis/redis-6381.conf --port 6381 & 複製程式碼
&符號的作用是讓命令在後臺執行,但程式執行的log依然會列印在console中。也可以通過配置redis.conf中 deamonize yes
,讓Redis在後臺執行。
連上6379的Redis例項,然後通過 cluster nodes
檢視叢集範圍。

連上其他例項也是一樣,目前6379、6380、6381在各自的叢集中,且叢集只有它們自己一個。
在6379上,通過 cluster meet
命令,與6380、6381建立連結。
127.0.0.1:6379> cluster meet 127.0.0.1 6380 127.0.0.1:6379> cluster meet 127.0.0.1 6381 複製程式碼

可以看到叢集中已經包含了6379、6380、6381三個節點了。登入其他節點檢視也是一樣的結果。即使6380與6381之間沒有直接手動關聯,但在叢集中,節點一旦發現有未關聯的節點,會自動與之握手關聯。
槽位分配
通過 cluster info
命令檢視叢集的狀態

state的狀態是fail的,還沒啟用。看下官方的說明

只有state為ok,節點才能接受請求。如果只要有一個槽位(slot)沒有分配,那麼這個狀態就是fail。而一共需要分配16384槽位才能讓叢集正常工作。
接下來給6379分配0~5000的槽位,給6380分配5001~10000的槽位,給6381分配10001~16383的槽位。
> redis-cli -c -p 6379 cluster addslots {0..5000} > redis-cli -c -p 6380 cluster addslots {5001..10000} > redis-cli -c -p 6381 cluster addslots {10001..16383} 複製程式碼
再看看 cluster info

state已經為ok,16384個槽位都已經分配好了。現在叢集已經可以正常工作了。
效果測試
隨便登上一個例項,記得加上引數 -c
,啟用叢集模式的客戶端,否則無法正常執行。
redis-cli -c -p 6380 複製程式碼
嘗試下set、get操作

可以看到,Redis叢集會計算key落在哪個卡槽,然後會把命令轉發到負責該卡槽的節點上執行。
利用 cluster keyslot
命令計算出key是在哪個槽位上,從而得出會跳轉到哪個節點上執行。
更多技術文章、精彩乾貨,請關注
部落格:zackku.com
微信公眾號:Zack說碼
