redis dict字典的源码分析

来源:互联网 发布:淘宝哪家手机壳质量好 编辑:程序博客网 时间:2024/05/21 11:17

       Redis的字典用哈希表作为底层实现,一个哈希表里可以有很多哈希表节点,而每个哈希表节点就保存

了字典的一个键值对。

 一、字典的基本实现

1、哈希表节点

typedef struct dictEntry {    void *key;//键    union {        void *val;        uint64_t u64;        int64_t s64;        double d;    } v;//值    struct dictEntry *next;//指向下一个节点,用于解决键冲突} dictEntry;

2、哈希表

typedef struct dictht {    dictEntry **table;//哈希表数组,每个元素都是指向dictEntry结构体指针    unsigned long size;//哈希表的大小    unsigned long sizemask;//哈希表大小掩码(等于size-1),用于计算索引值    unsigned long used;//哈希表已有节点数量} dictht;

3、字典

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;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;
二、字典的基本操作

dict *dictCreate(dictType *type, void *privDataPtr);//创建新的字典int dictAdd(dict *d, void *key, void *val);//将给定键值对添加到字典int dictReplace(dict *d, void *key, void *val);//将给定键值对添加到字典,如果键已存在,则用新值替换旧值void *dictFetchValue(dict *d, const void *key);//返回给定键的值dictEntry *dictGetRandomKey(dict *d);//从字典中随机返回一个键值对int dictDelete(dict *d, const void *key);//从字典中删除指定键值对void dictRelease(dict *d);//释放给定字典及所有键值对
三、rehash

        为了让哈希表的负载因子维持在一个合理范围之内,当哈希表保存的键值对数量太多或者太少时,程序

需要对哈希表的大小进行扩展或者收缩。

      调整哈希表的工作通过rehash(重新散列)来完成,rehash步骤如下:

      1、为字典的ht[1]分配空间,空间大小取决于要执行的操作和ht[0]当前的包含的键值对数量(ht[0].used)

         1)扩展操作,ht[1].size为第一个大于等于ht[0].used*2的2的n次方;

         2)收缩操作,ht[1].size为第一个大于等于ht[0].used的2的n次方;

      2、将保存在ht[0]中的键值对rehash到ht[1]上面:重新计算键的哈希值和索引值,然后将键值对放到ht[1]

            哈希表的指定位置上。

      3、当ht[0]的所有键值对都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新建空白哈希表。

      哈希表的收缩或者扩展

     1、 当满足以下任一条件,程序将对哈希表进行扩张:

         1)当服务器没有执行BGSAVE或者BGREWRITEAOF,并且负载因子大于1;

          2)当服务器在执行BGSAVE或者BGREWRITEAOF,并且负载因子大于5;

//判断是否要扩展字典空间static int _dictExpandIfNeeded(dict *d){    if (dictIsRehashing(d)) return DICT_OK;    if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);    if (d->ht[0].used >= d->ht[0].size &&        (dict_can_resize ||          d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))    {/*dict_can_resize为1表示服务器不在执行BGSAVE或者BGREWRITEAOF    dict_can_resize为0表示服务器正在执行BGSAVE或者BGREWRITEAOF*/        return dictExpand(d, d->ht[0].used*2);    }    return DICT_OK;}//给ht[1]分配第一个大于等于size的2的n次方大小的空间int dictExpand(dict *d, unsigned long size){    dictht n;     unsigned long realsize = _dictNextPower(size);//获取第一个大于等于size的2的n次方的值;    ……    n.size = realsize;    n.sizemask = realsize-1;    n.table = zcalloc(realsize*sizeof(dictEntry*));    n.used = 0;    ……    //为ht[1]分配空间,启动rehash    d->ht[1] = n;    d->rehashidx = 0;    return DICT_OK;}

     2、当负载因子小于0.1时,程序将对哈希表进行收缩(主要为了节省空间)。

