Redis 叢集功能說明

rainbowbridg發表於2012-02-20

這些是redis叢集的一些進展,有些已經實現有些沒有實現,如果真的實現了,叢集功能還是很強大的說!

[@more@]

介紹

這篇文件主要是為了說明正在進展中的Redis叢集功能。文件主要分為兩個部分,前一部分主要介紹我在非穩定分支已完成的程式碼,後一部分主要介紹還有哪些功能待實現。

本文件所有的說明都有可能在將來由於設計原因而進行更改,而未實現的計劃比已實現的功能更有可能會被更改。

本文件包含了所有client library所需要的細節,但是client library的作者們需要提前意識到真正實現的細節在將來很有可能會有變化。

什麼是Redis叢集?

Redis叢集是一個實現分散式並且允許單點故障的Redis高階版本。

Redis叢集沒有最重要或者說中心節點,這個版本最主要的一個目標是設計一個線性可伸縮(可隨意增刪節點?)的功能。

Redis叢集為了資料的一致性可能犧牲部分允許單點故障的功能,所以當網路故障和節點發生故障時這個系統會盡力去保證資料的一致性和有效性。(這裡我們認為節點故障是網路故障的一種特殊情況)

為了解決單點故障的問題,我們同時需要masters 和 slaves。 即使主節點(master)和從節點(slave)在功能上是一致的,甚至說他們部署在同一臺伺服器上,從節點也僅用以替代故障的主節點。 實際上應該說 如果對從節點沒有read-after-write(寫並立即讀取資料 以免在資料同步過程中無法獲取資料)的需求,那麼從節點僅接受只讀操作。

已實現子集

Redis叢集會把所有的單一key儲存在非分散式版本的Redis中。對於複合操作比如求並集求交集之類則未實現。

在將來,有可能會增加一種為“Computation Node”的新型別節點。這種節點主要用來處理在叢集中multi-key的只讀操作,但是對於multi-key的只讀操作不會以叢集傳輸到Computation Node節點再進行計算的方式實現。

Redis叢集版本將不再像獨立版本一樣支援多資料庫,在叢集版本中只有database 0,並且SELECT命令是不可用的。

客戶端與服務端在Redis叢集版中的約定

在Redis叢集版本中,節點有責任/義務儲存資料和自身狀態,這其中包括把資料(key)對映到正確的節點。所有節點都應該自動探測叢集中的其他 節點,並且在發現故障節點之後把故障節點的從節點更改為主節點(原文這裡有“如果有需要” 可能是指需要設定或者說存在從節點)。

叢集節點使用TCP bus和二進位制協議進行互聯並對任務進行分派。各節點使用gossip 協議傳送ping packets給叢集其他節點以確定其他節點是否正常工作。cluster bus也可以用來在節點間執行PUB/SUB命令。

當發現叢集節點無應答的時候則會使用redirections errors -MOVED and -ASK命令並且會重定向至可用節點。理論上客戶端可隨意向叢集中任意節點傳送請求並獲得重定向,也就是說客戶端實際上並不用關心叢集的狀態。然而,客戶 端也可以快取資料對應的節點這樣可以免去服務端進行重定向的工作,這在一定程度上可以提高效率。

Keys分配模式

一個叢集可以包含最多4096個節點(但是我們建議最多設定幾百個節點)。

所有的主節點會控制4096個key空間的百分比。當叢集穩定之後,也就是說不會再更改叢集配置(更改配置指的增刪節點),那麼一個節點將只為一個hash slot服務。(但是服務節點(主節點)可以擁有多個從節點用來防止單點故障)

用來計算key屬於哪個hash slot的演算法如下:

HASH_SLOT = CRC16(key) mod 4096

Name: XMODEM (also known as ZMODEM or CRC-16/ACORN)
Width: 16 bit
Poly: 1021 (That is actually x^16 + x^12 + x^5 + 1)
Initialization: 0000
Reflect Input byte: False
Reflect Output CRC: False
Xor constant to output CRC: 0000
Output for "123456789": 31C3

這裡我們會取CRC16後的12個位元組。在我們的測試中,對於4096個slots, CRC16演算法最合適。

叢集節點特性

