30 張圖 講清楚 Redis Cluster

你好,我是田哥

今天下午和一位同學聊 Redis 集羣,這玩意真沒那麼簡單,內容非常多。

Redis Cluster 是 Redis 官方提供的 Redis 集羣功能。

1. 爲什麼要實現 Redis Cluster

  1. 主從複製不能實現高可用

  2. 隨着公司發展,用戶數量增多,併發越來越多,業務需要更高的 QPS,而主從複製中單機的 QPS 可能無法滿足業務需求

  3. 數據量的考慮,現有服務器內存不能滿足業務數據的需要時,單純向服務器添加內存不能達到要求,此時需要考慮分佈式需求,把數據分佈到不同服務器上

  4. 網絡流量需求:業務的流量已經超過服務器的網卡的上限值,可以考慮使用分佈式來進行分流

  5. 離線計算,需要中間環節緩衝等別的需求

2. 數據分佈

2.1 爲什麼要做數據分佈

全量數據,單機 Redis 節點無法滿足要求,按照分區規則把數據分到若干個子集當中

2.2 常用數據分佈方式之順序分佈

比如:1 到 100 個數字,要保存在 3 個節點上,按照順序分區,把數據平均分配三個節點上 1 號到 33 號數據保存到節點 1 上,34 號到 66 號數據保存到節點 2 上,67 號到 100 號數據保存到節點 3 上

順序分區常用在關係型數據庫的設計

2.3 常用數據分佈方式之哈希分佈

例如 1 到 100 個數字,對每個數字進行哈希運算,然後對每個數的哈希結果除以節點數進行取餘,餘數爲 1 則保存在第 1 個節點上,餘數爲 2 則保存在第 2 個節點上,餘數爲 0 則保存在第 3 個節點,這樣可以保證數據被打散,同時保證數據分佈的比較均勻

哈希分佈方式分爲三個分區方式:

2.3.1 節點取餘分區

比如有 100 個數據,對每個數據進行 hash 運算之後,與節點數進行取餘運算,根據餘數不同保存在不同的節點上

節點取餘方式是非常簡單的一種分區方式

節點取餘分區方式有一個問題:即當增加或減少節點時,原來節點中的 80% 的數據會進行遷移操作,對所有數據重新進行分佈

節點取餘分區方式建議使用多倍擴容的方式,例如以前用 3 個節點保存數據,擴容爲比以前多一倍的節點即 6 個節點來保存數據,這樣只需要適移 50% 的數據。數據遷移之後,第一次無法從緩存中讀取數據,必須先從數據庫中讀取數據,然後回寫到緩存中,然後才能從緩存中讀取遷移之後的數據

節點取餘方式優點:

客戶端分片
配置簡單:對數據進行哈希,然後取餘

節點取餘方式缺點:

數據節點伸縮時,導致數據遷移
遷移數量和添加節點數據有關,建議翻倍擴容

2.3.2 一致性哈希分區

一致性哈希原理:

將所有的數據當做一個 token 環,token 環中的數據範圍是 0 到 2 的 32 次方。然後爲每一個數據節點分配一個 token 範圍值,這個節點就負責保存這個範圍內的數據。

對每一個 key 進行 hash 運算,被哈希後的結果在哪個 token 的範圍內,則按順時針去找最近的節點,這個 key 將會被保存在這個節點上。

在上面的圖中,有 4 個 key 被 hash 之後的值在在 n1 節點和 n2 節點之間,按照順時針規則,這 4 個 key 都會被保存在 n2 節點上, 如果在 n1 節點和 n2 節點之間添加 n5 節點,當下次有 key 被 hash 之後的值在 n1 節點和 n5 節點之間,這些 key 就會被保存在 n5 節點上面了 在上面的例子裏,添加 n5 節點之後,數據遷移會在 n1 節點和 n2 節點之間進行,n3 節點和 n4 節點不受影響,數據遷移範圍被縮小很多

同理,如果有 1000 個節點,此時添加一個節點,受影響的節點範圍最多隻有千分之 2 一致性哈希一般用在節點比較多的時候

