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