標籤:redis 高效能分布式緩衝 叢集 nosql 雜湊槽分區
Redis也可通過叢集來實現分布式,通過分區進行資料共用,並提供複製和容錯移轉。當前Redis版本的叢集功能還沒有正式發布,目前只是一個不穩定的分支,據說快要正式發布了。
添加叢集節點
伺服器節點通過執行CLUSTER MEET <ip> <port>命令把指定的伺服器添加到當前叢集中,通過CLUSTER NODES來查詢當前叢集中的所有節點資訊,當cluster-enabled配置選項設成yes時,說明該伺服器開啟了叢集模式,開啟叢集模式的節點才能被添加到叢集。
在執行serverCron函數時,叢集節點比單擊節點多執行一個clusterCron函數,redis使用clusterNode、clusterLink、clusterState等結構來記錄集群資訊。
CLUSTER MEET命令實現:
- 在節點B向節點A發送CLUSTER MEET命令時,節點A伺服器通過參數解析出目標伺服器B的ip和連接埠。
- A為指定的ip和連接埠節點B建立一個clusterNode結構並且添加到clusterState.nodes字典中,並且發送一條MEET訊息到B。
- B接收到A的MEET訊息之後為A建立一個clusterNode結構並把它添加到clusterState.nodes字典中,並且回複PONG訊息通知A已經收到了MEET訊息。
- A收到PONG訊息之後回複一條PING通知B已經收到了PONG回複,至此握手結束。之後節點A將節點B的訊息通過Gossip協議傳播給叢集中其它節點,讓其它節點和B進行握手,最終節點B會被叢集中所有節點認識。
鍵槽Redis叢集通過分區的方式來儲存資料庫(叢集節點只能使用0號資料庫)中的鍵值對:整個資料庫被分成16384(2的14次方)個槽,每個鍵都屬於其中某個槽的一個,計算鍵所在的槽,通過計算鍵的CRC-16校正和,把校正和和16383做與運算。當所有槽都有節點處理時,叢集處於上線狀態(ok),否則處於下線狀態(fail)。
clusterNode結構中的slots屬性記錄了節點處理了哪些槽,numslot屬性記錄了節點處理槽的個數。slots是一個長度16384的位元組,數組中的第i為為1表示i號槽被當前節點處理,為0表示i號不是該節點處理,對該數組的取值和設值複雜度都是O(1)。節點會把slots數組傳播給叢集中其它節點告知當前節點負責處理的槽。
clusterState.slots記錄了叢集中所有槽的指派資訊,該數組是一個儲存clusterNode指標的數組如果slots[i]指向NULL說明該槽尚未被指派給任何節點,如果指向一個clusterNode結構,說明i號槽被指派給了當前clusterNode結構代表的節點。
同時儲存clusterNode.slots和clusterState.slots的原因是為了提高某些情境的查詢效率,如果不儲存clusterNode.slots,那麼在把當前節點所處理的槽傳播給其它節點時只能挨個遍曆clusterState.slots,同樣地,如果不儲存clusterState.slots,當想知道某個槽被那個節點處理時需要遍曆clusterState.nodes所有結構並檢查它們的slots數組。
同時clusterState結構中有一個跳躍表屬性slots_to_keys來儲存槽和鍵的映射關係,通過這個屬性可以很方便實現CLUSTER GETKEYSINSLOT <slot> <count>命令,返回最多count個屬於slot的資料庫鍵。
通過執行CLUSTER ADDSLOTS <slot>命令指派槽給當前節點,這個命令接受一個或多個槽作為參數,將所有輸入的槽指派給接受該命令的節點負責,這個命令的實現如下:
1、檢查參數中的槽是否存在已經被其它節點處理的,如果存在直接返回錯誤。
2、對i號槽,clusterState.slots[i]設成當前節點對應的clusterNode,並且更新clusterNode的slots數組。
叢集中執行命令1、如果鍵所在的槽被指派給了當前節點,節點直接執行命令。
2、如果鍵所在的槽沒有指派給當前節點,節點給用戶端返回一個MOVED錯誤,指引用戶端轉向正確的結點。
3、用戶端根據MOVED命令帶回的ip和port參數把命令發到目標節點重試。
重新分區叢集可以通過重新分區操作將任意已指派給某個節點的槽改為指向另外一個節點,重新分區操作通過叢集管理軟體redis-trib負責執行,步驟如下:
- redis-trib對目標節點發送CLUSTER SETSLOT <slot> IMPORTING <source_id>命令,讓目標節點準備好從源節點匯入屬於槽slot的鍵值對。
- redis-trib對源節點發送CLUSTER SETSLOT <slot> MIGRATING <target_id>命令,讓源節點準備好將屬於槽slot的鍵值對遷移到目標節點。
- redis-trib向源節點發送CLUSTER GETKEYSINSLOT <slot> <count>命令,獲得最多count個屬於槽slot的鍵值對。
- 對步驟3獲得的每個鍵名,redis-trib都想源節點發送一個MIGRATE <target_ip> <target_port> <key_name> 0 <timeout>命令,將被選中的鍵原子地從源節點遷移到目標節點。
- 重複步驟3、4,直到源節點儲存的所有屬於槽slot的鍵值對都被遷移至目標節點為止。
- redis-trib向叢集中任意一個節點發送CLUSTER SETSLOT <slot> NODE <target_id>,將槽slot指派給目標節點,這一指派訊息會通過訊息發送至整個叢集,最終叢集中所有節點都會直到槽slot已經指派給了目標節點。
如果重新分區涉及到多個槽,那麼在redis-trib上重複上面過程。
clusterState結構中定義了clusterNode* importing_slots_from[16384]記錄當前節點正在從其它節點匯入的槽,如果importing_slots_from[i]為不為NULL,表示i號槽正在被當前節點匯入,它指向的clusterNode代表i號槽的源節點。
clusterState結構中的clusterNode* migrating_slots_to[16384]數組記錄了當前節點正在遷移至其它節點的槽,如果migrating_slots_to[i]不為NULL,說明當前節點正在遷移i號槽到目標節點,指向的clusterNode結構代表了目標節點。
ASK錯誤在重新分區的過程中,當用戶端向源節點發送一個資料庫鍵有關的命令時,並且鍵所在的槽正在被遷移:
- 源節點在本節點的資料庫中尋找指定的鍵,如果找到直接執行用戶端發送的命令。
- 如果找不到,說明鍵已經被遷移到目標節點,源節點向用戶端返回一個ASK錯誤,指引用戶端轉向目標節點。
- 用戶端執行ASKING命令道目標節點,目標節點接收到ASKING命令之後會開啟REDIS_ASKING標識。
- 用戶端重新之前發送的命令道目標節點。
ASK和MOVED的區別:
- MOVED錯誤表示當前節點不是當前鍵所在槽的處理節點,之後用戶端每次遇到關於該槽的命令請求時應該直接將請求按發送至MOVED所指向的結點。
- ASK錯誤是臨時的,用戶端遇到ASK錯誤之後,只是本次請求臨時轉向ASK指定的節點,下次請求時還是繼續訪問最開始訪問的節點。
複製與容錯移轉可以給叢集中的主節點設定從節點,從節點複製主節點,當主節點下線之後,叢集推選出一個從節點作為新的主節點來處理槽。通過執行CLUSTER REPLICATION <node_id>來設定從節點,當伺服器接收到該命令之後,該節點讓自己成為指定節點的從節點,並且開始對主節點進行複製:
- 接收到該命令的節點首先會在clusterState.nodes字典中找到node_id所對應的clusterNode結構,並將自己的clusterState.myself.slaveof指標指向這個結構,記錄當前節點正在複製的主節點。
- 然後節點修改clusterState.myself.flags屬性,關閉原本的REDIS_NODE_MASTER標識,開啟REDIS_NODE_SLAVE標識,表示該節點已經變成了從節點。
- 根據clusterState.myself.slaveof指向的clusterNode結構儲存的ip地址和連接埠號碼,對主節點進行複製,相當於對從節點發送了SLAVEOF <master_ip> <master_port>命令。
故障檢測叢集中每個節點都會定期向叢集中其它節點發送PING訊息,以此來檢查對方是否線上,如果接收PING訊息的結點在規定的時間內沒有返回PONG訊息,那麼發送PING訊息的節點會將接收訊息的PING節點標記為疑似下線(PFAIL),修改節點對應的clusterNode結構的flags屬性,開啟REDIS_NODE_PFAIL標識。之後節點A會把節點B的疑似下線訊息傳播給叢集中其它節點,當結點C通過訊息獲知節點B進入疑似下線狀態時,把節點A發送的節點B的下線報告記錄到B對應的clusterNode結構的fail_reports鏈表中。當一個叢集中半數以上負責處理槽的主節點都將某個主節x點報告為疑似下線時,那麼這個主節點將會被標記為下線(FAIL),並發送一條節點x的FAIL訊息通知其它主節點主節點x已下線。
容錯移轉當叢集檢測到故障之後,進行容錯移轉,容錯移轉步驟:
- 從下線的主節點中的從節點中推選一個新的主節點,和Sentinel選舉領頭類似,採用Raft演算法。
- 被選中的從節點執行SLAVEOF no one命令,成為新的主節點。
- 新的主節點撤銷所有堆已下線主節點的槽指派,並將這些槽全部指派給自己。
- 新的主節點向叢集中廣播一條PONG訊息,通知其它節點本節點已經由從節點變成了主節點。
- 新的主節點開始接收和自己負責處理的槽有關的命令,容錯移轉完成。
叢集訊息叢集中節點訊息主要有五種:
- MEET:將訊息接收這添加到叢集中。
- PING:叢集中每隔一秒會從已知節點列表中隨機選出五個節點,然後對這五個中最長時間沒有發送過PING訊息的節點發送PING訊息,以此來檢測節點是否線上。此外,節點A最後以此收到節點B發送的PONG訊息的時間距離目前時間超過了節點A的cluster-node-timeout選項設定時間長度的一半,那麼節點A也會向節點B發送PING訊息,防止節點A因為長時間沒有隨機選中節點B作為PING訊息的發送對象而導致節點B的資訊更新滯後。
- PONG:MEET和PING的回複訊息。另外一個節點也可以通過PONG訊息通知叢集中其它節點本節點已經由從節點升級成了主節點。
- FAIL:當一個主節點A判斷另一個主節點B已經進入FAIL狀態時,節點A會向叢集廣播一條關於B的FAIL訊息,所有收到該訊息的節點都會把B標記為已下線。這時不使用Gossip協議的原因是Gossip協議會有延遲需要一段時間才能傳播至整個叢集,而當結點已下線時需要儘快完成容錯移轉。
- PUBLISH:當結點收到一個PUBLISH命令時,節點會執行該命令,並向叢集廣播一條PUBLISH訊息,所有接收到這條PUBLISH訊息額節點都會執行相同的命令。
《Redis設計與實現》學習筆記-叢集