對於併發請求很高的生產環境,單個
redis滿足不了效能要求,通常都會配置redis集群來提高服務效能。3.0之後的redis支援了集群模式。
redis官方提供的集群功能是無中心的,命令請求可以傳送到任意乙個redis節點,如果該請求的key不是由該節點負責處理,則會返回給客戶端moved錯誤,提示客戶端需要轉向到該key對應的處理節點上。支援集群模式的redis客戶端會自動進行轉向,普通模式客戶端則只返回moved錯誤。
先看下常見的redis集群結構:
節點兩兩之間都有連線,只有主節點可以處理客戶端的命令請求;從節點複製主節點資料,並在主節點下線後,公升級為主節點。每個主節點可以掛多個從節點,在主節點下線後從節點需要競爭,只有乙個從節點會被選舉為主節點。
考慮以下幾個關鍵點:
節點是如何互發現的,請求又是如何分配到各個節點的?
其中部分節點出現故障,其他節點是如何發現的又是怎樣恢復的?
主節點下線後從節點是如何競爭的?
是否可以不中斷redis服務進行動態的擴容?
接下來幾篇會從這幾個關鍵問題入手來分析
redis集群原始碼;首先先看集群的基本資料結構,以及節點之間是如何建立連線的
redis集群是無中心的,每個節點會儲存整個集群各個節點的資訊。我們看下redis
原始碼中儲存集群節點資訊的資料結構:
struct clusternode ;clusternode結構體儲存了乙個節點的基本資訊,包括節點的ip,port,連線資訊等;redis節點每次和其他節點建立連線都會建立乙個clusternode用來記錄其他節點的資訊, 這些clusternode都會儲存到clusterstate結構中,每個節點自身只擁有乙個clusterstate,用來儲存整個集群系統的狀態和資訊。typedef
struct clusternode clusternode;
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。......
,......
}
//a節點收到cluster meet b指令後,a進入處理函式clustercommand,並在該函式中呼叫clusterstarthandshake連線b伺服器。這個函式實質上也只是建立乙個記錄了b節點資訊的clusternode(b),並將clusternode(b)的link置為空。真正發起連線的是集群的時間事件處理函式clustercron。clustercron會遍歷a節點上所有的nodes,並向link為空的節點發起連線。這裡的連線又用到前面介紹的檔案事件機制,不再贅述。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
......
}
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訊息並處理未知節點的函式實現:
*/ //gossip協議的原理通俗來講就是一傳十,十傳百;互相之間傳遞集群節點資訊,最終可以達到系統中所有節點都能獲取到完整的集群節點。在ping訊息中附加集群節點資訊,帶來的額外負擔就是每次接收到ping訊息都要預先遍歷下gossip訊息中所有節點資訊,並判斷是否有包含自身未知的節點,還要建立連線。為了減輕接收方的負擔,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++;}}
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.集...