Redis集群節(jié)點通信過程/原理流程分析
簡介
本文介紹Redis的Cluster(集群)的節(jié)點通信的流程。
通信流程
在分布式存儲中需要提供維護節(jié)點元數(shù)據(jù)信息的機制, 所謂元數(shù)據(jù)是指: 節(jié)點負責哪些數(shù)據(jù), 是否出現(xiàn)故障等狀態(tài)信息。 常見的元數(shù)據(jù)維護方式分為: 集中式和P2P方式。 Redis集群采用P2P的Gossip(流言) 協(xié)議,Gossip協(xié)議工作原理就是節(jié)點彼此不斷通信交換信息, 一段時間后所有的節(jié)點都會知道集群完整的信息, 這種方式類似流言傳播, 如下所示
通信過程說明:
- 集群中的每個節(jié)點都會單獨開辟一個TCP通道, 用于節(jié)點之間彼此通信, 通信端口號在基礎端口上加10000。
- 每個節(jié)點在固定周期內(nèi)通過特定規(guī)則選擇幾個節(jié)點發(fā)送ping消息。接收到ping消息的節(jié)點用pong消息作為響應。
- 集群中每個節(jié)點通過一定規(guī)則挑選要通信的節(jié)點, 每個節(jié)點可能知道全部節(jié)點, 也可能僅知道部分節(jié)點, 只要這些節(jié)點彼此可以正常通信, 最終它們會達到一致的狀態(tài)。 當節(jié)點出故障、 新節(jié)點加入、 主從角色變化、 槽信息變更等事件發(fā)生時, 通過不斷的ping/pong消息通信, 經(jīng)過一段時間后所有的節(jié)點都會知道整個集群全部節(jié)點的最新狀態(tài), 從而達到集群狀態(tài)同步的目的。
Gossip消息
消息流程
Gossip協(xié)議的主要職責就是信息交換。 信息交換的載體就是節(jié)點彼此發(fā)送的Gossip消息, 了解這些消息有助于我們理解集群如何完成信息交換。
常用的Gossip消息可分為: ping消息、 pong消息、 meet消息、 fail消息等, 它們的通信模式如下圖所示:
- meet消息: 用于通知新節(jié)點加入。
消息發(fā)送者通知接收者加入到當前集群, meet消息通信正常完成后, 接收節(jié)點會加入到集群中并進行周期性的ping、 pong消息交換。
- ping消息: 集群內(nèi)交換最頻繁的消息
集群內(nèi)每個節(jié)點每秒向多個其他節(jié)點發(fā)送ping消息, 用于檢測節(jié)點是否在線和交換彼此狀態(tài)信息。 ping消息發(fā)送封裝了自身節(jié)點和部分其他節(jié)點的狀態(tài)數(shù)據(jù)。
- pong消息: 當接收到ping、 meet消息時, 作為響應消息回復給發(fā)送方確認消息正常通信。
pong消息內(nèi)部封裝了自身狀態(tài)數(shù)據(jù)。 節(jié)點也可以向集群內(nèi)廣播自身的pong消息來通知整個集群對自身狀態(tài)進行更新。
fail消息: 當節(jié)點判定集群內(nèi)另一個節(jié)點下線時, 會向集群內(nèi)廣播一個fail消息, 其他節(jié)點接收到fail消息之后把對應節(jié)點更新為下線狀態(tài)。 具體細節(jié)將在后面“故障轉移”中說明。
消息格式
所有的消息格式劃分為: 消息頭和消息體。 消息頭包含發(fā)送節(jié)點自身狀態(tài)數(shù)據(jù), 接收節(jié)點根據(jù)消息頭就可以獲取到發(fā)送節(jié)點的相關數(shù)據(jù), 結構如下:
typedef struct { char sig[4]; /* 信號標示 */ uint32_t totlen; /* 消息總長度 */ uint16_t ver; /* 協(xié)議版本*/ uint16_t type; /* 消息類型,用于區(qū)分meet,ping,pong等消息 */ uint16_t count; /* 消息體包含的節(jié)點數(shù)量, 僅用于meet,ping,ping消息類型*/ uint64_t currentEpoch; /* 當前發(fā)送節(jié)點的配置紀元 */ uint64_t configEpoch; /* 主節(jié)點/從節(jié)點的主節(jié)點配置紀元 */ uint64_t offset; /* 復制偏移量 */ char sender[CLUSTER_NAMELEN]; /* 發(fā)送節(jié)點的nodeId */ unsigned char myslots[CLUSTER_SLOTS/8]; /* 發(fā)送節(jié)點負責的槽信息 */ char slaveof[CLUSTER_NAMELEN]; /* 如果發(fā)送節(jié)點是從節(jié)點, 記錄對應主節(jié)點的nodeId */ uint16_t port; /* 端口號 */ uint16_t flags; /* 發(fā)送節(jié)點標識,區(qū)分主從角色, 是否下線等 */ unsigned char state; /* 發(fā)送節(jié)點所處的集群狀態(tài) */ unsigned char mflags[3]; /* 消息標識 */ union clusterMsgData data /* 消息正文 */; } clusterMsg;
集群內(nèi)所有的消息都采用相同的消息頭結構clusterMsg, 它包含了發(fā)送節(jié)點關鍵信息, 如節(jié)點id、 槽映射、 節(jié)點標識(主從角色, 是否下線) 等。消息體在Redis內(nèi)部采用clusterMsgData結構聲明, 結構如下:
union clusterMsgData { /* ping,meet,pong消息體*/ struct { /* gossip消息結構數(shù)組 */ clusterMsgDataGossip gossip[1]; } ping; /* FAIL 消息體 */ struct { clusterMsgDataFail about; } fail; // ... };
消息體clusterMsgData定義發(fā)送消息的數(shù)據(jù), 其中ping、 meet、 pong都采用cluster MsgDataGossip數(shù)組作為消息體數(shù)據(jù), 實際消息類型使用消息頭的type屬性區(qū)分。 每個消息體包含該節(jié)點的多個clusterMsgDataGossip結構數(shù)據(jù), 用于信息交換, 結構如下:
typedef struct { char nodename[CLUSTER_NAMELEN]; /* 節(jié)點的nodeId */ uint32_t ping_sent; /* 最后一次向該節(jié)點發(fā)送ping消息時間 */ uint32_t pong_received; /* 最后一次接收該節(jié)點pong消息時間 */ char ip[NET_IP_STR_LEN]; /* IP */ uint16_t port; /* port*/ uint16_t flags; /* 該節(jié)點標識, */ } clusterMsgDataGossip;
當接收到ping、 meet消息時, 接收節(jié)點會解析消息內(nèi)容并根據(jù)自身的識別情況做出相應處理, 對應流程如下圖所示:
接收節(jié)點收到ping/meet消息時, 執(zhí)行解析消息頭和消息體流程:
- 解析消息頭過程:
消息頭包含了發(fā)送節(jié)點的信息, 如果發(fā)送節(jié)點是新節(jié)點且消息是meet類型, 則加入到本地節(jié)點列表; 如果是已知節(jié)點, 則嘗試更新發(fā)送節(jié)點的狀態(tài), 如槽映射關系、 主從角色等狀態(tài)。
- 解析消息體過程:
如果消息體的clusterMsgDataGossip數(shù)組包含的節(jié)點是新節(jié)點, 則嘗試發(fā)起與新節(jié)點的meet握手流程; 如果是已知節(jié)點, 則根據(jù)cluster MsgDataGossip中的flags字段判斷該節(jié)點是否下線, 用于故障轉移。
消息處理完后回復pong消息, 內(nèi)容同樣包含消息頭和消息體, 發(fā)送節(jié)點接收到回復的pong消息后, 采用類似的流程解析處理消息并更新與接收節(jié)點最后通信時間, 完成一次消息通信。
節(jié)點選擇
雖然Gossip協(xié)議的信息交換機制具有天然的分布式特性, 但它是有成本的。 由于內(nèi)部需要頻繁地進行節(jié)點信息交換, 而ping/pong消息會攜帶當前節(jié)點和部分其他節(jié)點的狀態(tài)數(shù)據(jù), 勢必會加重帶寬和計算的負擔。 Redis集群內(nèi)節(jié)點通信采用固定頻率(定時任務每秒執(zhí)行10次) 。 因此節(jié)點每次選擇需要通信的節(jié)點列表變得非常重要。 通信節(jié)點選擇過多雖然可以做到信息及時交換但成本過高。 節(jié)點選擇過少會降低集群內(nèi)所有節(jié)點彼此信息交換頻率,從而影響故障判定、 新節(jié)點發(fā)現(xiàn)等需求的速度。 因此Redis集群的Gossip協(xié)議需要兼顧信息交換實時性和成本開銷, 通信節(jié)點選擇的規(guī)則如下圖所示
根據(jù)通信節(jié)點選擇的流程可以看出消息交換的成本主要體現(xiàn)在單位時間選擇發(fā)送消息的節(jié)點數(shù)量和每個消息攜帶的數(shù)據(jù)量。
1.選擇發(fā)送消息的節(jié)點數(shù)量
集群內(nèi)每個節(jié)點維護定時任務默認每秒執(zhí)行10次, 每秒會隨機選取5個節(jié)點找出最久沒有通信的節(jié)點發(fā)送ping消息, 用于保證Gossip信息交換的隨機性。 每100毫秒都會掃描本地節(jié)點列表, 如果發(fā)現(xiàn)節(jié)點最近一次接受pong消息的時間大于cluster_node_timeout/2, 則立刻發(fā)送ping消息, 防止該節(jié)點信息太長時間未更新。 根據(jù)以上規(guī)則得出每個節(jié)點每秒需要發(fā)送ping消息的數(shù)
量=1+10*num(node.pong_received>cluster_node_timeout/2) , 因此cluster_node_timeout參數(shù)對消息發(fā)送的節(jié)點數(shù)量影響非常大。 當我們的帶寬資源緊張時, 可以適當調(diào)大這個參數(shù), 如從默認15秒改為30秒來降低帶寬占用率。 過度調(diào)大cluster_node_timeout會影響消息交換的頻率從而影響故障轉移、 槽信息更新、 新節(jié)點發(fā)現(xiàn)的速度。 因此需要根據(jù)業(yè)務容忍度和資源消耗進行平衡。 同時整個集群消息總交換量也跟節(jié)點數(shù)成正比。
2.消息數(shù)據(jù)量
每個ping消息的數(shù)據(jù)量體現(xiàn)在消息頭和消息體中, 其中消息頭主要占用空間的字段是myslots[CLUSTER_SLOTS/8], 占用2KB, 這塊空間占用相對固定。 消息體會攜帶一定數(shù)量的其他節(jié)點信息用于信息交換。 具體數(shù)量見以下偽代碼:
def get_wanted(): int total_size = size(cluster.nodes) # 默認包含節(jié)點總量的1/10 594int wanted = floor(total_size/10); if wanted < 3: # 至少攜帶3個其他節(jié)點信息 wanted = 3; if wanted > total_size -2 : # 最多包含total_size - 2個 wanted = total_size - 2; return wanted;
根據(jù)偽代碼可以看出消息體攜帶數(shù)據(jù)量跟集群的節(jié)點數(shù)息息相關, 更大的集群每次消息通信的成本也就更高, 因此對于Redis集群來說并不是大而全的集群更好, 對于集群規(guī)模控制的建議見之后“集群運維”。
其他網(wǎng)址
《Redis開發(fā)與運維》=> 第10章 集群=> 10.3 節(jié)點通信
到此這篇關于Redis集群節(jié)點通信過程/原理的文章就介紹到這了,更多相關Redis集群節(jié)點通信內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Redis可視化工具Redis?Desktop?Manager的具體使用
本文主要介紹了Redis可視化工具Redis?Desktop?Manager的具體使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12redis?zrange?與?zrangebyscore的區(qū)別解析
這篇文章主要介紹了redis?zrange與zrangebyscore的區(qū)別,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-06-06Redis實現(xiàn)排行榜及相同積分按時間排序功能的實現(xiàn)
這篇文章主要介紹了Redis實現(xiàn)排行榜及相同積分按時間排序,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08