美文网首页
redis字典dict——Part3:rehash

redis字典dict——Part3:rehash

作者: fred290 | 来源:发表于2019-03-06 21:33 被阅读0次

随着redis不断插入或者删除数据,dict保存的键值对也会增多或者减少,此时dict也会进行相对应的扩容和缩容,这些操作主要通过rehash来完成的。

dict的扩容

如果是扩容,首先判断是否需要进行扩容

  • 1,如果在重哈希的过程中,则直接返回
  • 2,如果此时dict的容量为0,也就是才进行了初始化,则将其容量扩容从DICT_HT_INITIAL_SIZE,默认为4
  • 3,如果dict装载因子used/size>1.0且此时可以进行扩容,或者装载因子大于dict_force_resize_ratio(设置为5)时,此时强制进行扩容
  • 4,扩容过程中,容量提升一倍,此时初始化ht[1],同时更新rehashidx为0,代表要进行重哈希过程了
/* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *d)
{
    /* Incremental rehashing already in progress. Return. */
    if (dictIsRehashing(d)) return DICT_OK;

    /* 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);

    /* If we reached the 1:1 ratio, and we are allowed to resize the hash
     * table (global setting) or we should avoid it but the ratio between
     * elements/buckets is over the "safe" threshold, we resize doubling
     * the number of buckets. */
    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);
    }
    return DICT_OK;
}

/* Expand or create the hash table */
int dictExpand(dict *d, unsigned long size)
{
    dictht n; /* the new hash table */
    unsigned long realsize = _dictNextPower(size);

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    /* Rehashing to the same table size is not useful. */
    if (realsize == d->ht[0].size) return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    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;
    }

    /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;
}

/* Resize the table to the minimal size that contains all the elements,
 * but with the invariant of a USED/BUCKETS ratio near to <= 1 */
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);
}

dict的缩容

dict不光有扩容,也有缩容,当删除元素时,会检测装载因子是否小于HASHTABLE_MIN_FILL(默认10%),此时进行缩容,通过调用dictResize函数将size缩小为最接近used的数组2的n次方的大小。

int htNeedsResize(dict *dict) {
    long long size, used;

    size = dictSlots(dict);
    used = dictSize(dict);
    return (size > DICT_HT_INITIAL_SIZE &&
            (used*100/size < HASHTABLE_MIN_FILL));
}

rehash的过程

dictRehash每次将重哈希至少向前推进n步(除非不到n步整个重哈希就结束了),
每一步都将ht[0]上某一个bucket(即一个dictEntry链表)上的所有dictEntry移动到ht[1]上,在ht[1]上的新位置根据ht[1]的sizemask进行重新计算。
每次移动的dictEntry都会插入ht[1]的链表上的第一个位置
rehashidx记录了当前尚未迁移(有待迁移)的ht[0]的bucket位置。
如果dictRehash被调用的时候,rehashidx指向的bucket里一个dictEntry也没有,那么它就没有可迁移的数据。这时它尝试在ht[0].table数组中不断向后遍历,直到找到下一个存有数据的bucket位置。如果一直找不到,则最多走n*10步,本次重哈希暂告结束。
最后,如果ht[0]上的数据都迁移到ht[1]上了(即d->ht[0].used == 0),那么整个重哈希结束,ht[0]变成ht[1]的内容,而ht[1]重置为空。

三种情况下会结束这个rehash的过程:

  • 1,移动了ht[0]上n个dictEntry链表
  • 2,虽然没有移动ht[0]上n个dictEntry链表,但是遍历空的ht[0]的bucket已经达到n*10
  • 3,整个ht[0]所有的dictEntry已经重哈希到ht[1]
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];
        /* Move all the keys in this bucket from the old to the new hash HT */
        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++;
    }

    /* Check if we already rehashed the whole table... */
    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;
}

在前面介绍dict的添加,删除,查找和更新操作时,替代rehash的步骤都会穿插在其中进行,因此redis的rehash的过程是一个渐进式的过程,所有元素的rehash均衡分摊到各个操作中去,这样可以避免集中式rehash对机器带来的计算尖峰以及对redis功能的影响。

参考:

http://zhangtielei.com/posts/blog-redis-dict.html
《redis设计与实现》第二版

相关文章

  • redis字典dict——Part3:rehash

    随着redis不断插入或者删除数据,dict保存的键值对也会增多或者减少,此时dict也会进行相对应的扩容和缩容,...

  • Reids 字典

    Reids 字典dict 1、字典结构 包含两个哈希表dictht ht[2],用于rehash,rehash包含...

  • Redis深度历险 - dict的rehash

    Redis深度历险 - dict的rehash 字典是Redis中非常重要的一个数据结构,本质就是一个hash表;...

  • 2. 浅析Redis底层数据结构

    概要1)Redis中的字符串-sds2)Redis中的HashMap-dict3)dict的渐进式rehash4)...

  • 工作中遇到的hashtable

    一.redis 中使用的字典 redis的字典是由hash表实现的,代码主要是在dict.cpp/dict.h中 ...

  • redis笔记

    数据结构 SDS 字典 index确定 渐进式rehash 为了缓解一次性rehash带来的性能问题,redis提...

  • Redis数据结构--字典

    字典是Redis的重要数据结构,Redis的数据库就是使用字典作为底层实现的。代码位于dict.h和dict.c中...

  • Redis 字典(dict)

    Redis的字典(map)和Java里的HashMap类似,不过HashMap是尾插法(好像1.8之前的版本是头插...

  • Redis 数据结构与内存管理策略(下)

    字典(dict) dict字典是基于hash算法来实现,是Hash数据类型的底层存储数据结构。我们来看下redis...

  • redis-hash

    hash 字典 类似于java中的hashmap,数组加链表,碰撞进链表 区别: rehash过程不同,redis...

网友评论

      本文标题:redis字典dict——Part3:rehash

      本文链接:https://www.haomeiwen.com/subject/gvhfpqtx.html