在叢集中每個節點都擁有唯一的名字。節點名為16進位制的160 bit隨機數,當節點獲取到名字後將被立即啟用。節點名將被永久儲存到節點設定檔案中,除非系統管理員手動刪除節點配置檔案。

節點名是叢集中每個節點的身份證明。在不更改節點ID的情況下是允許修改節點IP和地址的。cluster bus會自動透過gossip協議獲取更改後的節點設定。

每個節點可獲知其他節點的資訊包括:

  • IP 埠
  • 狀態
  • 管理的hash slots
  • cluster bus最後傳送PING的時間
  • 最後接收到PONG的時間
  • 從節點數量
  • 節點ID

無論是主節點還是從節點都可以透過CLUSTER NODES命令來獲取以上資訊
示例如下:

$ redis-cli cluster nodes
d1861060fe6a534d42d8a19aeb36600e18785e04 :0 myself - 0 1318428930 connected 0-1364
3886e65cc906bfd9b1f7e7bde468726a052d1dae 127.0.0.1:6380 master - 1318428930 1318428931 connected 1365-2729
d289c575dcbc4bdd2931585fd4339089e461a27d 127.0.0.1:6381 master - 1318428931 1318428931 connected 2730-4095

節點互動

所有節點總是允許接受來自cluster bus的連線請求,並且即使請求PING的節點是不可信的也會進行應答。然而,所有來自非叢集節點的packets都會被忽略。

只有以下兩種情況節點才會把其他節點認為是叢集的一部分:

如果一個節點使用 MEET message 介紹自己。MEET message 命令是強制其他節點把自己當成是叢集的一部分。只有系統管理員使用 CLUSTER MEET ip port 命令節點才會傳送MEET message給其他節點。

另外一種方式就是透過叢集節點間的推薦機制。例如 如果A節點知道B節點屬於叢集,而B知道C節點屬於叢集,那麼B將會傳送gossip資訊告知A:C是屬於叢集的。當A獲得gossip資訊之後就會嘗試去連線C。

這意味著,當我們以任意連線方式為叢集加入一個節點,叢集中所有節點都會自動對新節點建立信任連線。也就是說,叢集具備自動識別所有節點的功能,但是這僅發生在當系統管理強制為新節點與叢集中任意節點建立信任連線的前提下。

這個機制使得叢集系統更加健壯。

當一個節點故障時,其餘節點會嘗試連線其他所有已知的節點已確定其他節點的健壯性。

被移動資料的重定向

Redis客戶端被允許向叢集中的任意節點傳送命令,其中包括從節點。被訪問的節點將會分析命令中所需要的資料(這裡僅指請求單個key),並自己透過判斷hash slot確定資料儲存於哪個節點。

如果被請求節點擁有hash slot資料(這裡指請求資料未被遷移過 或者 hash slot在資料遷移後被重新計算過),則會直接返回結果,否則將會返回一個 MOVED 錯誤。

MOVED 錯誤如下:

GET x
-MOVED 3999 127.0.0.1:6381

這個錯誤包括請求的資料所處的 hash slot(3999) 和 資料目前所屬的IP:埠。客戶端需要透過訪問返回的IP:埠獲取資料。即使在客戶端請求並等待資料返回的過程中,叢集配置已被更改(也就是說請求的 key在配置檔案中所屬的節點ID已被重定向至新的IP:埠),目標節點依然會返回一個MOVED錯誤。

所以雖然在叢集中的節點使用節點ID來確定身份,但是map依然是靠hash slot和Redis節點的IP:埠來進行配對。

客戶端雖然不被要求但是應該嘗試去記住hash slot 3999現在已被轉移至127.0.0.1:6381。這樣的話,當一個新的命令需要從hash slot 3999獲取資料時就可以有更高的機率從hash slot獲取到正確的目標節點。

假設叢集已經足夠的穩定(不增刪節點),那麼所有的客戶端將會擁有一份hash slots對應節點的資料,這可以使整個叢集更高效,因為這樣每個命令都會直接定向到正確的節點,不需要透過節點尋找節點或者重新計算hash slot對應的節點。

叢集不下線更新配置

Redis叢集支援線上增刪節點。實際上對於系統來說,增加和刪除節點在本質上是一樣的,因為他們都是把hash slot從一個節點遷移至另外一個節點而已。

