Redis 字典的实现原理

139 阅读3分钟

背景

本章主要记录一下学习redis字典的一些总结

字典简介

字典,又称为符号表(symbol table)、关联数组(associative array)、映射(map),是一种用于保存键值对(key-value)的数据结构

redis底层字典的实现

redis的字典使用hash表作为底层实现,一个hash表里面可以有多个hash表节点,而每个hash表节点就保存了字段中的一个键值对。

redis数据结构


//字典对象
typeof struct dict {

    //类型特定函数
    dictType *type;
    
    //私有数据
    void *privdate;
    
    //hash表,用于reindex的时候时候,扩表以后互相传递数据
    dictht ht[2];
    
    //当reindex不进行的时候数值是-1.
    int rehashidx;
}

//提供了一些函数用户redis操作用
typeof struct dictType {
    // 计算hash值
    unsigned int (*hashFunction)(const void *key);
    
    //复制key
    void *(*keyDup)(void *privdata, const void *key);
    
    //复制值
    void *(*valDup)(void *privdata, const void *obj);
    
    //对比键
    int (*keyCompare)(void *privdata, void *key);
    
    //销毁值
    void (*valDestructor) (void *privdata, void *obj);
} dictType;

typedef struct dictht {
    //hash表数组
    dictEntry **table;
    
    // hash表大小
    unsigned long size;
    
    //hash表大小掩码,用户计算hash值,总是等于size-1
    unsigned long sizemark;
    
    //hash表已使用节点数量
    unsigned long used;

}

typedef struct dictEntry {
    //键
    void *key;
    
    //值
    union {
        //指针,指向其他数据
        void *val;
        //整数
        uint64_t u64;
        //整数
        int64_t s64;
    } v;
    
    //链表中下一个节点
    struct disctEntry *next;
}


字典结构图

image.png

hash算法

当有新的key添加到字典的时候,会进行如下步骤
1.hash = dict -> type -> hashFunction(key);
2.index = hash & dict -> ht[x].sizemark;//根据情况的不同ht[x]是ht[0]或者ht[1],当reindex进行的时候就可能是ht[1]
得到的index就是dictht对象中的table的下标

hash冲突

其实跟hashmap里面的解决方案是一样的,这里不重复了。哈哈

rehash

出现场景

hash负载因子计算公式 = hash表中的key个数 / hash表大小
随着操作不断执行,hash表会增加或者减少,为了让hash表的负载因子(key的size/hashtable的size)在一个合理范围内,hash表会适当进行扩展或者收缩。 1.当服务器没有执行BGSAVE、BGREWRITEAOP,并且hash负载因子大于1的时候
2.当服务器正在执行BGSAVE、BGREWRITEAOP,并且hash负载因大于5的时候
因为当BGSAVE、BGREWRITEAOP进行时,redis服务器性能消耗比较大,不建议进行rehash操作,所以通过增加负载因子来提高rehash的门槛。

rehash步骤

1.为ht[1]分配空间为ht[0].used * 2的空间 2.将ht[0]上的值rehash到ht[1], 其中包含了重新计算hash值,然后放到ht[1]的指定位置 3.当所有的key迁移完了,将ht[1]设置成ht[0], 在ht[1]新建一个空的hash表,为下一次rehash做准备

在rehash的时候,数据如何保证一致性?(中途有数据变更怎么办)

由于hash表中的key可能非常多,几百万、甚至上千万,所以reindexhash操作是异步进行的。那么如何保证2个ht的数据一致性呢。dict结构中有一个字段是rehashidx,当ht[0]中某个key变更的时候,会将key对应的下标记录到rehashidx,那么rehash的时候会同事将hash表中下标为rehashidx的数据复制到ht[1]中。具体的逻辑图见下图

image.png

重点回顾

  1. redis字典已hash表作为底层实现,每个字典中有2个hash表,平时只只用一个,另外一个在reindex的时候使用
  2. hash表用链地址法解决hash冲突,也就是hash表中加一个单项链表
  3. redis中进行rehash的时候,并不是一次性完成的,因为key可能非常多,其中需要处理数据一致性的问题。