//尝试收缩字典空间节省内存void tryResizeHashTables(int dbid) {    if (htNeedsResize(server.db[dbid].dict))        dictResize(server.db[dbid].dict);    if (htNeedsResize(server.db[dbid].expires))        dictResize(server.db[dbid].expires);}//判断字典释放需要收缩空间int htNeedsResize(dict *dict) {    long long size, used;    size = dictSlots(dict);    used = dictSize(dict);    //空间大于DICT_HT_INITIAL_SIZE,负载因子小于0.1    return (size > DICT_HT_INITIAL_SIZE &&            (used*100/size < HASHTABLE_MIN_FILL));}//重设字典的空间int dictResize(dict *d){    int minimal;    if (!dict_can_resize || dictIsRehashing(d)) return DICT_ERR;    minimal = d->ht[0].used;    if (minimal < DICT_HT_INITIAL_SIZE)        minimal = DICT_HT_INITIAL_SIZE;    return dictExpand(d, minimal);}

        渐进式rehash

      在redis中,rehash操作不是一次性完成的,而是分多次完成。这样做的目的主要是在redis中的dict可能会保存百万级、

千万级甚至亿级别个键值对,如果一次性将所有的键值对全部rehash到ht[1],巨大的的运算量将会导致redis在期间停止对外

服务。渐进式的rehash的实现关键是dict维持一个索引计数器rehashidx,并将它设为0,表示rehash开始。在rehash进行

期间,每次对字典执行添加、删除、查找或者更新操作时,程序会顺带从ht[0]上rehashidx索引开始遍历将第一个非空的哈

希桶的所有的键值对rehash到ht[1],rehash完成后,rehashidx加一。

      在进行渐进式rehash过程中,dict同时使用ht[0]和ht[1]两个哈希表,所以字典的删除,查找,更新都会在两个哈希表上

进行。新添加的键值对都是一律保存到ht[1]里面,以保证ht[0]的键值对数量只减不增;其余操作都是先在ht[0]上查找,找

不到再到ht[1]查找进行操作。

//将d->ht[0]中从索引d->rehashid开始的n个非空哈希桶的键值对迁移到ht[1]int dictRehash(dict *d, int n) {    int empty_visits = n*10;     if (!dictIsRehashing(d)) return 0;    while(n-- && d->ht[0].used != 0) {        dictEntry *de, *nextde;        //从d->rehashidx开始遍历获取第一个非空哈希桶        assert(d->ht[0].size > (unsigned long)d->rehashidx);        while(d->ht[0].table[d->rehashidx] == NULL) {            d->rehashidx++;//遍历了n*10个空桶,就停止迁移            if (--empty_visits == 0) return 1;        }        de = d->ht[0].table[d->rehashidx];        //遍历哈希桶,将键值对重新哈希到ht[1]        while(de) {            unsigned int h;            nextde = de->next;            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;    }    return 1;}static void _dictRehashStep(dict *d) {    if (d->iterators == 0) dictRehash(d,1);}
选几个函数,具体看了一下渐进式rehash的键值对迁移和期间字典操作实现
dictEntry *dictAddRaw(dict *d, void *key){    ……//顺带迁移一个非空哈希桶上的所有键值对到ht[1]    if (dictIsRehashing(d)) _dictRehashStep(d);    ……//如果在rehash,直接保存到ht[1]    ht = dictIsRehashing(d) ? &d->ht[1] : &d->ht[0];    entry = zmalloc(sizeof(*entry));    entry->next = ht->table[index];    ht->table[index] = entry;    ht->used++;    ……}static int dictGenericDelete(dict *d, const void *key, int nofree){    ……//顺带迁移一个非空哈希桶上的所有键值对到ht[1]    if (dictIsRehashing(d)) _dictRehashStep(d);    ……//先在ht[0]上查找,找不到再到ht[1]查找进行操作    for (table = 0; table <= 1; table++) {        idx = h & d->ht[table].sizemask;        he = d->ht[table].table[idx];        ……//遍历he,删除指定的键值对    }    return DICT_ERR; /* not found */}dictEntry *dictFind(dict *d, const void *key){    ……//顺带迁移一个非空哈希桶上的所有键值对到ht[1]    if (dictIsRehashing(d)) _dictRehashStep(d);    ……//先在ht[0]上查找,找不到再到ht[1]查找进行操作    for (table = 0; table <= 1; table++) {        idx = h & d->ht[table].sizemask;        he = d->ht[table].table[idx];        ……//遍历he,查找指定键值对    }    return NULL;}


0 0