12 October 2017

      dict是一个key-value存储的hash map,实际上redis对外提供的key-value服务的底层数据结构就是dicthash map由一个hash算法来确定数据的在数组中的放置位置,dict使用开链法解决冲突。

dict的数据结构:

typedef struct dictEntry {
    void *key;
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    struct dictEntry *next;
} dictEntry;

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;

typedef struct dictht {
    dictEntry **table;
    unsigned long size;
    unsigned long sizemask;
    unsigned long used;
} dictht;

typedef struct dict {
    dictType *type;
    void *privdata;
    dictht ht[2];
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int iterators; /* number of iterators currently running */
} dict;

      dictEntrykey-valuevalue的存储节点,其中存储了keyvnextnext指向下一个节点用于解决hash冲突

      dictType定义了一组函数指针用来指定hash算法、比较、复制和销毁等方法。

      dictht定义了一个哈希表,其中size是哈希表数组的大小,sizemask用于和hash函数一同计算一个key-value对在数组中的索引位置。used记录保存的key-value对的数量。

      dict中包含两个哈希表dictht,当进行rehash操作时会用到ht[1],由rehashidx记录当前rehash的进度,这个进度即hash数组的索引号,如果没有rehash操作则rehashidx的值为-1

      rehash操作:随着对redis的操作,会有大量的数据加入到hash表中。遇到这情况都会进行rehash操作,重新确定hash数组的大小,放置键值对数据。触发rehash操作是由一个负载因子(load factor)决定的,计算方法也比较简单:load factor = used / size

rehash的过程如下:

  1. ht[1]分配空间

  2. ht[0]的数据迁移到ht[1]

  3. 清空ht[0],交换ht[0]ht[1]的指针

可以看到最终结果数据还是在ht[0]中访问。

 

rehash分两种:

自然rehash:需满足used/size >= 1 && dict_can_resize条件时触发(dict_can_resize表示目前没有执行BGSAVEBGREWRITEAOF命令)

强制rehash:需满足load factor>dict_force_resize_ratio(默认值为5)条件时触发

分配的空间大小根据公式ht[0].used*2 < min(2^n),设置为min(2^n)

      rehash过程采用了渐进式rehash,主要是为了性能考虑,不能一次将全部或大量的键值对rehashht[1]中,而是每次将一个hash数组索引号下的所有键值对rehashht[1]中,下一次rehash时再执行下一个索引号,直到所有的索引号全部执行完毕,完成rehash操作。在一次渐进式rehash中,如果连续发现10个索引号下都是空的,则只修改rehashidx,退出这次渐进式rehash过程,这也是从性能上考虑。

      另外,dict还提供了一个dictRehashMilliseconds方法,由服务器常规任务程序执行,这时一次渐进式rehash过程处理100个索引号,直到耗时大于1ms时结束。

      在rehash过程中,当有新节点插入到dict中时,会直接插入到ht[1]中,而非ht[0]中,每次渐进式rehash步骤,会把ht[0]中的键值对放到ht[1]中,因此可知,ht[0].usedrehash时只减不增,直至为0

      dict对外提供的接口包括增、删、改、查、创建和销毁等。