Redis-字典遍历内部实现原理源码

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zanpengfei/article/details/86519134

1、遍历Redis

1)Redis对象树的主干是一个字典,如果对象很多,那么这个树会很大,当我们执行keys命令时,首先它会遍历整棵树,查找出满足条件的key,其次会判断遍历出的key对应的对象是否过期,如果过期则从主干中删除掉

void keysCommand(client *c) {

    dictIterator *di; // 迭代器

    dictEntry *de; // 迭代器当前的entry

    sds pattern = c->argv[1]->ptr; // keys的匹配模式参数

    int plen = sdslen(pattern);

    int allkeys; // 是否要获取所有key,用于keys *这样的指令

    unsigned long numkeys = 0;

    void *replylen = addDeferredMultiBulkLength(c);

    // why safe?

    di = dictGetSafeIterator(c->db->dict);

    allkeys = (pattern[0] == '*' && pattern[1] == '\0');

    while((de = dictNext(di)) != NULL) {

        sds key = dictGetKey(de);

        robj *keyobj;

 

        if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {

            keyobj = createStringObject(key,sdslen(key));

            // 判断是否过期,过期了要删除元素

            if (expireIfNeeded(c->db,keyobj) == 0) {

                addReplyBulk(c,keyobj);

                numkeys++;

            }

            decrRefCount(keyobj);

        }

    }

    dictReleaseIterator(di);

    setDeferredMultiBulkLength(c,replylen,numkeys);

}

2、迭代器结构

1)Redis为字典的遍历提供了2种迭代器,一种是安全迭代器,一种是不安全的迭代器

typedef struct dictIterator {

    dict *d; // 目标字典对象

    long index; // 当前遍历的槽位置,初始化为-1

    int table; // ht[0] or ht[1]

    int safe; // 这个属性非常关键,它表示迭代器是否安全

    dictEntry *entry; // 迭代器当前指向的对象

    dictEntry *nextEntry; // 迭代器下一个指向的对象

    long long fingerprint; // 迭代器指纹,放置迭代过程中字典被修改

} dictIterator;

// 获取非安全迭代器,只读迭代器,允许rehashStep

dictIterator *dictGetIterator(dict *d)

{

    dictIterator *iter = zmalloc(sizeof(*iter));

 

    iter->d = d;

    iter->table = 0;

    iter->index = -1;

    iter->safe = 0;

    iter->entry = NULL;

    iter->nextEntry = NULL;

    return iter;

}

 

// 获取安全迭代器,允许触发过期处理,禁止rehashStep

dictIterator *dictGetSafeIterator(dict *d) {

    dictIterator *i = dictGetIterator(d);

    i->safe = 1;

    return i;

}

2)迭代器安全是指在遍历过程中,能够对迭代器进行读和修改遍历过程中不会出现重复元素,为了保证不重复,就要禁止rehashStep操作,安全迭代器在开始遍历时,会给字典打上一个标记,有了这个标记,rehashStep就不会进行了。

3)不安全迭代器是指在遍历过程中,只能对迭代器进行读操作,只能调用dictNext对字典进行持续遍历,不能调用任何触发过期函数的操作,但是不安全迭代器会出现重复元素

4)禁止rehash方法

typedef struct dict {

    dictType *type;

    void *privdata;

    dictht ht[2];

    long rehashidx;

    // 这个就是标记,它表示当前加在字典上的安全迭代器的数量

    unsigned long iterators;

} dict;

// 如果存在安全的迭代器,就禁止rehash

static void _dictRehashStep(dict *d) {

    if (d->iterators == 0) dictRehash(d,1);

}

3、迭代过程

1)遍历链表方法

dictEntry *dictNext(dictIterator *iter)

