历史文章

Redis源码阅读之字典(一)

Redis Rehash是什么?

在我们日常使用redis的过程中,随着key不断的增加,dict的size也在不断的增加,当dict.used == dict.size或者used*100/size < HASHTABLE_MIN_FILL,HASHTABLE_MIN_FILL一般为10,也就是说,要么容量不够,要么容量使用率小于10%了,就会调用dictExpand进行重新分配内存,这个时候就会触发rehash了。但是rehash不行一次性就操作完成了,试想一下,如果一个dict里面含有数百万的key,rehash一次可能会很久,就可能造成服务假死的情况。所以rehashing是一个长时间的过程,每一次可能只进行几个key的迁移。

哪些操作会触发rehashing的step

dictRehashMilliseconds

redis server的定时任务会去执行dictRehashMilliseconds,但是都是传入的ms==1,主要是rehashing一下redis的dict和expired相关的键

dictAddRaw

在给dict增加key的时候,新增的key会直接放入dict.ht[1]中

dictGenericDelete

删除key时

dictFind

查找key

dictGetRandomKey 和 dictGetSomeKeys

获取随机key

dictScan

遍历dict

每次rehash的过程

源码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107

/* Performs N steps of incremental rehashing. Returns 1 if there are still

 * keys to move from the old to the new hash table, otherwise 0 is returned.

 *

 * Note that a rehashing step consists in moving a bucket (that may have more

 * than one key as we use chaining) from the old to the new hash table, however

 * since part of the hash table may be composed of empty spaces, it is not

 * guaranteed that this function will rehash even a single bucket, since it

 * will visit at max N*10 empty buckets in total, otherwise the amount of

 * work it does would be unbound and the function may block for a long time. */

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) {

            uint64_t 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;

}

  • 参数n是指step的次数

  • rehash的过程就是不断的把老数据从ht[0]中迁移到ht[1]中,最后迁移完成,把ht[1]赋值给ht[0],然后置空ht[1]

  • 如果rehash完成,返回0, 反之返回1