增加節點:叢集中加入一個空節點並且把hash slot從已存在的節點們移至新節點。
刪除節點:叢集刪除一個已存在節點並且把hash slot分散到已存在的其他節點中。

所以實現這個功能的核心就是遷移slots。實際上從某種觀點上來說,hash slot只不過是一堆key的合集,所以Redis叢集要做的事情只是在重分片的時候把一堆key從一個例項移動到另外一個例項。

為了清楚的瞭解這是如何實現的,我們需要先了解一下CLUSTER用來控制slots傳輸的底層命令。
這些底層命令包括:

CLUSTER ADDSLOTS slot1 [slot2] ... [slotN]
CLUSTER DELSLOTS slot1 [slot2] ... [slotN]
CLUSTER SETSLOT slot NODE node
CLUSTER SETSLOT slot MIGRATING node
CLUSTER SETSLOT slot IMPORTING node

前兩個命令 ADDSLOTS 和 DELSLOTS 是用來在Redis節點上增加/刪除slots。當hash slots被賦值之後他們會透過gossip協議在整個叢集進行廣播(例如:大喊一聲 兄弟們 我現在住在X節點 有需要我的以後請到X節點來找我)。當slots被新增,ADDSLOTS 命令是用來通知叢集其餘所有節點最高效的方法。

SETSLOT 命令是用來給把slot註冊給一個特殊的node ID(也就是說ADDSLOTS 和 DELSLOTS 對slots進行操作是不指定節點的 而SETSLOT 是會指定節點的)。另外 SETSLOT 還包含兩個特殊的狀態 MIGRATING 和 IMPORTING:

當一個slot是以 MIGRATING 狀態進行設定,那麼目標節點將在確認key存在的前提下接受這個hash slot的所有請求,否則查詢會被使用 -ASK 重定向至源節點。
當一個slot是以 IMPORTING 狀態進行設定,那麼目標節點只接受被設定過ASKING命令的所有請求,否則查詢將會透過 -MOVED錯誤重定向至真正的hash slot所有者。
(MIGRATING 和 IMPORTING 我自己也沒太看懂 所以這裡不敢保證翻譯的沒有問題)

當你第一次看到以上內容的時候或許會感到困惑,不過沒關係,現在我們來把思路理清楚。假設我們有2個Redis節點,一個叫A,另一個叫B。現在我們希望把hash slot8 從A移動到B,那麼我們執行的命令應該如下:

We send B: CLUSTER SETSLOT 8 IMPORTING A
We send A: CLUSTER SETSLOT 8 MIGRATING B

所有來自客戶端對hash slot8的查詢每次都會被導向至節點A,實際過程如下:
所有對A節點存在的資料查詢會由A節點來處理
所有對A節點不存在的資料查詢會由B節點來處理
我們會發現我們將會無法在A節點建立任何新的資料,因為會被導向B節點。為了解決這個問題,我們設計了一個叫redis-trib的特殊客戶端來保證把A節點所有存在的key遷移至B節點。
我們用以下命令來處理:

CLUSTER GETKEYSINSLOT slot count

上面的命令將會返回hash slot中 count keys。對每一個key,redis-trib都會給A節點傳送一個 MIGRATE 命令,這個命令會以一種原子的方式把key從A遷移到B(兩個節點在遷移過程中都會被鎖定)。
以下展示 MIGRATE 如何工作:

MIGRATE target_host target_port key target_database id timeout

MIGRATE 命令會先連線目標節點,並把目標key序列化後進行傳輸,當源節點收到OK返回值後會刪除源節點上的key刪除。所以從這個觀點上來看,一個key只能存在A或者B而不會同時存在與A和B。

ASK 重定向

在之前的章節我們說了一下ASK重定向,為什麼我們不能只是簡單的使用 MOVED 重定向?因為如果使用MOVED命令則有可能會為一個key輪詢叢集中所有的節點,而ASK命令只詢問下一個節點。

ASK是必要的因為在對於hash slot8的下一次查詢命令依然是傳送給A節點,我們希望客戶端先嚐試在A節點找資料然後在獲取不到的情況下再向B節點請求資料。

