1. 程式人生 > >淺談Redis Cluster

淺談Redis Cluster

資料分割槽

在介紹Redis Cluster之前,先簡單介紹下分散式資料庫的資料分割槽。所謂的資料分割槽就是將一個較大的資料集分佈在不同的節點上進行儲存。常見的資料分割槽方式:節點取餘、一致性雜湊、虛擬槽,下面我們來看下這幾種分割槽方式。

節點取餘:根據key的hash值和節點數取模的方式計算出節點ID,然後向對應的節點提交資料,如下圖所示。
這裡寫圖片描述

對於這種分割槽方式,新增或者刪除節點會造成大量的資料遷移。

假設資料集為1 2 3 … 10,那麼資料分佈應如下所示。

這裡寫圖片描述

如果新增一個節點,那麼資料分佈會變成什麼樣子呢?

這裡寫圖片描述

來看下結果對比:只有1 2 3還分佈在原來的節點上,其餘所有的資料都進行了遷移。在這種分割槽方式下,如果新增的節點時原來的節點的倍數時,遷移的節點數量量會少很多。

一致性雜湊:對於任何的雜湊函式,都有其取值範圍。我們可以用環形結構來標識範圍。通過雜湊函式,每個節點都會被分配到環上的一個位置,每個鍵值也會被對映到環上的一個位置,然後順時針找到相鄰的節點。如下圖所示,例如key分佈在range1內,那麼資料儲存在node2上。

這裡寫圖片描述

對於這種分割槽方式,新增或者刪除節點會造成資料分佈不均勻。

假設資料集為1 2 3 … 12,資料範圍也是1~12,那麼資料分佈應如下所示。

這裡寫圖片描述

如果我們在node1和node2之間新增一個節點,那麼資料分佈應該變成什麼樣子呢?

這裡寫圖片描述

可以看到,我們只是講資料3進行了遷移。但是造成了每個節點負責的資料範圍不等。會造成資料分佈不均等的問題。

虛擬槽:在redis cluster中使用槽來儲存一定範圍內的資料集。每個redis節點上有一定數量的槽。當客戶端提交資料時,要先根據CRC16(key)&16383來計算出資料要落到哪個虛擬槽內。

假設我們有3個節點,那麼可以按如下分配槽:

這裡寫圖片描述

與節點取餘和一致性雜湊分割槽不同,虛擬槽分割槽是服務端分割槽。客戶端可以將資料提交到任意一個redis cluster節點上,如果儲存該資料的槽不在這個節點上,則返回給客戶端目標節點資訊,告知客戶端向目標節點提交資料。

Redis Cluster

redis cluster採用無中心結構,節點間使用gossip協議進行通訊。每個節點儲存資料和所有節點和槽的對映關係。其架構圖如下:

這裡寫圖片描述

moved異常

從上圖可以看出,客戶端是採用直連的方式來連線redis客戶端。那麼redis客戶端是如何知道要提交到節點呢?

實際上,客戶端可以將資料提交到任意一個redis cluster節點上,如果儲存該資料的槽不在這個節點上,則返回給客戶端moved異常,客戶端通過moved異常,永久的將請求轉移到目標節點,如下圖所示。

這裡寫圖片描述

通過moved異常,客戶端可以知道目標節點並重新提交資料,但是這種情形效率不算高,那怎麼解決這個問題的呢?

我們看下redis客戶端JedisCluster怎麼解決這個問題的。

  1. 初始化時從叢集中選一個可執行節點,使用cluster slots初始化槽和節點對映。

  2. 將cluster slots的結果對映到本地,為每個節點建立JedisPool。

  3. 準備執行命令。

現在我們來實際看下這個異常,關於redis cluster的搭建過程本篇文章不進行描述。

首先來看下redis cluster的節點資訊,執行命令redis-cli cluster nodes

這裡寫圖片描述

可以看到使用的是3主3從的配置,虛擬槽的分配是0-5460、5461-10922、10923-16383,我們來演示下這個異常。。

127.0.0.1:6379> cluster keyslot a
(integer) 15495
127.0.0.1:6379> set a b
(error) MOVED 15495 127.0.0.1:6381

可以看到,key為的時候應該將資料插入到15495的槽內。但是15495這個槽是分配在埠號為6381的這個節點上。當嘗試插入操作的時候返回了moved異常,異常資訊包括目標節點和槽的資訊。

ask異常
在redis cluster中,如果新增或者刪除節點,需要進行虛擬槽的遷移。ask異常與moved異常類似,只不過ask異常發生在虛擬槽的遷移過程中,另外它與moved異常不同,ask異常只是將下一次的請求轉移到目標節點,如下圖所示。

這裡寫圖片描述

