詳細介紹redis的集群功能,帶你了解真正意義上的分布式
Redis 集群是一個分布式(distributed)、容錯(fault-tolerant)的 Redis 實現, 集群可以使用的功能是普通單機 Redis 所能使用的功能的一個子集(subset)。
Redis 集群中不存在中心(central)節點或者代理(proxy)節點, 集群的其中一個主要設計目標是達到線性可擴展性(linear scalability)。
Redis 集群為了保證一致性(consistency)而犧牲了一部分容錯性: 系統會在保證對網絡斷線(netsplit)和節點失效(node failure)具有有限(limited)抵抗力的前提下,盡可能地保持數據的一致性。
一、四個集群特性:
(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬。
(2)節點的fail是通過集群中超過半數的節點檢測失效時才生效。
(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集群所有節點,連接集群中任何一個可用節點即可。
(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value
二、Redis 集群實現的功能子集:
Redis集群實現了單機 Redis 中, 所有處理單個數據庫鍵的命令。針對多個數據庫鍵的復雜計算操作, 比如集合的並集操作、合集操作沒有被實現,那些理論上需要使用多個節點的多個數據庫鍵才能完成的命令也沒有被實現。在將來, 用戶也許可以通過 MIGRATE COPY 命令,在集群的計算節點(computation node)中執行針對多個數據庫鍵的只讀操作, 但集群本身不會去實現那些需要將多個數據庫鍵在多個節點中移來移去的復雜多鍵命令。
Redis 集群不像單機Redis 那樣支持多數據庫功能, 集群只使用默認的 0 號數據庫, 並且不能使用 SELECT 命令。
三、Redis 集群協議中的客戶端和服務器:
Redis 集群中的節點有以下責任:
1. 持有鍵值對數據。
2. 記錄集群的狀態,包括鍵到正確節點的映射(mappingkeys to right nodes)。
3. 自動發現其他節點,識別工作不正常的節點,並在有需要時,在從節點中選舉出新的主節點。
為了執行以上列出的任務, 集群中的每個節點都與其他節點建立起了“集群連接(cluster bus)”, 該連接是一個 TCP 連接, 使用二進制協議進行通訊。
節點之間使用Gossip 協議 來進行以下工作:
1. 傳播(propagate)關於集群的信息,以此來發現新的節點。
2. 向其他節點發送 PING 數據包,以此來檢查目標節點是否正常運作。
3. 在特定事件發生時,發送集群信息。
4. 除此之外, 集群連接還用於在集群中發布或訂閱信息。
因為集群節點不能代理(proxy)命令請求, 所以客戶端應該在節點返回 -MOVED 或者 -ASK 轉向(redirection)錯誤時,自行將命令請求轉發至其他節點。因為客戶端可以自由地向集群中的任何一個節點發送命令請求, 並可以在有需要時, 根據轉向錯誤所提供的信息, 將命令轉發至正確的節點,所以在理論上來說, 客戶端是無須保存集群狀態信息的。不過, 如果客戶端可以將鍵和節點之間的映射信息保存起來, 可以有效地減少可能出現的轉向次數, 籍此提升命令執行的效率。
四、鍵分布模型
Redis 集群的鍵空間被分割為 16384 個槽(slot), 集群的最大節點數量也是 16384 個。
推薦的最大節點數量為 1000 個左右。每個主節點都負責處理 16384 個哈希槽的其中一部分。
當我們說一個集群處於“穩定”(stable)狀態時, 指的是集群沒有在執行重配(reconfiguration)操作,每個哈希槽都只由一個節點進行處理。重配置指的是將某個/某些槽從一個節點移動到另一個節點。一個主節點可以有任意多個從節點,這些從節點用於在主節點發生網絡斷線或者節點失效時, 對主節點進行替換。
五、集群節點屬性:
每個節點在集群中都有一個獨一無二的 ID , 該 ID 是一個十六進制表示的 160 位隨機數, 在節點第一次啟動時由 /dev/urandom 生成。
節點會將它的 ID 保存到配置文件, 只要這個配置文件不被刪除,節點就會一直沿用這個 ID 。節點 ID 用於標識集群中的每個節點。一個節點可以改變它的 IP 和端口號, 而不改變節點 ID 。集群可以自動識別出 IP/端口號的變化, 並將這一信息通過 Gossip 協議廣播給其他節點知道。
以下是每個節點都有的關聯信息, 並且節點會將這些信息發送給其他節點:
1. 節點所使用的 IP 地址和 TCP 端口號。
2. 節點的標誌(flags)。
3. 節點負責處理的哈希槽。
4. 節點最近一次使用集群連接發送 PING 數據包(packet)的時間。
5. 節點最近一次在回復中接收到 PONG 數據包的時間。
6. 集群將該節點標記為下線的時間。
7. 該節點的從節點數量。
8. 如果該節點是從節點的話,那麽它會記錄主節點的節點 ID 。如果這是一個主節點的話,那麽主節點 ID 這一欄的值為 0000000 。
以上信息的其中一部分可以通過向集群中的任意節點(主節點或者從節點都可以)發送 CLUSTER NODES 命令來獲得。
六、節點握手:
節點總是應答(accept)來自集群連接端口的連接請求,並對接收到的 PING 數據包進行回復, 即使這個 PING 數據包來自不可信的節點。然而,除了 PING 之外, 節點會拒絕其他所有並非來自集群節點的數據包。要讓一個節點承認另一個節點同屬於一個集群,只有以下兩種方法:
1. 一個節點可以通過向另一個節點發送 MEET 信息,來強制讓接收信息的節點承認發送信息的節點為集群中的一份子。 一個節點僅在管理員顯式地向它發送CLUSTER MEET ipport 命令時, 才會向另一個節點發送 MEET 信息。
2. 如果一個可信節點向另一個節點傳播第三者節點的信息, 那麽接收信息的那個節點也會將第三者節點識別為集群中的一份子。也即是說, 如果 A 認識 B , B 認識 C , 並且 B 向 A 傳播關於 C 的信息, 那麽 A 也會將 C 識別為集群中的一份子, 並嘗試連接 C 。
這意味著如果我們將一個/一些新節點添加到一個集群中, 那麽這個/這些新節點最終會和集群中已有的其他所有節點連接起來。
這說明只要管理員使用 CLUSTER MEET 命令顯式地指定了可信關系,集群就可以自動發現其他節點。這種節點識別機制通過防止不同的 Redis 集群因為 IP 地址變更或者其他網絡事件的發生而產生意料之外的聯合(mix), 從而使得集群更具健壯性。當節點的網絡連接斷開時,它會主動連接其他已知的節點。
七、MOVED 轉向:
一個 Redis 客戶端可以向集群中的任意節點(包括從節點)發送命令請求。節點會對命令請求進行分析, 如果該命令是集群可以執行的命令, 那麽節點會查找這個命令所要處理的鍵所在的槽。如果要查找的哈希槽正好就由接收到命令的節點負責處理,那麽節點就直接執行這個命令。另一方面, 如果所查找的槽不是由該節點處理的話, 節點將查看自身內部所保存的哈希槽到節點 ID 的映射記錄,並向客戶端回復一個 MOVED 錯誤。
即使客戶端在重新發送 GET 命令之前, 等待了非常久的時間,以至於集群又再次更改了配置, 使得節點 127.0.0.1:6381 已經不再處理槽 3999 , 那麽當客戶端向節點 127.0.0.1:6381 發送 GET 命令的時候, 節點將再次向客戶端返回 MOVED 錯誤, 指示現在負責處理槽 3999 的節點。
雖然我們用 ID 來標識集群中的節點, 但是為了讓客戶端的轉向操作盡可能地簡單,,節點在 MOVED 錯誤中直接返回目標節點的 IP 和端口號,而不是目標節點的 ID 。但一個客戶端應該記錄(memorize)下“槽 3999 由節點 127.0.0.1:6381 負責處理“這一信息, 這樣當再次有命令需要對槽 3999 執行時, 客戶端就可以加快尋找正確節點的速度。
註意, 當集群處於穩定狀態時, 所有客戶端最終都會保存有一個哈希槽至節點的映射記錄(map of hash slots to nodes), 使得集群非常高效: 客戶端可以直接向正確的節點發送命令請求, 無須轉向、代理或者其他任何可能發生單點故障(single point failure)的實體(entiy)。
除了 MOVED轉向錯誤之外, 一個客戶端還應該可以處理稍後介紹的 ASK 轉向錯誤。
八、集群在線重配置:
Redis 集群支持在集群運行的過程中添加或者移除節點。實際上, 節點的添加操作和節點的刪除操作可以抽象成同一個操作,那就是, 將哈希槽從一個節點移動到另一個節點:添加一個新節點到集群, 等於將其他已存在節點的槽移動到一個空白的新節點裏面。從集群中移除一個節點, 等於將被移除節點的所有槽移動到集群的其他節點上面去。
因此, 實現Redis 集群在線重配置的核心就是將槽從一個節點移動到另一個節點的能力。 因為一個哈希槽實際上就是一些鍵的集合, 所以 Redis 集群在重哈希(rehash)時真正要做的, 就是將一些鍵從一個節點移動到另一個節點。
要理解Redis 集群如何將槽從一個節點移動到另一個節點, 我們需要對 CLUSTER 命令的各個子命令進行介紹,這些命理負責管理集群節點的槽轉換表(slots translation table)。
以下是CLUSTER 命令可用的子命令:
最開頭的兩條命令ADDSLOTS 和 DELSLOTS 分別用於向節點指派(assign)或者移除節點,當槽被指派或者移除之後, 節點會將這一信息通過 Gossip 協議傳播到整個集群。 ADDSLOTS 命令通常在新創建集群時, 作為一種快速地將各個槽指派給各個節點的手段來使用。
CLUSTERSETSLOT slot NODE node 子命令可以將指定的槽 slot 指派給節點node 。
至於CLUSTER SETSLOT slot MIGRATING node 命令和 CLUSTER SETSLOTslot IMPORTING node 命令, 前者用於將給定節點 node 中的槽 slot 遷移出節點, 而後者用於將給定槽 slot導入到節點 node :
當一個槽被設置為MIGRATING 狀態時, 原來持有這個槽的節點仍然會繼續接受關於這個槽的命令請求, 但只有命令所處理的鍵仍然存在於節點時, 節點才會處理這個命令請求。
如果命令所使用的鍵不存在與該節點, 那麽節點將向客戶端返回一個 -ASK 轉向(redirection)錯誤, 告知客戶端, 要將命令請求發送到槽的遷移目標節點。
當一個槽被設置為IMPORTING 狀態時, 節點僅在接收到 ASKING 命令之後, 才會接受關於這個槽的命令請求。
如果客戶端沒有向節點發送 ASKING 命令, 那麽節點會使用 -MOVED 轉向錯誤將命令請求轉向至真正負責處理這個槽的節點。
上面關於MIGRATING 和 IMPORTING 的說明有些難懂, 讓我們用一個實際的實例來說明一下。
假設現在, 我們有 A 和 B 兩個節點, 並且我們想將槽8 從節點 A 移動到節點 B , 於是我們:
向節點 B 發送命令 CLUSTER SETSLOT 8 IMPORTING A
向節點 A 發送命令 CLUSTER SETSLOT 8 MIGRATING B
每當客戶端向其他節點發送關於哈希槽 8 的命令請求時, 這些節點都會向客戶端返回指向節點 A 的轉向信息:
如果命令要處理的鍵已經存在於槽 8 裏面, 那麽這個命令將由節點 A 處理。
如果命令要處理的鍵未存在於槽 8 裏面(比如說,要向槽添加一個新的鍵), 那麽這個命令由節點 B 處理。
這種機制將使得節點 A 不再創建關於槽 8 的任何新鍵。
與此同時, 一個特殊的客戶端 redis-trib 以及 Redis 集群配置程序(configuration utility)會將節點 A 中槽 8 裏面的鍵移動到節點 B 。
鍵的移動操作由以下兩個命令執行:
CLUSTERGETKEYSINSLOT slot count
上面的命令會讓節點返回 count 個 slot 槽中的鍵, 對於命令所返回的每個鍵, redis-trib 都會向節點 A 發送一條 MIGRATE 命令, 該命令會將所指定的鍵原子地(atomic)從節點 A 移動到節點 B (在移動鍵期間,兩個節點都會處於阻塞狀態,以免出現競爭條件)。
以下為MIGRATE 命令的運作原理:
MIGRATEtarget_host target_port key target_database id timeout
執行MIGRATE 命令的節點會連接到 target 節點, 並將序列化後的 key 數據發送給 target , 一旦 target 返回 OK , 節點就將自己的 key 從數據庫中刪除。
從一個外部客戶端的視角來看, 在某個時間點上, 鍵 key 要麽存在於節點 A , 要麽存在於節點 B , 但不會同時存在於節點 A 和節點 B 。
因為 Redis集群只使用 0 號數據庫, 所以當 MIGRATE 命令被用於執行集群操作時, target_database 的值總是 0 。
target_database參數的存在是為了讓 MIGRATE 命令成為一個通用命令, 從而可以作用於集群以外的其他功能。
我們對MIGRATE 命令做了優化, 使得它即使在傳輸包含多個元素的列表鍵這樣的復雜數據時, 也可以保持高效。
不過, 盡管MIGRATE 非常高效, 對一個鍵非常多、並且鍵的數據量非常大的集群來說, 集群重配置還是會占用大量的時間, 可能會導致集群沒辦法適應那些對於響應時間有嚴格要求的應用程序。
九、ASK 轉向:
在之前介紹 MOVED 轉向的時候, 我們說除了 MOVED 轉向之外, 還有另一種 ASK 轉向。當節點需要讓一個客戶端長期地(permanently)將針對某個槽的命令請求發送至另一個節點時,節點向客戶端返回 MOVED 轉向。另一方面, 當節點需要讓客戶端僅僅在下一個命令請求中轉向至另一個節點時, 節點向客戶端返回 ASK 轉向。
比如說, 在我們上一節列舉的槽 8 的例子中, 因為槽 8 所包含的各個鍵分散在節點 A 和節點 B 中, 所以當客戶端在節點 A 中沒找到某個鍵時, 它應該轉向到節點 B 中去尋找, 但是這種轉向應該僅僅影響一次命令查詢,而不是讓客戶端每次都直接去查找節點 B : 在節點 A 所持有的屬於槽 8 的鍵沒有全部被遷移到節點 B 之前, 客戶端應該先訪問節點 A , 然後再訪問節點 B 。因為這種轉向只針對 16384 個槽中的其中一個槽, 所以轉向對集群造成的性能損耗屬於可接受的範圍。
因為上述原因, 如果我們要在查找節點 A 之後, 繼續查找節點 B , 那麽客戶端在向節點 B 發送命令請求之前, 應該先發送一個 ASKING 命令, 否則這個針對帶有IMPORTING 狀態的槽的命令請求將被節點 B 拒絕執行。接收到客戶端 ASKING 命令的節點將為客戶端設置一個一次性的標誌(flag), 使得客戶端可以執行一次針對 IMPORTING 狀態的槽的命令請求。從客戶端的角度來看, ASK 轉向的完整語義(semantics)如下:
1. 如果客戶端接收到 ASK 轉向, 那麽將命令請求的發送對象調整為轉向所指定的節點。
2. 先發送一個 ASKING 命令,然後再發送真正的命令請求。
3. 不必更新客戶端所記錄的槽 8 至節點的映射: 槽 8 應該仍然映射到節點 A , 而不是節點 B 。
一旦節點 A 針對槽 8 的遷移工作完成, 節點 A 在再次收到針對槽 8 的命令請求時, 就會向客戶端返回 MOVED 轉向, 將關於槽 8 的命令請求長期地轉向到節點 B 。
註意, 即使客戶端出現 Bug , 過早地將槽 8 映射到了節點 B 上面, 但只要這個客戶端不發送 ASKING 命令, 客戶端發送命令請求的時候就會遇上 MOVED 錯誤, 並將它轉向回節點 A 。
十、容錯:
節點失效檢測,以下是節點失效檢查的實現方法:
1. 當一個節點向另一個節點發送 PING 命令, 但是目標節點未能在給定的時限內返回 PING 命令的回復時, 那麽發送命令的節點會將目標節點標記為 PFAIL(possible failure,可能已失效)。等待 PING 命令回復的時限稱為“節點超時時限(node timeout)”, 是一個節點選項(node-wise setting)。
2. 每次當節點對其他節點發送 PING 命令的時候,它都會隨機地廣播三個它所知道的節點的信息, 這些信息裏面的其中一項就是說明節點是否已經被標記為 PFAIL或者 FAIL 。
當節點接收到其他節點發來的信息時, 它會記下那些被其他節點標記為失效的節點。這稱為失效報告(failure report)。
3. 如果節點已經將某個節點標記為 PFAIL , 並且根據節點所收到的失效報告顯式,集群中的大部分其他主節點也認為那個節點進入了失效狀態, 那麽節點會將那個失效節點的狀態標記為 FAIL 。
4. 一旦某個節點被標記為 FAIL , 關於這個節點已失效的信息就會被廣播到整個集群,所有接收到這條信息的節點都會將失效節點標記為 FAIL 。
簡單來說, 一個節點要將另一個節點標記為失效, 必須先詢問其他節點的意見, 並且得到大部分主節點的同意才行。因為過期的失效報告會被移除,所以主節點要將某個節點標記為 FAIL 的話, 必須以最近接收到的失效報告作為根據。
從節點選舉:一旦某個主節點進入 FAIL 狀態, 如果這個主節點有一個或多個從節點存在,那麽其中一個從節點會被升級為新的主節點, 而其他從節點則會開始對這個新的主節點進行復制。
新的主節點由已下線主節點屬下的所有從節點中自行選舉產生,以下是選舉的條件:
1. 這個節點是已下線主節點的從節點。
2. 已下線主節點負責處理的槽數量非空。
3. 從節點的數據被認為是可靠的, 也即是, 主從節點之間的復制連接(replication link)的斷線時長不能超過節點超時時限(nodetimeout)乘以REDIS_CLUSTER_SLAVE_VALIDITY_MULT 常量得出的積。
如果一個從節點滿足了以上的所有條件, 那麽這個從節點將向集群中的其他主節點發送授權請求, 詢問它們,是否允許自己(從節點)升級為新的主節點。
如果發送授權請求的從節點滿足以下屬性, 那麽主節點將向從節點返FAILOVER_AUTH_GRANTED 授權, 同意從節點的升級要求:
1. 發送授權請求的是一個從節點, 並且它所屬的主節點處於 FAIL狀態。
2. 在已下線主節點的所有從節點中, 這個從節點的節點 ID 在排序中是最小的。
3. 這個從節點處於正常的運行狀態: 它沒有被標記為 FAIL 狀態,也沒有被標記為 PFAIL 狀態。
一旦某個從節點在給定的時限內得到大部分主節點的授權,它就會開始執行以下故障轉移操作:
1. 通過 PONG 數據包(packet)告知其他節點, 這個節點現在是主節點了。
2. 通過 PONG 數據包告知其他節點, 這個節點是一個已升級的從節點(promoted slave)。
3. 接管(claiming)所有由已下線主節點負責處理的哈希槽。
4. 顯式地向所有節點廣播一個 PONG 數據包, 加速其他節點識別這個節點的進度,而不是等待定時的 PING / PONG 數據包。
所有其他節點都會根據新的主節點對配置進行相應的更新:
所有被新的主節點接管的槽會被更新。
已下線主節點的所有從節點會察覺到 PROMOTED 標誌,並開始對新的主節點進行復制。
如果已下線的主節點重新回到上線狀態, 那麽它會察覺到PROMOTED 標誌, 並將自身調整為現任主節點的從節點。
在集群的生命周期中, 如果一個帶有 PROMOTED 標識的主節點因為某些原因轉變成了從節點,那麽該節點將丟失它所帶有的 PROMOTED 標識。
詳細介紹redis的集群功能,帶你了解真正意義上的分布式