Redis原始碼閱讀(三)集群 連線初始化

2021-08-26 14:49:08 字數 3913 閱讀 5744

對於併發請求很高的生產環境,單個

redis滿足不了效能要求,通常都會配置redis集群來提高服務效能。3.0之後的redis支援了集群模式。

redis官方提供的集群功能是無中心的,命令請求可以傳送到任意乙個redis節點,如果該請求的key不是由該節點負責處理,則會返回給客戶端moved錯誤,提示客戶端需要轉向到該key對應的處理節點上。支援集群模式的redis客戶端會自動進行轉向,普通模式客戶端則只返回moved錯誤。

先看下常見的redis集群結構:

節點兩兩之間都有連線,只有主節點可以處理客戶端的命令請求;從節點複製主節點資料,並在主節點下線後,公升級為主節點。每個主節點可以掛多個從節點,在主節點下線後從節點需要競爭,只有乙個從節點會被選舉為主節點。

考慮以下幾個關鍵點:

節點是如何互發現的,請求又是如何分配到各個節點的?

其中部分節點出現故障,其他節點是如何發現的又是怎樣恢復的?

主節點下線後從節點是如何競爭的?

是否可以不中斷redis服務進行動態的擴容?

接下來幾篇會從這幾個關鍵問題入手來分析

redis集群原始碼;首先先看集群的基本資料結構,以及節點之間是如何建立連線的

redis集群是無中心的,每個節點會儲存整個集群各個節點的資訊。我們看下redis

原始碼中儲存集群節點資訊的資料結構:

struct clusternode ;

typedef

struct clusternode clusternode;

clusternode結構體儲存了乙個節點的基本資訊,包括節點的ip,port,連線資訊等;redis節點每次和其他節點建立連線都會建立乙個clusternode用來記錄其他節點的資訊, 這些clusternode都會儲存到clusterstate結構中,每個節點自身只擁有乙個clusterstate,用來儲存整個集群系統的狀態和資訊。

typedef struct clusterstate  clusterstate;
clusterstate結構中還有很多是故障遷移時需要用到的成員,與集群連線初始化關係不大,可以先不關注,後面再分析。nodes* 儲存的就是本節點所知的集群所有節點的資訊。

集群節點在初始化前都是孤立的redis服務節點,還沒有連成乙個整體。其他節點的資訊是如何被該節點獲取的,整個集群是如何連線起來的呢?

這裡有兩種途徑:

1)人為干預指定讓節點和其他節點連線,也就是通過cluster meet命令來指定要連入的其他節點;

2)集群自發傳播,靠集群內部的gossip協議自發擴散其他節點的資訊。想象下如果沒有集群內部的自發傳播,任意兩個節點間的連線都需要人為輸入命令來建立;節點數如果為n, 整個集群建立的總連線數量會達到n*(n-1);要想建立起整個集群,讓每個節點都知道完整的集群資訊,需要的cluster meet指令數量是o(n2),節點多起來的話初始化的成本會很高。所以說內部自發的傳播是很有必要的。

下面來看兩種方式的原始碼實現:

meet指令

cluster meet
該指令會指定另乙個節點的

ip和port,讓接收到meet命令的redis節點去和該ip和埠建立連線;

struct rediscommand rediscommandtable = ,,,

......

,......

}

可以看出redis服務處理cluster meet指令的函式是clustercommand。

//

cluster 命令的實現

void clustercommand(redisclient *c)

if (!strcasecmp(c->argv[1]->ptr,"

meet

") && c->argc == 4

)

//a通過cluster meet bip bport b後,b端在clusteraccepthandler接收連線,a端通過clustercommand->clusterstarthandshake連線伺服器

//嘗試與給定位址的節點進行連線

if (clusterstarthandshake(c->argv[2]->ptr,port) == 0 &&errno ==einval)

else

......

}

a節點收到cluster meet b指令後,a進入處理函式clustercommand,並在該函式中呼叫clusterstarthandshake連線b伺服器。這個函式實質上也只是建立乙個記錄了b節點資訊的clusternode(b),並將clusternode(b)的link置為空。真正發起連線的是集群的時間事件處理函式clustercron。clustercron會遍歷a節點上所有的nodes,並向link為空的節點發起連線。這裡的連線又用到前面介紹的檔案事件機制,不再贅述。

gossip訊息擴散

假定對於a、b、c三個節點,初始只向a節點傳送了如下兩條meet指令:

cluster meet b

cluster meet c

對於a來講,b和c都是已知的節點資訊;a會向b、c分別傳送ping訊息;在a傳送ping訊息給b時,傳送方a會在gossip訊息體中隨機帶上已知的節點資訊(假設包含c節點);接收到ping訊息的b節點會解析這gossip訊息體中的節點資訊,發現c節點是未知節點,那麼就會向c節點進行握手,並建立連線。那麼對b來講,c也成為了已知節點。

看下接收gossip訊息並處理未知節點的函式實現:

*/ //

解釋 meet 、 ping 或 pong 訊息中和 gossip 協議有關的資訊。

void clusterprocessgossipsection(clustermsg *hdr, clusterlink *link)

//嘗試將 node 標記為 fail

marknodeasfailingifneeded(node);

//節點處於正常狀態

} else}}

/*if we already know this node, but it is not reachable, and

* we see a different address in the gossip section, start an

* handshake with the (possibly) new address: this will result

* into a node address update if the handshake will be

* successful.

*///

如果節點之前處於 pfail 或者 fail 狀態

//並且該節點的 ip 或者埠號已經發生變化

//那麼可能是節點換了新位址,嘗試對它進行握手

if (node->flags & (redis_node_fail|redis_node_pfail) &&(strcasecmp(node->ip,g->ip) || node->port != ntohs(g->port)))

//當前節點不認識 node

} else

}/*next node

*///

處理下個節點的資訊

g++;}}

gossip協議的原理通俗來講就是一傳十,十傳百;互相之間傳遞集群節點資訊,最終可以達到系統中所有節點都能獲取到完整的集群節點。在ping訊息中附加集群節點資訊,帶來的額外負擔就是每次接收到ping訊息都要預先遍歷下gossip訊息中所有節點資訊,並判斷是否有包含自身未知的節點,還要建立連線。為了減輕接收方的負擔,gossip訊息可以不附帶所有節點資訊,附帶隨機節點也可以最終達到所有節點都去到完整集群資訊的目的。

Redis原始碼閱讀(三)集群 連線初始化

對於併發請求很高的生產環境,單個 redis滿足不了效能要求,通常都會配置redis集群來提高服務效能。3.0之後的redis支援了集群模式。redis官方提供的集群功能是無中心的,命令請求可以傳送到任意乙個redis節點,如果該請求的key不是由該節點負責處理,則會返回給客戶端moved錯誤,提示...

Druid系列《三》集群

集群配置的規劃需要根據需求來定製,下面以乙個開發環境機器搭建為例,描述如何搭建乙個有ha特性的druid集群.集群部署有以下幾點需要說明 1.為了保證ha,主節點部署兩台 2.管理節點與查詢節點可以考慮多核大記憶體的機器 部署規劃 角色 機器 配置 集群角色 主節點10.5.24.137 8c16g...

Redis(三) 集群搭建 day13

redis 集群架構 所有節點之間相互連通,內部使用二進位制協議優化傳輸速度和寬頻。redis集群中內建16384個雜湊槽,每個key寫入的時候,都會由crc16演算法運算後用16384求餘,將這個key分配到相應的hash槽中,這16384個槽又被大致平均的分配到各個節點上。集群不可用情況 1.集...