一致性哈希分區優點:

採用客戶端分片方式:哈希 + 順時針(優化取餘)
節點伸縮時,隻影響鄰近節點,但是還是有數據遷移

一致性哈希分區缺點:

翻倍伸縮,保證最小遷移數據和負載均衡

2.3.3 虛擬槽分區

虛擬槽分區是 Redis Cluster 採用的分區方式

預設虛擬槽,每個槽就相當於一個數字,有一定範圍。每個槽映射一個數據子集,一般比節點數大

Redis Cluster 中預設虛擬槽的範圍爲 0 到 16383

步驟:

  1. 把 16384 槽按照節點數量進行平均分配,由節點進行管理

  2. 對每個 key 按照 CRC16 規則進行 hash 運算

  3. 把 hash 結果對 16383 進行取餘

  4. 把餘數發送給 Redis 節點

  5. 節點接收到數據,驗證是否在自己管理的槽編號的範圍

需要注意的是:Redis Cluster 的節點之間會共享消息,每個節點都會知道是哪個節點負責哪個範圍內的數據槽

虛擬槽分佈方式中,由於每個節點管理一部分數據槽,數據保存到數據槽中。當節點擴容或者縮容時,對數據槽進行重新分配遷移即可,數據不會丟失。虛擬槽分區特點:

使用服務端管理節點,槽,數據:例如Redis Cluster
可以對數據打散,又可以保證數據分佈均勻

2.3 順序分佈與哈希分佈的對比

3.Redis Cluster 基本架構

3.1 節點

Redis Cluster 是分佈式架構:即 Redis Cluster 中有多個節點,每個節點都負責進行數據讀寫操作

每個節點之間會進行通信。

3.2 meet 操作

節點之間會相互通信

meet 操作是節點之間完成相互通信的基礎,meet 操作有一定的頻率和規則

3.3 分配槽

把 16384 個槽平均分配給節點進行管理,每個節點只能對自己負責的槽進行讀寫操作

由於每個節點之間都彼此通信,每個節點都知道另外節點負責管理的槽範圍

客戶端訪問任意節點時,對數據 key 按照 CRC16 規則進行 hash 運算,然後對運算結果對 16383 進行取作,如果餘數在當前訪問的節點管理的槽範圍內,則直接返回對應的數據 如果不在當前節點負責管理的槽範圍內,則會告訴客戶端去哪個節點獲取數據,由客戶端去正確的節點獲取數據

3.4 複製

保證高可用,每個主節點都有一個從節點,當主節點故障,Cluster 會按照規則實現主備的高可用性

對於節點來說,有一個配置項:cluster-enabled,即是否以集羣模式啓動

3.5 客戶端路由

3.5.1 moved 重定向

  1. 每個節點通過通信都會共享 Redis Cluster 中槽和集羣中對應節點的關係

  2. 客戶端向 Redis Cluster 的任意節點發送命令,接收命令的節點會根據 CRC16 規則進行 hash 運算與 16383 取餘,計算自己的槽和對應節點

  3. 如果保存數據的槽被分配給當前節點,則去槽中執行命令,並把命令執行結果返回給客戶端

  4. 如果保存數據的槽不在當前節點的管理範圍內,則向客戶端返回 moved 重定向異常

  5. 客戶端接收到節點返回的結果,如果是 moved 異常,則從 moved 異常中獲取目標節點的信息

  6. 客戶端向目標節點發送命令,獲取命令執行結果

需要注意的是:客戶端不會自動找到目標節點執行命令

槽命中:直接返回

[root@mysql ~]# redis-cli -p 9002 cluster keyslot hello
(integer) 866

槽不命中:moved 異常

[root@mysql ~]# redis-cli -p 9002 cluster keyslot php
(integer) 9244