{

    while (1) {

        if (iter->entry == NULL) {

            // 遍历一个新槽位下面的链表,数组的index往前移动了

            dictht *ht = &iter->d->ht[iter->table];

            if (iter->index == -1 && iter->table == 0) {

                // 第一次遍历,刚刚进入遍历过程

                // 也就是ht[0]数组的第一个元素下面的链表

                if (iter->safe) {

                  // 给字典打安全标记,禁止字典进行rehash

                  iter->d->iterators++;

                } else {

                  // 记录迭代器指纹,就好比字典的md5

                  // 如果遍历过程中字典有任何变动,指纹就会改变

                  iter->fingerprint = dictFingerprint(iter->d);

                }     

            }

            iter->index++; // index=0,正式进入第一个槽位

            if (iter->index >= (long) ht->size) {

                // 最后一个槽位都遍历完了

                if (dictIsRehashing(iter->d) && iter->table == 0) {

                    // 如果处于rehash中,那就继续遍历第二个 hashtable

                    iter->table++;

                    iter->index = 0;

                    ht = &iter->d->ht[1];

                } else {

                    // 结束遍历

                    break;

                }

            }

            // 将当前遍历的元素记录到迭代器中

            iter->entry = ht->table[iter->index];

        } else {

            // 直接将下一个元素记录为本次迭代的元素

            iter->entry = iter->nextEntry;

        }

        if (iter->entry) {

            // 将下一个元素也记录到迭代器中,这点非常关键

            // 防止安全迭代过程中当前元素被过期删除后,找不到下一个需要遍历的元素

           

            // 试想如果后面发生了rehash,当前遍历的链表被打散了,会发生什么

            // 这里要使劲发挥自己的想象力来理解

            // 旧的链表将一分为二,打散后重新挂接到新数组的两个槽位下

            // 结果就是会导致当前链表上的元素会重复遍历

           

            // 如果rehash的链表是index前面的链表,那么这部分链表也会被重复遍历

            iter->nextEntry = iter->entry->next;

            return iter->entry;

        }

    }

    return NULL;

}

 

// 遍历完成后要释放迭代器,安全迭代器需要去掉字典的禁止rehash的标记

// 非安全迭代器还需要检查指纹,如果有变动,服务器就会奔溃(failfast)

void dictReleaseIterator(dictIterator *iter)

{

    if (!(iter->index == -1 && iter->table == 0)) {

        if (iter->safe)

            iter->d->iterators--; // 去掉禁止rehash的标记

        else

            assert(iter->fingerprint == dictFingerprint(iter->d));

    }

    zfree(iter);

}

 

// 计算字典的指纹,就是将字典的关键字段进行按位糅合到一起

// 这样只要有任意的结构变动,指纹都会发生变化

// 如果只是某个元素的value被修改了,指纹不会发生变动

long long dictFingerprint(dict *d) {

    long long integers[6], hash = 0;

    int j;

 

    integers[0] = (long) d->ht[0].table;

    integers[1] = d->ht[0].size;

    integers[2] = d->ht[0].used;

    integers[3] = (long) d->ht[1].table;

    integers[4] = d->ht[1].size;

    integers[5] = d->ht[1].used;

 

    for (j = 0; j < 6; j++) {

        hash += integers[j];

        hash = (~hash) + (hash << 21);

        hash = hash ^ (hash >> 24);

        hash = (hash + (hash << 3)) + (hash << 8);

        hash = hash ^ (hash >> 14);

        hash = (hash + (hash << 2)) + (hash << 4);

        hash = hash ^ (hash >> 28);

        hash = hash + (hash << 31);

    }

    return hash;

}

2)字典扩容时,进行rehash,将旧的数组链表迁移到新的数组下面,某个槽位下的链表只会迁移到新数组的俩个槽位中

hash mod 2^n = k

hash mod 2^(n+1) = k or k+2^n

4、迭代器的选择

1)遍历过程中不允许出现重复对象,就选择安全迭代器,否则选择不安全迭代器

2)bgaofrewrite指令需要遍历所有的对象转换哼操作指令进行持久化,那么绝对不允许出现重复

3)bgsave需要遍历所有对象进行持久化,同样不允许出现重复

4)遍历过程中如果需要处理过期元素,对字典进行修改,那就必须使用安全遍历器

5)其他情况下,可以选择不安全遍历器

猜你喜欢

转载自blog.csdn.net/zanpengfei/article/details/86519134
今日推荐