Redis Cluster原理分析
文章較長,如需轉載可分段。轉載請標明作者以及文章來源,謝謝!
作者介紹
姓名:李航
分享時間:2016年8月
這次主要是給大家分享的提綱如下:
1.簡介
2.集群通信
3.數據分布及槽信息
4.數據遷移
5.通信故障
1.簡介
繼上次分享的Redis服務平臺化之路,這次著重來分享下Redis Cluster淺析,歡迎大家互相多交流學習。
Redis Cluster是一個高性能高可用的分布式系統。由多個Redis實例組成的整體,數據按照Slot存儲分布在多個Redis實例上,通過Gossip協議來進行節點之間通信。
Redis Cluster功能特點如下:
1)所有的節點相互連接
2)集群消息通信通過集群總線通信,,集群總線端口大小為客戶端服務端口+10000,這個10000是固定值
3)節點與節點之間通過二進制協議進行通信
4)客戶端和集群節點之間通信和通常一樣,通過文本協議進行
5)集群節點不會代理查詢
6)數據按照Slot存儲分布在多個Redis實例上
7)集群節點掛掉會自動故障轉移
8)可以相對平滑擴/縮容節點
2.集群通信
2.1 CLUSTER MEET
需要組建一個真正的可工作的集群,我們必須將各個獨立的節點連接起來,構成一個包含多個節點的集群。
連接各個節點的工作使用CLUSTER MEET命令來完成。
CLUSTER MEET <ip> <port>
CLUSTER?MEET命令實現:
1)節點A會為節點B創建一個clusterNode結構,并將該結構添加到自己的clusterState.nodes字典里面。
2)節點A根據CLUSTER MEET命令給定的IP地址和端口號,向節點B發送一條MEET消息。
3)節點B接收到節點A發送的MEET消息,節點B會為節點A創建一個clusterNode結構,并將該結構添加到自己的clusterState.nodes字典里面。
4)節點B向節點A返回一條PONG消息。
5)節點A將受到節點B返回的PONG消息,通過這條PONG消息節點A可以知道節點B已經成功的接收了自己發送的MEET消息。
6)之后,節點A將向節點B返回一條PING消息。
7)節點B將接收到的節點A返回的PING消息,通過這條PING消息節點B可以知道節點A已經成功的接收到了自己返回的PONG消息,握手完成。
8)之后,節點A會將節點B的信息通過Gossip協議傳播給集群中的其他節點,讓其他節點也與節點B進行握手,最終,經過一段時間后,節點B會被集群中的所有節點認識。
2.2集群消息處理clusterProcessPacket
1)更新接收消息計數器
2)查找發送者節點并且不是handshake節點
3)更新自己的epoch和slave的offset信息
4)處理MEET消息,使加入集群
5)從goosip中發現未知節點,發起handshake
6)對PING,MEET回復PONG
7)根據收到的心跳信息更新自己clusterState中的master-slave,slots信息
8)對FAILOVER_AUTH_REQUEST消息,檢查并投票
9)處理FAIL,FAILOVER_AUTH_ACK,UPDATE信息
2.3定時任務clusterCron
定時任務clusterCron
1)對handshake節點建立Link,發送Ping或Meet
2)向隨機幾點發送Ping
3)如果是從查看是否需要做Failover
4)統計并決定是否進行slave的遷移,來平衡不同master的slave數
5)判斷所有pfail報告數是否過半數
2.4心跳數據
發送消息頭信息Header
1)所負責slots的信息
2)主從信息
3)ip port信息
4)狀態信息
發送其他節點Gossip信息
1)ping_sent, pong_received
2)ip, port信息
3)狀態信息,比如發送者認為該節點已經不可達,會在狀態信息中標記其為PFAIL或FAIL
clusterMsg結構的currentEpoch、sender、myslots等屬性記錄了發送者自身的節點信息,接收者會根據這些信息,在自己的clusterState.nodes字典里找到發送者對應的clusterNode結構,并對結構進行更新。
Redis集群中的各個節點通過Gossip協議來交換各自關于不同節點的狀態信息,其中Gossip協議由MEET、PING、PONG三種消息實現,這三種消息的正文都由兩個clusterMsgDataGossip結構組成。
每次發送MEET、PING、PONG消息時,發送者都從自己的已知節點列表中隨機選出兩個節點(可以是主節點或者從節點),并將這兩個被選中節點的信息分別保存到兩個結構中。
當接收者收到消息時,接收者會訪問消息正文中的兩個結構,并根據自己是否認識clusterMsgDataGossip結構中記錄的被選中節點進行操作:
1.如果被選中節點不存在于接收者的已知節點列表,那么說明接收者是第一次接觸到被選中節點,接收者將根據結構中記錄的IP地址和端口號等信息,與被選擇節點進行握手。
2.如果被選中節點已經存在于接收者的已知節點列表,那么說明接收者之前已經與被選中節點進行過接觸,接收者將根據clusterMsgDataGossip結構記錄的信息,對被選中節點對應的clusterNode結構進行更新。
2.5數據結構
clusterNode結構保存了一個節點的當前狀態,比如節點的創建時間,節點的名字,節點當前的配置紀元,節點的IP和地址,等等。
1)slots:位圖,由當前clusterNode負責的slot為1
2)salve, slaveof:主從關系信息
3)ping_sent, pong_received:心跳包收發時間
4)clusterLink *link:節點間的連接
5)list *fail_reports:收到的節點不可達投票
clusterState結構記錄了在當前節點的集群目前所處的狀態。
1)myself:指針指向自己的clusterNode
2)currentEpoch:當前節點的最大epoch,可能在心跳包的處理中更新
3)nodes:當前節點記錄的所有節點,為clusterNode指針數組
4)slots:slot與clusterNode指針映射關系
5)migrating_slots_to,
importing_slots_from:記錄slots的遷移信息
6)failover_auth_time,failover_auth_count,failover_auth_sent,failover_auth_rank,
failover_auth_epoch:Failover相關信息
clusterLink結構保存了連接節點所需的有關信息,比如套接字描述符,輸入緩沖區和輸出緩沖區。
3.數據分布及槽信息
3.1槽(slot)概念
Redis Cluster中有一個16384長度的槽的概念,他們的編號為0、1、2、3……16382、16383。這個槽是一個虛擬的槽,并不是真正存在的。正常工作的時候,Redis Cluster中的每個Master節點都會負責一部分的槽,當有某個key被映射到某個Master負責的槽,那么這個Master負責為這個key提供服務,至于哪個Master節點負責哪個槽,這是可以由用戶指定的,也可以在初始化的時候自動生成(redis-trib.rb腳本)。這里值得一提的是,在Redis Cluster中,只有Master才擁有槽的所有權,如果是某個Master的slave,這個slave只負責槽的使用,但是沒有所有權。
3.2數據分片
在Redis Cluster中,擁有16384個slot,這個數是固定的,存儲在Redis Cluster中的所有的鍵都會被映射到這些slot中。數據庫中的每個鍵都屬于這16384個哈希槽的其中一個,集群使用公式CRC16(key) % 16384來計算鍵key屬于哪個槽,其中CRC16(key)語句用于計算鍵key的CRC16校驗和。集群中的每個節點負責處理一部分哈希槽。
3.3節點的槽指派信息
clusterNode結構的slots屬性和numslot屬性記錄了節點負責處理那些槽:
struct clusterNode {
//…
unsignedchar slots[16384/8];
};
Slots屬性是一個二進制位數組(bit
array),這個數組的長度為16384/8=2048個字節,共包含16384個二進制位。
Master節點用bit來標識對于某個槽自己是否擁有。比如對于編號為1的槽,Master只要判斷序列的第二位(索引從0開始)是不是為1即可。時間復雜度為O(1)。
3.4集群所有槽的指派信息
通過將所有槽的指派信息保存在clusterState.slots數組里面,程序要檢查槽i是否已經被指派,又或者取得負責處理槽i的節點,只需要訪問clusterState.slots[i]的值即可,復雜度僅為O(1)。
3.5請求重定向
由于每個節點只負責部分slot,以及slot可能從一個節點遷移到另一節點,造成客戶端有可能會向錯誤的節點發起請求。因此需要有一種機制來對其進行發現和修正,這就是請求重定向。有兩種不同的重定向場景:
a)MOVED錯誤
1.請求的key對應的槽不在該節點上,節點將查看自身內部所保存的哈希槽到節點ID的映射記錄, ? ? ?節點回復一個MOVED錯誤。
2.需要客戶端進行再次重試。
b)ASK錯誤
1.請求的key對應的槽目前的狀態屬于MIGRATING狀態,并且當前節點找不到這個key了,節點回 ? ?復ASK錯誤。ASK會把對應槽的IMPORTING節點返回給你,告訴你去IMPORTING的節點嘗試找找。
2.客戶端進行重試首先發送ASKING命令,節點將為客戶端設置一個一次性的標志(flag),使得 客戶端可以執行一次針對IMPORTING狀態的槽的命令請求,然后再發送真正的命令請求。
3.不必更新客戶端所記錄的槽至節點的映射。
4.數據遷移
當槽x從Node A向Node B遷移時,Node A和Node B都會有這個槽x,Node A上槽x的狀態設置為MIGRATING,Node B上槽x的狀態被設置為IMPORTING。
MIGRATING狀態
1)如果key存在則成功處理
2)如果key不存在,則返回客戶端ASK,客戶端根據ASK首先發送ASKING命令到目標節點,然后發送請求的命令到目標節點
3)當key包含多個命令,
? ? ?a)如果都存在則成功處理
? ? ?b)如果都不存在,則返回客戶端ASK
? ? ?c)如果一部分存在,則返回客戶端TRYAGAIN,通知客戶端稍后重試,這樣當所有的 ? ? ? ?key都遷移完畢的時候客戶端重試請求的時候回得到ASK,然后經過一次重定向就 ? ? ? ? ? 可以獲取這批鍵
4)此時不刷新客戶端中node的映射關系
IMPORTING狀態
1)如果key不在該節點上,會被MOVED重定向,刷新客戶端中node的映射關系
2)如果是ASKING命令則命令會被執行,key不在遷移的節點已經被遷移到目標的節點
3)Key不存在則新建
4.1讀寫請求
槽里面的key還未遷移,并且槽屬于遷移中
假如k1屬于槽x,并且k1還在Node A
4.2 MOVED請求
槽里面的key已經遷移過去,并且槽屬于遷移完
假如k1屬于槽x,并且k1不在Node A,而且槽x已經遷移到Node B
4.3 ASK請求
槽里面的key已經遷移完,并且槽屬于遷移中
假如k1屬于槽x,并且k1不在Node A,而且槽x還是MIGRATING狀態
5.通信故障
5.1故障檢測
集群中的每個節點都會定期地向集群中的其他節點發送PING消息,以此交換各個節點狀態信息,檢測各個節點狀態:在線狀態、疑似下線狀態PFAIL、已下線狀態FAIL。
當主節點A通過消息得知主節點B認為主節點D進入了疑似下線(PFAIL)狀態時,
主節點A會在自己的clusterState.nodes字典中找到主節點D所對應的clusterNode結構,
并將主節點B的下線報告(failure report)添加到clusterNode結構的fail_reports鏈表中
struct clusterNode {
//...
//記錄所有其他節點對該節點的下線報告
list*fail_reports;
//...
};
每個下線報告由一個clusterNodeFailReport結構:
struct clusterNodeFailReport{
//報告目標節點已經下線的節點
structclusterNode *node;
//最后一次從node節點收到下線報告的時間
mstime_ttime;
}typedef clusterNodeFailReport;
如果集群里面,半數以上的主節點都將主節點D報告為疑似下線,那么主節點D將被標記為已下線(FAIL)狀態,將主節點D標記為已下線的節點會向集群廣播主節點D的FAIL消息,
所有收到FAIL消息的節點都會立即更新nodes里面主節點D狀態標記為已下線。
將node標記為FAIL需要滿足以下兩個條件:
1.有半數以上的主節點將node標記為PFAIL狀態。
2.當前節點也將node標記為PFAIL狀態。
5.2多個從節點選主
選新主的過程基于Raft協議選舉方式來實現的
1)當從節點發現自己的主節點進行已下線狀態時,從節點會廣播一條
CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST消息,要求所有收到這條消息,并且具有投票權的主節點向這個從節點投票
2)如果一個主節點具有投票權,并且這個主節點尚未投票給其他從節點,那么主節點將向要求投票的從節點返回一條,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,表示這個主節點支持從節點成為新的主節點
3)每個參與選舉的從節點都會接收CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK消息,并根據自己收到了多少條這種消息來統計自己獲得了多少主節點的支持
4)如果集群里有N個具有投票權的主節點,那么當一個從節點收集到大于等于集群N/2+1張支持票時,這個從節點就成為新的主節點
5)如果在一個配置紀元沒有從能夠收集到足夠的支持票數,那么集群進入一個新的配置紀元,并再次進行選主,直到選出新的主節點為止
5.3故障轉移
當從節點發現自己的主節點變為已下線(FAIL)狀態時,便嘗試進Failover,以期成為新的主。
以下是故障轉移的執行步驟:
1)從下線主節點的所有從節點中選中一個從節點
2)被選中的從節點執行SLAVEOF NO NOE命令,成為新的主節點
3)新的主節點會撤銷所有對已下線主節點的槽指派,并將這些槽全部指派給自己
4)新的主節點對集群進行廣播PONG消息,告知其他節點已經成為新的主節點
5)新的主節點開始接收和處理槽相關的請求