[root@mysql ~]# redis-cli -c -p 9002
127.0.0.1:9002> cluster keyslot hello
(integer) 866
127.0.0.1:9002> set hello world
-> Redirected to slot [866] located at 192.168.81.100:9003
OK
192.168.81.100:9003> cluster keyslot python
(integer) 7252
192.168.81.100:9003> set python best
-> Redirected to slot [7252] located at 192.168.81.101:9002
OK
192.168.81.101:9002> get python
"best"
192.168.81.101:9002> get hello
-> Redirected to slot [866] located at 192.168.81.100:9003
"world"
192.168.81.100:9003> exit
[root@mysql ~]# redis-cli -p 9002
127.0.0.1:9002> cluster keyslot python
(integer) 7252
127.0.0.1:9002> set python best
OK
127.0.0.1:9002> set hello world
(error) MOVED 866 192.168.81.100:9003
127.0.0.1:9002> exit
[root@mysql ~]#

3.5.2 ask 重定向

在對集羣進行擴容和縮容時,需要對槽及槽中數據進行遷移

當客戶端向某個節點發送命令,節點向客戶端返回 moved 異常,告訴客戶端數據對應的槽的節點信息

如果此時正在進行集羣擴展或者縮空操作,當客戶端向正確的節點發送命令時,槽及槽中數據已經被遷移到別的節點了,就會返回 ask,這就是 ask 重定向機制

步驟:

  1. 客戶端向目標節點發送命令,目標節點中的槽已經遷移支別的節點上了,此時目標節點會返回 ask 轉向給客戶端

  2. 客戶端向新的節點發送 Asking 命令給新的節點,然後再次向新節點發送命令

  3. 新節點執行命令,把命令執行結果返回給客戶端

moved 異常與 ask 異常的相同點和不同點

兩者都是客戶端重定向
moved異常:槽已經確定遷移,即槽已經不在當前節點
ask異常:槽還在遷移中

3.5.3 smart 智能客戶端

使用智能客戶端的首要目標:追求性能

從集羣中選一個可運行節點,使用 Cluster slots 初始化槽和節點映射

將 Cluster slots 的結果映射在本地,爲每個節點創建 JedisPool,相當於爲每個 redis 節點都設置一個 JedisPool,然後就可以進行數據讀寫操作

讀寫數據時的注意事項:

3.6 多節點命令實現

Redis Cluster 不支持使用 scan 命令掃描所有節點 多節點命令就是在在所有節點上都執行一條命令 批量操作優化

3.6.1 串行 mget

定義 for 循環,遍歷所有的 key,分別去所有的 Redis 節點中獲取值並進行彙總,簡單,但是效率不高,需要 n 次網絡時間

3.6.2 串行 IO

對串行 mget 進行優化,在客戶端本地做內聚,對每個 key 進行 CRC16hash,然後與 16383 取餘,就可以知道哪個 key 對應的是哪個槽

本地已經緩存了槽與節點的對應關係,然後對 key 按節點進行分組,成立子集,然後使用 pipeline 把命令發送到對應的 node,需要 nodes 次網絡時間,大大減少了網絡時間開銷

3.6.3 並行 IO

並行 IO 是對串行 IO 的一個優化,把 key 分組之後,根據節點數量啓動對應的線程數,根據多線程模式並行向 node 節點請求數據,只需要 1 次網絡時間

3.6.4 hash_tag

將 key 進行 hash_tag 的包裝,然後把 tag 用大括號括起來,保證所有的 key 只向一個 node 請求數據,這樣執行類似 mget 命令只需要去一個節點獲取數據即可,效率更高

3.6.5 四種優化方案優缺點分析

3.7 故障發現

Redis Cluster 通過 ping/pong 消息實現故障發現:不需要 sentinel

ping/pong 不僅能傳遞節點與槽的對應消息,也能傳遞其他狀態,比如:節點主從狀態,節點故障等

故障發現就是通過這種模式來實現,分爲主觀下線和客觀下線

3.7.1 主觀下線

某個節點認爲另一個節點不可用,'偏見',只代表一個節點對另一個節點的判斷,不代表所有節點的認知

