redis 字典

redis的字典使用哈希表作爲底層實現,一個哈希表裏面可以有多個哈希表節點,而每個哈希表節點就保存了字典中的一個鍵值對

typedef struct dictht {
    dictEntry **table;				//hash table 採用開鏈來解決hash衝突
    unsigned long size;			//哈希表大小
    unsigned long sizemask;		//哈希表掩碼,總是等於size-1
    unsigned long used;			//哈希表已有節點數量
} dictht;

哈希表節點:

typedef struct dictEntry {
    void *key;					//保存着鍵值中的鍵
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;							//保存鍵值中的值
    struct dictEntry *next;
} dictEntry;

字典:

 

typedef struct dict {
    dictType *type;				//操作函數
    void *privdata;				//數據
    dictht ht[2];					//hash表
    long rehashidx; 				/* rehashing not in progress if rehashidx == -1 */
    int iterators; 					/* number of iterators currently running */
} dict;
typedef struct dictType {
    unsigned int (*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;

字典的ht屬性是一個包含兩個項的數組,數組中每個項都是一個dictht哈希表,一般情況下,字典只使用ht[0]哈希表,ht[1]哈希表只會在ht[0]哈希表進行rehash時使用。

字典可能在rehash,當字典的rehashidx值爲-1的時候,字典沒有rehash,當爲非-1的時候rehashidx記錄着哈希表rehash的bucket id

 

rehash:

隨着操作的不斷執行,哈希表保存的鍵值對會逐漸地增多或者減少,爲了讓hash表的負載因子(load factor)維持在一個合理範圍之內,需要rehash

1 爲字典的ht[1]哈希表分配空間,這個哈希表的空間大小取決於要執行的操作,以及ht[0]當前包含的鍵值對的數量,如果是擴展操作ht[1]大小爲第一個大於等於ht[0].used*2的2的n次冪,

2 將保存在ht[0]中的所有鍵值對rehash到ht[1]上面:rehash指的是重新計算鍵的哈希值和索引值,然後將鍵值對放置到ht[1]哈希表指定位置上。

3 當ht[0]包含的所有鍵值對都遷移到了ht[1]之後,釋放ht[0], 將ht[1]設置爲ht[0],並在ht[1]新創建一個空白哈希表,爲下次rehash做準備

rehash觸發

其中負載因子:

load_factor = ht[0].used/ht[0].size

rehash觸發函數調用:

dictAddRaw -> _dictExpandIfNeeded -> dictExpand

static int _dictExpandIfNeeded(dict *d)
{
    //如果正在rehash,那麼不擴展大小
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    //如果爲空,那麼初始化爲4個bucket 大小
    /* If the hash table is empty expand it to the initial size. */
    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);

    //dict_can_resize 全局變量設置
    if (d->ht[0].used >= d->ht[0].size &&
        (dict_can_resize ||
         d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
    {
        return dictExpand(d, d->ht[0].used*2);	//申請空間,並且設置d->rehashidx=0
    }
    return DICT_OK;
}

擴展(滿足其中一條就觸發)

1 服務器目前沒有執行bgsave或者bgrewriteaof命令,並且字典負載因子大於等於1

2 服務器目前正在執行bgsave或者bgrewriteaof命令,並且字典負載因子大於5

收縮:

當負載因子小於0.1時,程序自動開始對哈希表進行收縮操作

rehash 漸進式

在dict每次api調用都戶判斷rehash,來漸進式遷移。

 

//d爲rehash 字典,n是遷移多少個bucket

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;

        assert(d->ht[0].size > (unsigned long)d->rehashidx);
//跳過bucket爲空
        while(d->ht[0].table[d->rehashidx] == NULL) {
            d->rehashidx++;
            if (--empty_visits == 0) return 1;
        }
        de = d->ht[0].table[d->rehashidx];
        /*遷移*/
        while(de) {
            unsigned int 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[0]指向ht[1] */
    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;
}

字典API

發佈了83 篇原創文章 · 獲贊 8 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章