進階的Redis之哈希分片原理與集羣實戰

前面介紹了《進階的Redis之數據持久化RDB與AOF》《進階的Redis之Sentinel原理及實戰》,這次來了解下Redis的集羣功能,以及其中哈希分片原理。

集羣分片模式

如果Redis只用複製功能做主從,那麼當數據量巨大的情況下,單機情況下可能已經承受不下一份數據,更不用說是主從都要各自保存一份完整的數據。在這種情況下,數據分片是一個非常好的解決辦法。

Redis的Cluster正是用於解決該問題。它主要提供兩個功能:

  1. 自動對數據分片,落到各個節點上
  2. 即使集羣部分節點失效或者連接不上,依然可以繼續處理命令

對於第二點,它的功能有點類似於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等人提出了一致性哈希算法,爲的就是解決分佈式緩存的問題。

一致性哈希算法中,整個哈希空間是一個虛擬圓環
進階的Redis之哈希分片原理與集羣實戰

假設有四個節點Node A、B、C、D,經過ip地址的哈希計算,它們的位置如下
進階的Redis之哈希分片原理與集羣實戰

有4個存儲對象Object A、B、C、D,經過對Key的哈希計算後,它們的位置如下
進階的Redis之哈希分片原理與集羣實戰
對於各個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進行存儲。
進階的Redis之哈希分片原理與集羣實戰

同理,假設現在數據量大了,需要增加一臺節點Node X。Node X的位置在Node B到Node C直接,那麼受到影響的僅僅是Node B到Node X間的數據,它們要重新落到Node X上。

所以一致性哈希算法對於容錯性和擴展性有非常好的支持。但一致性哈希算法也有一個嚴重的問題,就是數據傾斜

如果在分片的集羣中,節點太少,並且分佈不均,一致性哈希算法就會出現部分節點數據太多,部分節點數據太少。也就是說無法控制節點存儲數據的分配。如下圖,大部分數據都在A上了,B的數據比較少。
進階的Redis之哈希分片原理與集羣實戰

哈希槽

Redis集羣(Cluster)並沒有選用上面一致性哈希,而是採用了哈希槽(SLOT)的這種概念。主要的原因就是上面所說的,一致性哈希算法對於數據分佈、節點位置的控制並不是很友好。

首先哈希槽其實是兩個概念,第一個是哈希算法。Redis Cluster的hash算法不是簡單的hash(),而是crc16算法,一種校驗算法。

另外一個就是槽位的概念,空間分配的規則。其實哈希槽的本質和一致性哈希算法非常相似,不同點就是對於哈希空間的定義。一致性哈希的空間是一個圓環,節點分佈是基於圓環的,無法很好的控制數據分佈。而Redis Cluster的槽位空間是自定義分配的,類似於Windows盤分區的概念。這種分區是可以自定義大小,自定義位置的。

Redis Cluster包含了16384個哈希槽,每個Key通過計算後都會落在具體一個槽位上,而這個槽位是屬於哪個存儲節點的,則由用戶自己定義分配。例如機器硬盤小的,可以分配少一點槽位,硬盤大的可以分配多一點。如果節點硬盤都差不多則可以平均分配。所以哈希槽這種概念很好地解決了一致性哈希的弊端。

另外在容錯性擴展性上,表象與一致性哈希一樣,都是對受影響的數據進行轉移。而哈希槽本質上是對槽位的轉移,把故障節點負責的槽位轉移到其他正常的節點上。擴展節點也是一樣,把其他節點上的槽位轉移到新的節點上。

但一定要注意的是,對於槽位的轉移和分派,Redis集羣是不會自動進行的,而是需要人工配置的。所以Redis集羣的高可用是依賴於節點的主從複製與主從間的自動故障轉移。

集羣搭建

下面以最簡單的例子,拋開高可用主從複製級轉移的內容,來重點介紹下Redis集羣是如何搭建,槽位是如何分配的,以加深對Redis集羣原理及概念的理解。

redis.conf配置

先找到redis.conf,啓用cluster功能。
進階的Redis之哈希分片原理與集羣實戰

cluster-enabled yes默認是關閉的,要啓用cluster,讓redis成爲集羣的一部分,需要手動打開才行。

然後配置cluster的配置文件
進階的Redis之哈希分片原理與集羣實戰
每一個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查看集羣範圍。
進階的Redis之哈希分片原理與集羣實戰
連上其他實例也是一樣,目前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

進階的Redis之哈希分片原理與集羣實戰
可以看到集羣中已經包含了6379、6380、6381三個節點了。登錄其他節點查看也是一樣的結果。即使6380與6381之間沒有直接手動關聯,但在集羣中,節點一旦發現有未關聯的節點,會自動與之握手關聯。

槽位分配

通過cluster info命令查看集羣的狀態
進階的Redis之哈希分片原理與集羣實戰
state的狀態是fail的,還沒啓用。看下官方的說明
進階的Redis之哈希分片原理與集羣實戰
只有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
進階的Redis之哈希分片原理與集羣實戰
state已經爲ok,16384個槽位都已經分配好了。現在集羣已經可以正常工作了。

效果測試

隨便登上一個實例,記得加上參數-c,啓用集羣模式的客戶端,否則無法正常運行。

  redis-cli -c -p 6380

嘗試下set、get操作
進階的Redis之哈希分片原理與集羣實戰
可以看到,Redis集羣會計算key落在哪個卡槽,然後會把命令轉發到負責該卡槽的節點上執行。

利用cluster keyslot命令計算出key是在哪個槽位上,從而得出會跳轉到哪個節點上執行。


更多技術文章、精彩乾貨,請關注
博客:zackku.com
微信公衆號:Zack說碼
進階的Redis之哈希分片原理與集羣實戰

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章