美文网首页程序员
Redis 源码--字典P3 初始化和Rehash。

Redis 源码--字典P3 初始化和Rehash。

作者: namelessEcho | 来源:发表于2017-10-01 11:01 被阅读0次

一个字典所要做的最重要的工作就是保证快速的查找,删除和修改值。

首先可以来看一下Redis的初始化过程。

Redis的字典的初始方法是

dict *dictCreate(dictType *type,
        void *privDataPtr)
{
 // 为dict结构体分配内存
    dict *d = zmalloc(sizeof(*d));
//初始化
    _dictInit(d,type,privDataPtr);

    return d;
}

通过调用 _dictInit(d,type,privDataPtr)来实现初始化。

int _dictInit(dict *d, dictType *type,
        void *privDataPtr)
{
    // 初始化两个哈希表的各项属性值
    // 但暂时还不分配内存给哈希表数组
    _dictReset(&d->ht[0]);
    _dictReset(&d->ht[1]);

    // 设置类型特定函数
    d->type = type;

    // 设置私有数据
    d->privdata = privDataPtr;

    // 设置哈希表 rehash 状态
    d->rehashidx = -1;

    // 设置字典的安全迭代器数量
    d->iterators = 0;

    return DICT_OK;
}

这个方法对于dict结构的各个配置做了初始化。应该注意_dictReset()方法并未分配内存给dictht结构中的entry数组,只是简单的初始化(赋给null)。

static void _dictReset(dictht *ht)
{
  // 只赋值 ht->table  为null。
    ht->table = NULL;
    ht->size = 0;
    ht->sizemask = 0;
    ht->used = 0;
}

此时的核心数组还没有初始化,这个过程是通过dictExpand方法来实现的。
这个方法遵循这样一个规则,在方法体内初始化一个长度大于等于我们所要SIZE的TABLE,(通过_dictNextPower(size)来实现),如果此时第一个table还没有初始化,那么通过将table赋给ht[0],dictExpand实现的是数组的初始化,如果已经初始化了,那么dictExpand将其赋给第二个table数组ht[1],通过将rehashindex置零,来实现之后的rehash过程。