主觀下線流程:

  1. 節點 1 定期發送 ping 消息給節點 2

  2. 如果發送成功,代表節點 2 正常運行,節點 2 會響應 PONG 消息給節點 1,節點 1 更新與節點 2 的最後通信時間

  3. 如果發送失敗,則節點 1 與節點 2 之間的通信異常判斷連接,在下一個定時任務週期時,仍然會與節點 2 發送 ping 消息

  4. 如果節點 1 發現與節點 2 最後通信時間超過 node-timeout,則把節點 2 標識爲 pfail 狀態

3.7.2 客觀下線

當半數以上持有槽的主節點都標記某節點主觀下線時,可以保證判斷的公平性

集羣模式下,只有主節點 (master) 纔有讀寫權限和集羣槽的維護權限,從節點 (slave) 只有複製的權限

客觀下線流程:

  1. 某個節點接收到其他節點發送的 ping 消息,如果接收到的 ping 消息中包含了其他 pfail 節點,這個節點會將主觀下線的消息內容添加到自身的故障列表中,故障列表中包含了當前節點接收到的每一個節點對其他節點的狀態信息

  2. 當前節點把主觀下線的消息內容添加到自身的故障列表之後,會嘗試對故障節點進行客觀下線操作

故障列表的週期爲:集羣的node-timeout * 2,保證以前的故障消息不會對週期內的故障消息造成影響,保證客觀下線的公平性和有效性

3.8 故障恢復

3.8.1 資格檢查

3.9.2 準備選舉時間

使偏移量最大的從節點具備優先級成爲主節點的條件

3.8.3 選舉投票

對選舉出來的多個從節點進行投票,選出新的主節點

3.8.4 替換主節點

當前從節點取消複製變成離節點(slaveof no one)
執行cluster del slot撤銷故障主節點負責的槽,並執行cluster add slot把這些槽分配給自己
向集羣廣播自己的pong消息,表明已經替換了故障從節點

3.8.5 故障轉移演練

對某一個主節點執行kill -9 {pid}來模擬宕機的情況

3.9 Redis Cluster 的缺點

當節點數量很多時,性能不會很高
解決方式:使用智能客戶端。智能客戶端知道由哪個節點負責管理哪個槽,而且當節點與槽的映射關係發生改變時,客戶端也會知道這個改變,這是一種非常高效的方式

4. 搭建 Redis Cluster

搭建 Redis Cluster 有兩種安裝方式

5. 開發運維常見的問題

5.1 集羣完整性

cluster-require-full-coverage 默認爲 yes,即是否集羣中的所有節點都是在線狀態且 16384 個槽都處於服務狀態時,集羣纔會提供服務

集羣中 16384 個槽全部處於服務狀態,保證集羣完整性

當某個節點故障或者正在故障轉移時獲取數據會提示:(error)CLUSTERDOWN The cluster is down

建議把 cluster-require-full-coverage 設置爲 no

5.2 帶寬消耗

Redis Cluster 節點之間會定期交換 Gossip 消息,以及做一些心跳檢測

官方建議 Redis Cluster 節點數量不要超過 1000 個, 當集羣中節點數量過多時,會產生不容忽視的帶寬消耗

消息發送頻率:節點發現與其他節點最後通信時間超過 cluster-node-timeout /2 時,會直接發送 PING 消息

消息數據量:slots 槽數組 (2kb 空間) 和整個集羣 1/10 的狀態數據(10 個節點狀態數據約爲 1kb)

節點部署的機器規模:集羣分佈的機器越多且每臺機器劃分的節點數越均勻,則集羣內整體的可用帶寬越高

帶寬優化:

避免使用'大'集羣:避免多業務使用一個集羣,大業務可以多集羣
cluster-node-timeout:帶寬和故障轉移速度的均衡
儘量均勻分配到多機器上:保證高可用和帶寬

5.3 Pub/Sub 廣播

在任意一個 cluster 節點執行 publish,則發佈的消息會在集羣中傳播,集羣中的其他節點都會訂閱到消息,這樣節點的帶寬的開銷會很大

publish 在集羣每個節點廣播,加重帶寬

解決辦法:需要使用 Pub/Sub 時,爲了保證高可用,可以單獨開啓一套 Redis Sentinel

