歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux教程 >> Redis集群功能說明

Redis集群功能說明

日期:2017/2/27 16:00:34   编辑:Linux教程

介紹

這篇文檔主要是為了說明正在進展中的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或者其他算法進行優化。

Copyright © Linux教程網 All Rights Reserved