int dictExpand(dict *d, unsigned long size)
{
    // 新哈希表
    dictht n; /* the new hash table */

    // 根据 size 参数,计算哈希表的大小
    // T = O(1)
    unsigned long realsize = _dictNextPower(size);

    /* the size is invalid if it is smaller than the number of
     * elements already inside the hash table */
    // 不能在字典正在 rehash 时进行
    // size 的值也不能小于 0 号哈希表的当前已使用节点
    if (dictIsRehashing(d) || d->ht[0].used > size)
        return DICT_ERR;

    /* Allocate the new hash table and initialize all pointers to NULL */
    // 为哈希表分配空间,并将所有指针指向 NULL
    n.size = realsize;
    n.sizemask = realsize-1;
    // T = O(N)
    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. */
    // 如果 0 号哈希表为空,那么这是一次初始化:
    // 程序将新哈希表赋给 0 号哈希表的指针,然后字典就可以开始处理键值对了。
    if (d->ht[0].table == NULL) {
        d->ht[0] = n;
        return DICT_OK;
    }
  /* Prepare a second hash table for incremental rehashing */
    // 如果 0 号哈希表非空,那么这是一次 rehash :
    // 程序将新哈希表设置为 1 号哈希表,
    // 并将字典的 rehash 标识打开,让程序可以开始对字典进行 rehash
    d->ht[1] = n;
    d->rehashidx = 0;
    return DICT_OK;

下面来看一下dict的rehash过程,Redis的rehash不是一蹴而就的,而是以桶为单步进行的单步式渐进型rehash。对于键值对数量非常庞大的情况,如果一下子将所有的键值对都进行hash,那么大部分CPU时间都将花在rehash过程中,此时的响应是非常差的。所以Redis采取分步式的逐步渐进。
这里使用了一个宏来#define dictIsRehashing(ht) ((ht)->rehashidx != -1) ,简单的判断rehashindex是否是-1来判断是否在rehash,后面也会大量的出现这个宏。
我对这个函数稍微有点疑问,主要在于

  // 确保 rehashidx 没有越界
        assert(d->ht[0].size > (unsigned)d->rehashidx);

        // 略过数组中为空的索引,找到下一个非空索引
        while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;

这个部分,疑问是 这里是否在while之中也应该加入一句防御性语句(d->ht[0].size > (unsigned)d->rehashidx),改为 while((d->ht[0].size > (unsigned)d->rehashidx)&&(d->ht[0].table[d->rehashidx] == NULL))保证rehashidx小于等于d->ht[0].size-1。

int dictRehash(dict *d, int n) {

    // 只可以在 rehash 进行中时执行
    if (!dictIsRehashing(d)) return 0;

    // 进行 N 步迁移
    // T = O(N)
    while(n--) {
        dictEntry *de, *nextde;

        /* Check if we already rehashed the whole table... */
        // 如果 0 号哈希表为空,那么表示 rehash 执行完毕
        // T = O(1)
        if (d->ht[0].used == 0) {
            // 释放 0 号哈希表
            zfree(d->ht[0].table);
            // 将原来的 1 号哈希表设置为新的 0 号哈希表
            d->ht[0] = d->ht[1];
            // 重置旧的 1 号哈希表
            _dictReset(&d->ht[1]);
            // 关闭 rehash 标识
            d->rehashidx = -1;
            // 返回 0 ,向调用者表示 rehash 已经完成
            return 0;
        }

        /* Note that rehashidx can't overflow as we are sure there are more
         * elements because ht[0].used != 0 */
        // 确保 rehashidx 没有越界
        assert(d->ht[0].size > (unsigned)d->rehashidx);

        // 略过数组中为空的索引,找到下一个非空索引
        while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;

        // 指向该索引的链表表头节点
        de = d->ht[0].table[d->rehashidx];
        /* Move all the keys in this bucket from the old to the new hash HT */
        // 将链表中的所有节点迁移到新哈希表
        // T = O(1)
        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;

            // 首部插入节点到新哈希表,效率更高O(1)
            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;
        // 更新 rehash 索引
        d->rehashidx++;
    }

    return 1;
}

Redis的rehash过程只有在不存在安全迭代器的情况下才可以进行。如果存在一个安全迭代器,我们没有办法保证在两种不同的迭代和修改操作(迭代器和Rehash过程)不会弄乱字典。而非安全迭代器被限定为只能迭代,不能改变或者插入值。所以没有关系。
所以Redis的单步Rehash过程是这样定义的:

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

这个函数被大量的查找、更新操作调用时,在不存在安全迭代器的条件下,
可以让字典在被使用的同时进行 rehash 。

相关文章

  • Redis 源码--字典P3 初始化和Rehash。

    一个字典所要做的最重要的工作就是保证快速的查找,删除和修改值。 首先可以来看一下Redis的初始化过程。 Redi...

  • redis笔记

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

  • redis源码5--字典Dict:初始化api 及 rehash

    本文先介绍字典的初始化相关api,以及rehash相关的函数,并以向字典添加key,value为例,介绍rehas...

  • redis-hash

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

  • Redis深度历险 - dict的rehash

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

  • 思考题

    1.java hashMap和redis map的rehash有什么区别? Java hashMap rehash...

  • Reids 字典

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

  • Redis 数据结构之字典

    redis的字典数据结构是由哈希表实现的,字典内设有两个哈希表,一个用于存储数据,一个用于rehash时使用。字典...

  • Redis 源码--字典P3 查找,删除和修改。

    Redis的查找dictAdd通过一个底层的dictAddRaw方法来实现。 前面提到,如果在rehash过程中且...

  • 1.2 链表、字典

    每个字典的底层采用哈希表实现,每个字典带有两个哈希表,一个平常使用,一个仅在rehash时使用。redis使用mu...

网友评论

    本文标题:Redis 源码--字典P3 初始化和Rehash。

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