1.3. Redis数据结构-dict

101 阅读3分钟

dict ,也叫字典(和python的命名一样),对于java 程序员,可能更熟悉称为hashmapredisdictGomap 类似。 底层双table+linkedlist。存在rehash操作。

数据结构

底层hashtable的数据结构

typedef struct  dictht {
    tableEntry ** table;//底层实际存储值的数组。存储的是tableEntry结构体的指针
    unsigned long size;//数组长度,始终为2的n次方
    unsigned long sizemark; //hash表大小掩码,用于计算索引值,大小始终为size-1. 
    unsigned long used;//已有的节点数量。 
}

tableEntry的结构定义如下


typedef struct tableEntry{
    void * key; 
    union {
        void * val;
        unit64_t u64;
        int64_t s64;
    }v; //节点值为一个联合结构体.
    struct tableEntry * next;//hash冲突的时候,组成的链表.因为只有next,为了快速 查询,新的冲突节点始终在头部
}

如何确认联合结构体使用的是哪一个字段?(我也没明白)

dict数据结构

typedef stuct dict {
    dictht ht[2];//hashtable表
    dictType *type;//类型特定函数
    void * privdata;//私有数据
    int rehashidx ;//未进行`rehash`的时候`rehash`为`-1`
}
  • type是为了实现多态函数.针对不同数据类型的函数提供不同的功能.dictTyp内部包含了很多的
    typedef stuct dictType{
        unsigned int (*hashFun)(const void * key)//hash函数
        void (*keyDup)(void * privdata,const void * key);//复制key的函数
        void (*valDup)(void * privdata,const void * obj);//复制值的函数
        int (*keyCompare)(void * privdata,const void * key1,const * key2);//key比较函数
        void (*keyDestructor)(void * privdata,void *key);//key销毁函数
        void (*valueDestructor)(void * privdata,void *value);//key销毁函数
    } 
    
  • privdata 为传递给type函数的特定值.
  • rehash记录当前的hash进度.-1表示为rehash

hash 值计算

hash = dict->type->hashFun(key)
hashIdx = hash & dict->ht[x]->marksize

redis使用的hash函数为MurmurHash2

渐进式rehash

  1. 负载因子:used/size 已有的节点数/(dictht.table的size)
  2. reindex实际上指的就是内部table表的索引. 在reIndex!=-1的时候,表示开始rehash.此时redis在对dict执行任何操作的时候,都会对table[reIndex]上面的节点进行迁移.然后reIndex+1.直到reIndex = table.siz-1.

rehash过程

  1. 计算是否超过指定的负载因子(分为bgsave和非bgsave两种情况,前者为1,后者为5).如果超过.执行rehash
  2. 判断当前使用的ht大小是否超过特定的值.如果没有.一次rehash结束.否则采用渐进式rehash
  3. 根据当前的hashtable大小来创建新ht的大小.一般是两倍
  4. ht[0]table数组的0位置开始.重新计算hashIdx,存放到ht[1]中.当rehash单次执行次数到了,记录下一个需要rehash的下表到rehash中.
  5. ht[0]中所有的节点都迁移结束后,rehash=-1,是否ht[0]的空间.将ht[0]执行ht[1],并创建一个新的dicththt[1](底层tablenull).

在执行rehash期间,新增key只能存放到新的dictht,既ht[1].如果在rehash的时候发现ht[1]已经存在相同的key时.释放掉老key. 查询的时候,先查询ht[1],在查询ht[0].两者都没有的时候就没有.

tips

  1. rehash分为扩展和收缩.
  2. rehash的效率问题.会不会阻塞请求?渐进式hash.每次请求的时候执行一次reshash操作。
  3. redis处于不同的情况下,对于是否rehash的负载因子值的要求不同. 低负载的时候大于1. 高负载的情况下大于5