1. 程式人生 > >redis叢集實現(四) 資料的和槽位的分配

redis叢集實現(四) 資料的和槽位的分配

不知道有沒有人思考過redis是如何把資料分配到叢集中的每一個節點的,可能有人會說,把叢集中的每一個節點編號,先放第一個節點,放滿了就放第二個節點,以此類推。。如果真的是這樣的話,伺服器的利用率和效能就太低了,因為先放第一個,其他的伺服器節點就閒置下來了,單個節點的壓力就會非常的大,其實就相當於退化成為了單機伺服器,從而違背了叢集發揮每一個節點的效能的初衷。

redis官方給出的叢集方案中,資料的分配是按照槽位來進行分配的,每一個數據的鍵被雜湊函式對映到一個槽位,redis-3.0.0規定一共有16384個槽位,當然這個可以根據使用者的喜好進行配置。當用戶put或者是get一個數據的時候,首先會查詢這個資料對應的槽位是多少,然後查詢對應的節點,然後才把資料放入這個節點。這樣就做到了把資料均勻的分配到叢集中的每一個節點上,從而做到了每一個節點的負載均衡,充分發揮了叢集的威力。

redis中,把一個key-value鍵值對放入的最簡單的方式就是set key value,如下所示:

127.0.0.1:7000> set key value
-> Redirected to slot [12539] located at 192.168.39.153:7002
OK
192.168.39.153:7002> get key
"value"
192.168.39.153:7002> 

可以看出,當我們把key的值設定成為value的時候,客戶端被重定向到了另一個節點192.168.39.153:7002,這是因為key對應的槽位是12359,所以我們的key-value

就被放到了槽12359對應的節點,192.168.39.153:7002了。接下來,我們來看看redis是怎麼把一個key-value鍵值對對映成槽,然後又如何存放進叢集中的。

首先在redis.c檔案裡定義了客戶端命令和函式的對應關係,

	struct redisCommand redisCommandTable[] = {
	————————————————————————
	{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
	————————————————————————

可以看出,set命令會執行setCommand函式進行解析,繼續進入setCommand

函式檢視

void setCommand(redisClient *c) {                                              
    int j;                                                                     
    robj *expire = NULL;                                                       
    int unit = UNIT_SECONDS;                                                   
    int flags = REDIS_SET_NO_FLAGS;                                            
                                                               
    ————————————————————————                
                          
    // 對value編碼                                                    
    c->argv[2] = tryObjectEncoding(c->argv[2]); 
    //真正執行set命令的地方                                                                                                                                                     
    setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);    
}     

繼續進入setGenericCommand函式

void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    //引數檢查和過期時間的檢查
    ————————————————————————    
    //在資料庫裡設定key  value    
    setKey(c->db,key,val);
    //設定完成以後的時間通知
    ————————————————————————        
}

接著看資料庫的setKey函式

void setKey(redisDb *db, robj *key, robj *val) {

    // 新增或覆寫資料庫中的鍵值對
    if (lookupKeyWrite(db,key) == NULL) {
        dbAdd(db,key,val);
    } else {
        dbOverwrite(db,key,val);
    }
------------------------------------------------------------------------------------------

當沒有在資料庫中發現key的時候,我們需要執行dbAdd函式把key-value新增到資料庫裡。

void dbAdd(redisDb *db, robj *key, robj *val) {

    // 賦值key的名字
    sds copy = sdsdup(key->ptr);

    // 新增鍵值對到字典中
    int retval = dictAdd(db->dict, copy, val);

    // 如果鍵已經存在,那麼停止
    redisAssertWithInfo(NULL,key,retval == REDIS_OK);

    // 如果開啟了叢集模式,就把鍵儲存到槽裡面
    if (server.cluster_enabled) slotToKeyAdd(key);
 }

繼續進入slotToKeyAdd函式

//把鍵key新增到槽裡邊
void slotToKeyAdd(robj *key) {

    // 通過字串key計算出鍵對應的槽
    unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr));

    // 將槽 slot 作為分數,鍵作為成員,新增到 slots_to_keys 跳躍表裡面
    zslInsert(server.cluster->slots_to_keys,hashslot,key);                                                                                                   
    incrRefCount(key);
}

keyHashSlot是一個雜湊函式,通過key對映到一個0-16384的整數,我們來看一下實現

unsigned int keyHashSlot(char *key, int keylen) {                                                                                                           
    //start 和end
    int s, e; 

    for (s = 0; s < keylen; s++) 
        if (key[s] == '{') break;

    /* 沒有發現和{對應的},就直接雜湊整個字串 */
    if (s == keylen) return crc16(key,keylen) & 0x3FFF;

    /* 如果發現了{,看看是不是又}匹配 */
    for (e = s+1; e < keylen; e++) 
        if (key[e] == '}') break;

    /* 如果沒有發現},雜湊函式就計算整個字串. */
    if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;

    /*如果{}在我們的兩邊,雜湊中間的字元 */
    return crc16(key+s+1,e-s-1) & 0x3FFF;
}

計算key字串對應的對映值,redis採用了crc16函式然後與0x3FFF取低16位的方法。crc16以及md5都是比較常用的根據key均勻的分配的函式,就這樣,使用者傳入的一個key我們就對映到一個槽上,然後經過gossip協議,週期性的和叢集中的其他節點交換資訊,最終整個叢集都會知道key在哪一個槽上。