訊息機制

redis cluster叢集中通過訊息來進行通訊。訊息共有以下5種。

  • meet訊息:傳送者會向接受者傳送cluster meet命令,請求接受者將傳送者加入到叢集中。上文提到的無中心網路結構就是使用meet命令構建的。

  • ping訊息:叢集中的每個節點每秒鐘都會從已知節點列表選舉出5個節點,然後從這5個節點中選中一個最長時間沒有傳送ping訊息的節點作為目標節點來發送ping訊息,來檢測目標節點是否處於線上狀態。

  • pong訊息:接受者接受到傳送者傳送的meet訊息或者ping訊息後,會回覆pong訊息,用於確認訊息已經到達。另外,pong訊息也可以用於重新整理接收者對傳送者的資訊,例如故障轉移後,從節點會向叢集中傳送pong訊息,用於告知從節點已經升級為主節點。

  • fail訊息:fail訊息用於通知將某個節點置為下線狀態。例如節點A認為節點B已下線,節點A會向叢集中傳送一條fail訊息,接受到這條訊息的節點會將節點B標記為已 下線。

  • publish訊息:當某個節點收到publish命令後,會向叢集中傳送一條publish訊息。

故障檢測

叢集中的每個節點都會定期地向叢集中其它節點發送ping訊息,以此來檢測對方是否線上。如果在規定時間內沒有收到pong回覆。則認為目標節點標記為疑似下線(PFAIL)。

如果叢集中的半數異常(大於等於N/2 + 1)的主節點認為某個節點A疑似下線(PFAIL),那麼這個節點A將被標記為已下線(FAIL)。將節點標記A為已下線的節點會向叢集一條關於節點A顯現的訊息,所有收到這條F訊息的節點都會立即將主節點A標記為已下線。

舉個例子,在下圖所示中,主節點7002和主節點7003都認為主節點7000進入了下線狀態。並且主節點7001也認為主節點7000進入了下線狀態。綜合起來,在叢集中4個主節點裡面,有3個都將主節點7000標記為下線。所以主節點7001會將7000節點標記為已下線。並向叢集中廣播一條關於主節點7000的FAIL訊息,如下圖所示。

這裡寫圖片描述

故障轉移

當一個從節點發現自己正在複製的主節點進入了已下線時,從節點將開始對已下線的主節點進行故障轉移操作,以下是故障轉移的執行步驟:

  1. 下線的主節點的所有從節點裡面,會進行選舉,選舉出一個新的主節點。

  2. 被選中的從節點會執行 slave no one命令,成為新的主節點。

  3. 新的主節點會撤銷所有對已下線主節點的槽指派,並將這些槽指派給自己。

  4. 新的主節點向叢集廣播一條pong訊息,這條pong訊息可以讓叢集中的其他節點立即知道這個節點已經由從節點變成了主節點,並且這個主節點已經接管了原本由已下線節點處理的槽。

  5. 新的主節點開始接受和自己負責處理的槽有關的命令請求,故障轉移操作完成。

epoch

  • current epoch:current epoch類似於系統的邏輯時鐘,是一個64位無符號整數,啟動時設定為0。redis中的每個節點都會維護一個current epoch。通過gossip協議,叢集中的current epoch大多數情況下是一致的。current epoch越高,代表節點的配置或者操作越新。當一個節點被重新啟動其current epoch被設定為已知的節點的最大config epoch。

  • config epoch:config epoch類似於節點的版本號,每個節點都有一個不同的config epoch, 是一個單調遞增的64位無符號整數。當故障轉移操作後會將config epoch的值設定為current epoch + 1。

主節點選舉

上文提到了主節點選舉,現在簡單介紹下主節點時如何選舉出來的,以下是主節點選舉的步驟。

  1. 當從節點發現自己複製的主節點進入已下線時,從節點(這裡發出請求的從節點可能會有多個)會向叢集廣播一條cluster_type_failover_auth_request的訊息,要求有投票權(負責處理槽)的主節點向這個節點進行投票。

  2. 收到cluster_type_failover_auth_request訊息的主節點,根據自身條件(發起投票節點的current epoch不低於投票節點的current epoch)判斷是否贊成該從節點成為新的主節點,若贊成則返回一條cluster_type_failover_auth_ack訊息。

  3. 從節點接收到cluster_type_failover_auth_ack訊息,會將選票數加1。

  4. 如果某個從節點的選票大於等於叢集中主節點的一半時(大於等於N/2 + 1),這個節點就會成為新的主節點。

  5. 如果在一個配置週期內,沒有一個從節點獲得足夠多的選票,那麼叢集中會進入新的配置週期,並在此進行選舉,知道選出新的主節點為止。