redis我的源碼分析2---dict的實現原理

1. 整體結構redis

redis的dict就是hash表,使用鏈式結構來解決key值衝突,典型的數據結構數據庫

結構體的定義以下:數組

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

typedef struct dictType {
    uint64_t (*hashFunction)(const void *key);
    void *(*keyDup)(void *privdata, const void *key);
    void *(*valDup)(void *privdata, const void *obj);
    int (*keyCompare)(void *privdata, const void *key1, const void *key2);
    void (*keyDestructor)(void *privdata, void *key);
    void (*valDestructor)(void *privdata, void *obj);
} dictType;

/* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
    dictEntry **table;  //hash桶是一個指針數組,裏面存放的是hash entry的指針類型,只須要(8字節*size)個連續內存不須要大量的連續內存
    unsigned long size;  //這個是hash桶的大小
    unsigned long sizemask;  //hash桶大小-1, **用hash**/sizemask來計算桶下標
    unsigned long used; //當前這個dict一共放了多少個kv鍵值對
} dictht;
//一旦used/size >=dict_force_resize_ratio(默認值是5),就會觸發rehash,能夠理解爲一個hash桶後面平均掛載的衝突隊列個數爲5的時候,就會觸發rehash


typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    unsigned long iterators; /* number of iterators currently running */
} dict;



複製代碼

以下圖所示:數據結構

2. API接口分析函數

2.1 建立性能

API接口函數:ui

  • dictAdd(dict *d, void *key, void *val)

在d中增長一個k-v對,實現代碼以下:this

/* Add an element to the target hash table */
int dictAdd(dict *d, void *key, void *val) {
    dictEntry *entry = dictAddRaw(d,key,NULL);//調用了內部函數

    if (!entry) return DICT_ERR;
    dictSetVal(d, entry, val);
    return DICT_OK;
}



dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing) {
    long index;
    dictEntry *entry;
    dictht *ht;

    if (dictIsRehashing(d)) _dictRehashStep(d); //若是正在rehash進行中,則每次操做都嘗試進行一次rehash操做

    /* Get the index of the new element, or -1 if * the element already exists. 獲取到hash桶的入口index*/
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    /* Allocate the memory and store the new entry. * Insert the element in top, with the assumption that in a database * system it is more likely that recently added entries are accessed * more frequently. (譯文:申請內存來存儲一個新的entry結構,插入元素到頭部, 這裏的實現和通常的hash鏈式解決衝突的實現有點小不一樣,基於這樣的假定:在數據庫系統中,最近增長的entries越有可能被訪問。 這裏是把新插入的entry放到了鏈表頭上,能夠看上面的英文解釋*/
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    entry->next = ht->table[index];
    ht->table[index] = entry;
    ht->used++;

    /* Set the hash entry fields.*/
    dictSetKey(d, entry, key);
    return entry;
}


/* Returns the index of a free slot that can be populated with * a hash entry for the given 'key'. * If the key already exists, -1 is returned * and the optional output parameter may be filled. * * Note that if we are in the process of rehashing the hash table, the * index is always returned in the context of the second (new) hash table. 這個原版註釋寫的很清楚,若是正在rehashing的時候,index返回的是new的hashtable*/
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    unsigned long idx, table;
    dictEntry *he;
    if (existing) *existing = NULL;

    /* Expand the hash table if needed ,判斷hash桶是否須要擴大,這個地方是redis比較牛逼的地方, hash桶是動態擴大的,默認初始的時候只有4,而後每次乘2的方式進行擴展,若是擴展了,就須要進行rehash*/
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
    /*獲取索引的時候,若是正在rehash,須要兩個hashtable都進行查詢*/
    for (table = 0; table <= 1; table++) {
        /*這個idx就是hash桶的下標*/
        idx = hash & d->ht[table].sizemask;
        /* Search if this slot does not already contain the given key */
        he = d->ht[table].table[idx];
        while(he) {
        /*這裏是必須遍歷下衝突隊列,保證key沒有出現過*/
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                if (existing) *existing = he;
                return -1;
            }
            he = he->next;
        }
        /*若是不在rehash的話,其實就沒有必要再作查找的操做了,直接返回就行了*/
        if (!dictIsRehashing(d)) break;
    }
    return idx;
}

複製代碼
  • dictEntry *dictFind(dict *d, const void *key) 根據key在d中尋找值,這個邏輯和add差很少,代碼很簡單,這裏就不作解釋了
dictEntry *dictFind(dict *d, const void *key) {
    dictEntry *he;
    uint64_t h, idx, table;

    if (d->ht[0].used + d->ht[1].used == 0) return NULL; /* dict is empty */
    if (dictIsRehashing(d)) _dictRehashStep(d);  //和增長的時候邏輯同樣,若是正在rehashing,則進行一步rehash
    h = dictHashKey(d, key);
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key))
                return he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) return NULL;
    }
    return NULL;
}

複製代碼

3. rehash過程
redis對於dict支持兩種rehash的方式:按照時間,或者按照操做進行rehash。每次都調整一個key值桶內全部的衝突鏈表到新的hash表中。 rehash 代碼以下:spa

static void _dictRehashStep(dict *d) {
    if (d->iterators == 0) dictRehash(d,1);
}


/* Performs N steps of incremental rehashing. Returns 1 if there are still * keys to move from the old to the new hash table, otherwise 0 is returned. * * Note that a rehashing step consists in moving a bucket (that may have more * than one key as we use chaining) from the old to the new hash table, however * since part of the hash table may be composed of empty spaces, it is not * guaranteed that this function will rehash even a single bucket, since it * will visit at max N*10 empty buckets in total, otherwise the amount of * work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
    int empty_visits = n*10; /* Max number of empty buckets to visit. */
    if (!dictIsRehashing(d)) return 0;

    while(n-- && d->ht[0].used != 0) {
        dictEntry *de, *nextde;

        /* Note that rehashidx can't overflow as we are sure there are more * elements because ht[0].used != 0 */
        assert(d->ht[0].size > (unsigned long)d->rehashidx);
        while(d->ht[0].table[d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1; //redis爲了保證性能,掃描空桶,最多也是有必定的限制
        }
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT ,這個循環就是開始把這個rehashidx下標的hashtable遷移到新的下標下面,注意,這裏須要從新計算key值,從新插入*/
        while(de) {
            uint64_t h;

            nextde = de->next;
            /* Get the index in the new hash table */
            h = dictHashKey(d, de->key) & d->ht[1].sizemask;//從新計算key值,從新插入
            de->next = d->ht[1].table[h];
            d->ht[1].table[h] = de;
            d->ht[0].used--;
            d->ht[1].used++;
            de = nextde;
        }
        d->ht[0].table[d->rehashidx] = NULL;
        d->rehashidx++;
    }

    /* Check if we already rehashed the whole table...,一次操做完了,可能這個hashtable已經遷移完畢,返回0,不然返回1 */
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1]; //如今的0變成1
        _dictReset(&d->ht[1]);  //如今的1被reset掉
        d->rehashidx = -1;
        return 0;
    }

    /* More to rehash... */
    return 1;
}



複製代碼
相關文章
相關標籤/搜索