然後我們真正的需求是客戶端在向A節點請求資料失敗後僅嘗試向B節點請求資料而不再輪詢。節點B將只接受帶ASKING命令的IMPORTING 資料查詢。

簡單說,ASKING 命令給IMPORTING slot新增了一個只輪詢一次的標記。

Clients implementations hints

TODO Pipelining: use MULTI/EXEC for pipelining.
TODO Persistent connections to nodes.
TODO hash slot guessing algorithm.

單點故障

節點故障偵測

故障偵測使用以下方法實現:

  • 如果一個節點沒有在給定時間內回覆PING請求,則該一個節點會被其他節點設定 PFAIL 標誌(possible failure 有可能故障)
  • 如果一個節點被設定 PFAIL 標誌,那麼對目標節點設定 PFAIL 標誌的節點會在節點之間互相進行廣播通知並通知其他節點傳送PING請求
  • 如果有一個節點被設定 PFAIL 標誌,並且其他節點也認同其為 PFAIL 狀態,那麼該節點會被設定為 FAIL 狀態(故障)
  • 一旦一個節點被設定 FAIL 標誌,那麼對故障節點設定 FAIL 標誌的節點會通知其餘所有節點

所以實際只有大多數節點認同的情況下,一個節點才會被設定為故障狀態
(還在努力實現)一旦一個節點被設定為故障,那麼其他任何節點收到來自故障節點的PING或者連線請求則會返回“MARK AS FAIL”從而強制故障節點把自己設定為故障

叢集狀態偵測(目前僅實現了一部分):每當叢集配置檔案發生變更,所有叢集節點都會重新掃描節點列表(這可以是由更改一個hash slot 或者只是一個節點故障造成的)

每個被掃描的節點會返回以下狀態中的一個:

  • FAIL:節點故障
  • OK:節點正常

這意味著Redis叢集被設計為有能力拒絕對故障節點的查詢。然而這裡有一個特例,就是一個節點從被設定為 PFAIL 到被設定為 FAIL 是有時間差的,如果僅僅是被設定為 PFAIL 還是有可能對該節點嘗試連線

另外,Redis叢集將不再支援MULTI/EXEC批次方法

從節點推舉制度(未實現)

每個主節點可以擁有0個或者多個從節點。當主節點發生故障的時候,從節點有責任/義務推舉自己成為主節點。假設我們有A1,A2,A3三個節點,A1是主節點並且A2和A3為A1的從節點

如果A1發生故障並且長時間未回覆PING請求,那麼其他節點將會將A1標記為故障節點。當這種情況發生的時候,第一個從節點將會嘗試推舉自己為主節點。

定義第一個從節點非常簡單。取所有從節點中節點ID最小的那個。如果第一個從節點也被標記為故障,那麼就由第二個從節點推舉自己,以此類推。

實際流程是:叢集配置被變更(節點故障導致的),故障節點所有的從節點檢測自己是否是第一個從節點。從節點在升級為主節點後會通知其他節點更改配置

保護模式(未實現)

如果部分節點由於網路原因被隔離(比如斷網),則這些節點會停止判斷其他節點是否正常,而會開始從節點推舉或者其他操作去更改叢集配置。為了防止這 種情況發生,節點間一旦發現大部分節點在一段時間內被標記為 PFAIL 或者 FAIL 狀態則會立即讓叢集啟動保護模式以阻止叢集採取任何行動(更改配置)。

一旦叢集狀態恢復正常則保護模式會被取消

主節點多數原則(未完成)

對於發生網路故障的情況,2個或者更多的分片有能力處理所有的hash slots。而這會影響叢集資料的一致性,所以網路故障應該導致0或者只有1個分割槽可用。

為了強制此規則生效,所有符合主節點多數原則的節點應該強制不處理任何命令。

Publish/Subscribe(已實現,但是會重定義)

在一個Redis叢集中,所有節點都被允許訂閱其他節點或者對其他節點進行廣播。叢集系統會保證所有的廣播通知給所有節點。

目前的實現僅僅是簡單的一一進行廣播,但是在某種程度上廣播應該使用bloom filters或者其他演算法進行最佳化。

文章出處:http://blog.nosqlfan.com/html/3302.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7916042/viewspace-1057409/,如需轉載,請註明出處,否則將追究法律責任。

相關文章