redis字典源碼簡單分析

字典是redis底層數(shù)據(jù)結(jié)構(gòu)之一,在dict.c中實現(xiàn),下面分析下他的實現(xiàn)。

一.簡介

redis的dict仍然是一個數(shù)組+鏈表實現(xiàn)的哈希表。區(qū)別為redis的dict有兩個哈希表,第二個哈希表resize時使用,如下為它頭文件中定義的結(jié)構(gòu):

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

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    //當(dāng)前哈希表中
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; //rehash進度
    unsigned long iterators; /* number of iterators currently running */
} dict;

redis的字典有兩個哈希表dictht[0]和dictht[1],只有rehash時兩個哈希表才同時使用,平時只有dictht[0]哈希表中有數(shù)據(jù)。
rehashidx用來標(biāo)記rehash正在執(zhí)行到哪個槽,如果沒有rehashing則返回-1.
redis rehash最大的特點是漸進性rehash,將rehash操作分散在插入,刪除等操作中。這點和ThreadPoolExecutor實現(xiàn)思想很像有木有。

二.實現(xiàn)分析:
1.插入:

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;
    
    //如果正在rehashing,則執(zhí)行一次_dictRehashStep操作。
    if (dictIsRehashing(d)) _dictRehashStep(d);

    //獲取插入的key在數(shù)組的中的位置
    if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
        return NULL;

    //如果正在執(zhí)行rehash操作則將entry插入ht[1],否在插入ht[0]
    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];
    entry = zmalloc(sizeof(*entry));
    //放入鏈表頭
    entry->next = ht->table[index];
    ht->table[index] = entry;
    //負(fù)載+1
    ht->used++;

    dictSetKey(d, entry, key);
    return entry;
}

//返回一個key所在的數(shù)組下標(biāo)并判斷該key是否存在。如果沒有執(zhí)行rehash,則只搜索ht[0]并返回該key在ht[0]的下標(biāo)。如果正在rehash則兩個哈希表都搜索并返回在ht[1]的下標(biāo).
static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
{
    unsigned long idx, table;
    dictEntry *he;
    if (existing) *existing = NULL;

    //如果當(dāng)前負(fù)載已經(jīng)達到負(fù)載因子,則擴容。
    if (_dictExpandIfNeeded(d) == DICT_ERR)
        return -1;
    
    //先搜索第一個哈希表,搜索完第一個哈希表之后,如果當(dāng)前字典沒有在執(zhí)行rehash,則不搜索第二個哈希表。如果正在執(zhí)行rehash,則在搜索第二個哈希表。
    for (table = 0; table <= 1; table++) {
        //取余計算key應(yīng)放置的數(shù)組下標(biāo)
        idx = hash & d->ht[table].sizemask;
        //沿著當(dāng)前槽的鏈表向后搜索是否有該key,如果有返回-1.
        he = d->ht[table].table[idx];
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                if (existing) *existing = he;
                return -1;
            }
            he = he->next;
        }
        //如果沒有rehashing,則不再搜索ht[1]
        if (!dictIsRehashing(d)) break;
    }
    return idx;
}

//檢查當(dāng)前哈希表是否需要擴容,正在rehash的話不擴容,擴容后大小為比used*2大的最接近的2的n次冪。
static int _dictExpandIfNeeded(dict *d)
{
    //正在rehash的話不擴容。
    if (dictIsRehashing(d)) return DICT_OK;

    //如果ht[0]是空,則初始化
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
    
    //負(fù)載因為為1.但是如果正在執(zhí)行bgsave操作,負(fù)載因子為dict_force_resize_ratio 5
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        //擴容,擴容數(shù)為當(dāng)前已有元素數(shù)量*2
        return dictExpand(d, d->ht[0].used*2);
    }
    return DICT_OK;
}

//擴容
int dictExpand(dict *d, unsigned long size)
{
    //如果正在rehashing或者擴容之后負(fù)載仍然大于1,返回錯誤
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    dictht n; /* the new hash table */
    //確保size為2的倍數(shù)
    unsigned long realsize = _dictNextPower(size);

    //如果擴容之后的size仍等于ht[0]size,則返回錯誤。
    if (realsize == d->ht[0].size) return DICT_ERR;

    //開辟哈希表空間
    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0;

    /* Is this the first initialization? If so it's not really a rehashing
     * we just set the first hash table so that it can accept keys. */
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }

    //將新建的哈希表賦值給ht[1]
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

插入操作有幾個要點:
1.漸進性hash:
插入時會判斷當(dāng)前字典是否正在rehashing,如果正在rehashing,會執(zhí)行一次rehash操作.
2.二倍擴容
擴容后大小為比used*2大的最接近的2的n次冪。這樣&運算即可算出key要插入的數(shù)組下標(biāo)。
3.負(fù)載因子為1
但是如果dict_can_resize標(biāo)志位為true,表明正在執(zhí)行rdb寫入操作,此時負(fù)載因子為5.
4.頭插法
新的節(jié)點插入到表頭
5.如果正在rehash,則新節(jié)點直接插入到ht[1]。

rehash:

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;
        }
        de = d->ht[0].table[d->rehashidx];
        //將當(dāng)前槽上鏈表的所有節(jié)點都引動到ht[1]
        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;
            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++;
    }

    //檢查時候完成,完成的話將整個ht[1]賦值給ht[0].
    if (d->ht[0].used == 0) {
        zfree(d->ht[0].table);
        d->ht[0] = d->ht[1];
        _dictReset(&d->ht[1]);
        d->rehashidx = -1;
        return 0;
    }

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

rehash操作很簡單,一次rehash操作移動一個槽。rehash完成后會釋放ht[0],并將ht[1]賦值給ht[0].

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容