文章目錄
分區:如何在多個 Redis
實例之間拆分數據。
1. 前言
1.1 什麼是分區?
分區是將數據拆分給多個 Redis
實例的過程,因此每個實例將只包含鍵的一個子集。 本文檔的第一部分將向您介紹分區的概念,第二部分將向您展示 Redis
分區的替代方法。
1.2 爲什麼要用分區?
在 Redis
服務中使用分區主要有兩個目的:
- 有了分區就可以使用多臺計算機的內存總和,允許
Redis
處理更大的數據集,不進行分區,數據集的大小就會被限制在一臺計算機的內存。 - 它允許將計算能力擴展到多核和多臺計算機,並將網絡帶寬擴展到多臺計算機和網絡適配器。
2. 分區基礎知識
有多種分區方式。假設我們有4個 Redis
實例,分別是R0
, R1
, R2
, R3
,以及許多代表用戶的鍵,如:user:1
, uesr:2
, …等,我們可以使用多種方法來決定哪個實例存儲哪個鍵。
最簡單的方法是範圍分區(range partitioning
),將一定範圍的對象映射到指定的 Redis
實例。例如,用戶 ID
從0 ~ 10000分給R0
,10001 ~ 20000 分給R1
,依次類推。
上面這個方法有一個缺點:需要一個表記錄如何將範圍對象映射給實例。並且每種對象都需要這樣一個表,這樣的話,分區效率很低,所以通常都不用range partitioning
方法來分區。
哈希分區(hash partitioning
) 可以用來替換 range partitioning
。哈希分區適用於任何鍵,並不要求鍵的格式是 object_name:id
。
- 獲取鍵名,使用
hash
函數將其轉換爲數字。例如,使用crc32("foobar")
,將之轉傳承93024922。 - 對
hash
之後的數字取模,將其轉換成0 ~ 3之間的數字,這對應着R0 ~ R3
。例如,93024922模4等於2,所以這個鍵應該存在R2
實例中。
還有許多其他方法可以執行分區,但是通過這兩個示例,您應該瞭解一下。 哈希分區的一種高級形式稱爲 一致性哈希
,由一些Redis
客戶端和代理實現。
2.1 分區的不同實現
- 客戶端分區(
client side partitioning
)意味着客戶端直接選擇在哪個節點上寫入或讀取指定的鍵。 許多Redis客戶端
實現客戶端分區。 - 代理輔助分區(
proxy assisted partitioning
)意味着我們的客戶將請求發送到能夠使用Redis
協議的代理,而不是直接將請求發送到正確的Redis
實例。 代理將確保根據配置的分區架構(configured partitioning schema
)將請求轉發到正確的Redis
實例,並將答覆發送回客戶端。Redis
和Memcached
代理Twemproxy
實現了代理輔助分區。 - 查詢路由(
query routing
)意味着你可以將查詢發送到隨機實例,該實例將確保將查詢轉發到正確的節點。Redis Cluster
在客戶端的幫助下實現了混合形式的查詢路由(請求不會從Redis實例直接轉發到另一個實例,而是會將客戶端重定向到正確的節點)。
2.2 分區的缺點
Redis
的某些功能在分區中不能很好地發揮作用:
- 通常不支持涉及多個鍵的操作。 例如,將1個鍵映射到多個
Redis
實例,不能再這樣的鍵之間求交集(實際上,有很多方法可以執行此操作,但不能直接執行)。 - 不能使用涉及多個鍵的
Redis
事務。 - 分區粒度是關鍵,因此無法使用單個大鍵(如非常大的
Sorted set
)對數據集進行分片。 - 使用分區時,數據處理會更加複雜。例如,你必須處理多個
RDB
/AOF
文件,並且要備份數據,則需要從多個實例和主機聚合持久性文件。 - 添加和刪除容量可能很複雜。 例如,
Redis Cluster
支持大多數透明的數據重新平衡,並能夠在運行時添加和刪除節點,但是其他系統(例如客戶端分區和代理)不支持此功能。 但是,在這方面,一種稱爲“預分片
”的技術會有所幫助。
2.3 數據存儲還是緩存?
從概念上來看,無論將 Redis
用作數據存儲還是用作緩存,Redis
中的分區都是相同的,但是在將其用作數據存儲時存在很大的限制。
當 Redis
用作數據存儲時,指定的鍵必須始終映射到同一 Redis
實例。 當 Redis
用作緩存時,如果給定的節點不可用,則使用不同的節點也不是什麼大問題,因爲我們希望提高系統的可用性。
如果指定的鍵的首選節點不可用,則通常可以通過一致性哈希切換到其他節點。 同樣,如果您添加新節點,則部分新鍵將開始存儲在新節點上。
- 如果將
Redis
用作緩存,則可以使用一致哈希來進行向上和向下縮放。 - 如果將
Redis
用作存儲,則使用固定的鍵到節點的映射,因此節點數必須固定並且不能變化。否則,需要一個能夠在添加或刪除節點時在節點之間重新平衡鍵的系統,並且目前只有Redis Cluster
能夠做到這一點。
2.4 預分片(presharding)
從上面的瞭解可知,分區的一個問題是:除非將 Redis
作爲緩存,否則添加和刪除節點十分棘手。(當然,使用固定的key-instances映射
也沒這個問題。)
但是,數據存儲常常需要變化,所以固定的key-instances映射
不太符合實際。
由於 Redis
的佔用空間非常小且重量輕,所以從一開始就可以在多個 Redis
實例使用分區。
爲了儘量少的添加和刪除節點,從一開始就使用大量的 Redis
實例。例如,對於大多數用戶而言,32或64個實例可以解決問題,並將爲增長提供足夠的空間。這樣,隨着數據存儲需求的增加以及您需要更多的 Redis
服務器,您要做的就是將實例從一臺服務器移動到另一臺服務器。 一旦額外添加了第一臺服務器,您將需要將一半 Redis
實例從第一臺服務器移動到第二臺服務器,依此類推。
使用 Redis
副本,您將可以爲用戶減少停機時間或減少停機時間。
3. Redis 分區的實現
3.1 Redis 集羣
Redis Cluster
是獲得自動分片和高可用性的首選方法。Redis Cluster
是 query routing
和 client side partitioning
的混合體。
3.2 Twemproxy
Twemproxy
支持在多個 Redis
實例之間進行自動分區,並在節點不可用的情況下彈出可選節點(這將更改key-instances映射
,因此僅在將 Redis
用作緩存時才應使用此功能)。
基本上,Twemproxy
是客戶端和 Redis
實例之間的中間層,它將以最小的額外複雜性可靠地爲我們處理分區。
3.3 支持一致哈希的客戶端
Twemproxy
的替代方法是使用通過一致性哈希
或其他類似算法實現客戶端分區的客戶端。 有多個 Redis
客戶端支持一致性哈希
,特別是 Redis-rb
和 Predis
。
那麼什麼是一致性哈希呢?
還是拿前面的例子來說。4個 Redis
實例,使用簡單哈希的過程如下:
- 使用
hash
函數將鍵轉換成數字。 - 將此數字對4取模,結果爲0、1、2、3分別對應
Redis
的4個實例R0、 R1、 R2、 R3。
但是,因爲4個 Redis
實例不夠用了,又加了1個 Redis
實例,那麼前面的 key-instances的映射
關係就會發生改變了,如果要解決這個問題就需要重新對所用的鍵的哈希值對5取模,來決定映射到哪一個 Redis
實例上,而且在新映射的機器中沒有之前存的鍵了。
當機器數量發生變動的時候,幾乎所有的數據都會移動,就 Redis
而言可能造成緩存雪崩
(在這一時刻,所有緩存都沒了,因爲新映射的機器中沒有存對應的鍵)。此時的問題是,當增加或者刪除節點時,對於大多數記錄,保證原來分配到的某個節點,現在仍然應該分配到那個節點,將數據遷移量的降到最低,這就是一致性哈希要做的事情。在這裏我們不指定是數據庫還是什麼,反正都是分佈式存儲節點。
一致性哈希
就是解決的上述問題。從 2 個方向去考慮:
- 節點宕機時,數據記錄會被定位到下一個節點上;
- 新增節點時,相關區間內的數據記錄就需要重新哈希。
一致性 Hash
算法也是使用取模的思想,只是,剛纔描述的取模法是對節點數量進行取模,而一致性Hash算法是對 232 取模,什麼意思呢?簡單來說,一致性Hash算法將整個哈希值空間組織成一個虛擬的圓環,如假設某哈希函數H的值空間爲0 ~ 232-1(即哈希值是一個32位無符號整形),整個哈希環如下,從 0 ~ 232-1 代表的分別是一個個的節點,這個環也叫哈希環
。
- 根據節點
ip
或者其他進行哈希,並對 232 取模,找到這些節點在哈希環中的位置; - 根據
Redis
中的鍵進行哈希,並對 232 取模,找到這個鍵在哈希環中的位置; - 從這個鍵的位置順時針找到的第一個節點就是應該映射的節點了。
當服務器節點過少時,使用一致性哈希
可能會出現 數據傾斜問題
(也就是,被緩存的對象大部分都在某一臺服務器上)。採用 虛擬節點機制
,即對每一個服務節點計算多個哈希,每個計算結果位置都放置一個此服務節點,稱爲虛擬節點
。具體做法可以在服務器IP
或主機名
的後面增加編號來實現。
4. 參考文獻
[1] Redis 官方文檔 partitioning
[2] 一致性哈希
[3] 面試必備:什麼是一致性Hash算法?