5.4 集羣傾斜

對於分佈式數據庫來說,存在傾斜問題是比較常見的

集羣傾斜也就是各個節點使用的內存不一致

5.4.1 數據傾斜原因

  1. 節點和槽分配不均,如果使用 redis-trib.rb 工具構建集羣,則出現這種情況的機會不多
redis-trib.rb info ip:port查看節點,槽,鍵值分佈
redis-trib.rb rebalance ip:port進行均衡(謹慎使用)
  1. 不同槽對應鍵值數量差異比較大
CRC16算法正常情況下比較均勻
可能存在hash_tag
cluster countkeysinslot {slot}獲取槽對應鍵值個數
  1. 包含 bigkey:例如大字符串,幾百萬的元素的 hash,set 等
在從節點:redis-cli --bigkeys
優化:優化數據結構
  1. 內存相關配置不一致
hash-max-ziplist-value:滿足一定條件情況下,hash可以使用ziplist
set-max-intset-entries:滿足一定條件情況下,set可以使用intset
在一個集羣內有若干個節點,當其中一些節點配置上面兩項優化,另外一部分節點沒有配置上面兩項優化
當集羣中保存hash或者set時,就會造成節點數據不均勻
優化:定期檢查配置一致性
  1. 請求傾斜:熱點 key
重要的key或者bigkey
Redis Cluster某個節點有一個非常重要的key,就會存在熱點問題

5.4.2 集羣傾斜優化:

5.5 集羣讀寫分離

只讀連接:集羣模式下,從節點不接受任何讀寫請求

當向從節點執行讀請求時,重定向到負責槽的主節點

readonly 命令可以讀:連接級別命令,當連接斷開之後,需要再次執行 readonly 命令

讀寫分離:

同樣的問題:複製延遲,讀取過期數據,從節點故障
修改客戶端:cluster slaves {nodeId}

5.6 數據遷移

官方遷移工具:redis-trib.rb 和 import

只能從單機遷移到集羣

不支持在線遷移:source 需要停寫

不支持斷點續傳

單線程遷移:影響深度

在線遷移:

唯品會:redis-migrate-tool
豌豆莢:redis-port

5.7 集羣 VS 單機

集羣的限制:

key批量操作支持有限:例如mget,mset必須在一個slot
key事務和Lua支持有限:操作的key必須在一個節點
key是數據分區的最小粒度:不支持bigkey分區
不支持多個數據庫:集羣模式下只有一個db0
複製只支持一層:不支持樹形複製結構
Redis Cluster滿足容量和性能的擴展性,很多業務'不需要'
大多數時客戶端性能會'降低'
命令無法跨節點使用:mget,keys,scan,flush,sinter等
Lua和事務無法跨節點使用
客戶端維護更復雜:SDK和應用本身消耗(例如更多的連接池)

很多場景Redis Sentinel已經夠用了

6.Redis Cluster 總結:

1.Redis Cluster 數據分區規則採用虛擬槽方式 (16384 個槽),每個節點負責一部分槽和相關數據,實現數據和請求的負載均衡

  1. 搭建 Redis Cluster 劃分四個步驟:準備節點,meet 操作,分配槽,複製數據。

3.Redis 官方推薦使用 redis-trib.rb 工具快速搭建 Redis Cluster

  1. 集羣伸縮通過在節點之間移動槽和相關數據實現
  1. 使用 smart 客戶端操作集羣過到通信效率最大化,客戶端內部負責計算維護鍵,槽以及節點的映射,用於快速定位到目標節點

  2. 集羣自動故障轉移過程分爲故障發現和節點恢復。節點下線分爲主觀下線和客觀下線,當超過半數節點認爲故障節點爲主觀下線時,標記這個節點爲客觀下線狀態。從節點負責對客觀下線的主節點觸發故障恢復流程,保證集羣的可用性

  3. 開發運維常見問題包括:超大規模集羣帶席消耗,pub/sub 廣播問題,集羣傾斜問題,單機和集羣對比等。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tlocdMlyI4Sx8UxfbuVjTw