Redis Cluster原理初步
目錄
1. 前言
截至2016/5/16最新版本的redis-3.2.0仍然非強一致性,基於效能考慮master和它的slaves間資料是非同步複製的。另外,一個確定的key總是隻會落到確定的master,除非使用redis-trib.rb等工具修改slots和master間的繫結關係,目前的redis cluste不支援自動從一個master遷移一個slot到另一個master(slaves對slots來說,可以認為和對應的master相同)。
2. 槽(slots)
Redis cluster將所有儲存在其上的key通過一個hash演算法劃分成若干slots
3. 路由配置(node.conf)
儲存的內容和redis命令“cluster nodes”的輸出相同,即儲存了master和slave資訊,以及各master儲存的slots,亦即slots的路由資訊儲存在node.conf。
同一Redis cluster中的所有節點的node.conf檔案內容最終是一致的。
4. 總slots數(cluster.h:16384)
#define CLUSTER_SLOTS 16384// 等於(0x3FFF + 1) |
巨集CLUSTER_SLOTS定義了
5. key的路由
-> 將key轉成整數值
-> 計算key所在的slot
-> 找到slot所在的master或slaves(redis cluster可配置允許slaves提供讀)
-> 轉成直接對master或slaves的請求。
由於任何一個redis cluster節點都儲存了相同內容的node.conf,所以client可以請求任一節點獲得
而且由於node.conf中包含了master和slaves資訊,因此讀寫操作可以完美的路由到相應的節點。
6. 將key轉成整數值(crc16.c:crc16)
Redis使用crc演算法將一個字串轉成整數,巨集CLUSTER_SLOTS的值是不能超過CRC返回的最大值。
uint16_t crc16(const char *buf, int len) { int counter; uint16_t crc = 0; for (counter = 0; counter < len; counter++) crc = (crc<<8) ^ crc16tab[((crc>>8) ^ *buf++)&0x00FF]; return crc; } |
7. 計算key所在slot(cluster.c:keyHashSlot)
對於一個redis KEY它歸屬於哪一個slot,這個可以通過函式keyHashSlot()呼叫計算出來:
unsigned int keyHashSlot(char *key, int keylen) { int s, e; /* start-end indexes of { and } */ for (s = 0; s < keylen; s++) if (key[s] == '{') break; /* No '{' ? Hash the whole key. This is the base case. */ if (s == keylen) return crc16(key,keylen) & 0x3FFF; /* '{' found? Check if we have the corresponding '}'. */ for (e = s+1; e < keylen; e++) if (key[e] == '}') break; /* No '}' or nothing betweeen {} ? Hash the whole key. */ if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF; /* If we are here there is both a { and a } on its right. Hash * what is in the middle between { and }. */ return crc16(key+s+1,e-s-1) & 0x3FFF; // 3FFF即為16383 } |
8. Redis Cluster Client實現
通過上面的資訊,不然發現,Redis Cluster Client只是在原來單機版client基礎上多了一層薄的路由邏輯。因此可以基於現有的hiredis等實現支援redis cluster的client庫。大致過程如下:
class CRedisClusterClient { public: // nodes Redis叢集中的單個或多個節點,格式為:ip1:port1,ip2:port2,如:127.0.0.1:6379,127.0.0.1:6380,192.168.31.11:6379 CRedisClusterClient(const std::string& nodes); void set(const std::string& key, const std::string& value) const; void get(const std::string& key, std::string* value); private: redisContext* _redis_context; // hiredis }; |
set()函式實現:
1) CRedisClusterClient從nodes取任一nodeA,如:127.0.0.1:6380
2) 建立與nodeA的連線
3) 從nodeA取得slots路由資料(實現時可快取這部分資料,以提升效能)
4) 構造slots路由資料表(由於slots總數有限,可以以slot為下標陣列方式組織路由表)
5) 計算key所在的slot
6) 找到slot所在的nodeB(對於寫操作,要求nodeB為master,有可能碰巧就是nodeA)
7) 使用hiredis訪問nodeB(從這步開始和原使用hiredis相同)
8) 取得hiredis返回的結果
如果使用hiredis發生網路異常,對於寫操作從第3步開始重執行,對於讀操作從第6步重選一個node重執行。