redis中的字典结构是怎样的?

点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。

文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。

基础概念

redis支持的5种数据类型中,有hash类型,hash类型的底层采用字典结构(多对key-value)实现,而字典结构的代码实现=hashTable=用到了hash表

字典结构的实现

字典结构由三种结构组合而成:字典结构=dict+dictht+dictEntry,关系如下:
image

代码实现:

typedef struct dict {
    dictType *type;     //dictType也是一种数据结构,dictType结构中包含了一些函数,这些函数用来计算key的哈希值,进而用这个哈希值计算key在dictEntry型table数组中的下标
    void *privdata;     //私有数据,保存着dictType结构中函数的参数
    dictht ht[2];       //两张哈希表:一张用来正常存储节点,一张用来在rehash时临时存储节点
    long rehashidx;     //rehash的标记:默认-1,当table数组中已有元素个数增加/减少到一定量时,整个字典结构将进行rehash给每个table元素重新分配位置,rehashidx代表rehash过程的进度,rehashidx==-1代表字典没有在进行rehash,rehashidx>-1代表该字典结构正在对进行rehash
} dict;

typedef struct dictht { //哈希表
    dictEntry **table;      //存放一个数组的地址,每个数组元素存放的是哈希表节点dictEntry的地址
    unsigned long size;     //哈希表的table数组长度小,初始化大小为4
    unsigned long sizemask; //哈希表掩码sizemask的值总是等于(size-1),用于计算每个key在table中的下标位置=hash(key)&sizemask
    unsigned long used;     //记录哈希表的table中已有的节点数量(节点=dictEntry=键值对)。
} dictht;

typedef struct dictEntry {
    void *key;//键
    union{     //值
        void *val;//值可以是指针
        uint64_tu64;//值可以是无符号整数
        int64_ts64;//值可以是带符号整数
    } v;
    struct dicEntry *next;//指向下个dictEntry节点:redis的字典结构采用链表法解决hash冲突,当table数组某个位置处已有元素时,该位置采用头插法形成链表解决hash冲突
} dictEntry;

用key计算(key-value)在table中的位置下标:

//1、先计算key的hash值:使用字典中计算key哈希值的函数
hash = dict结构->dictType结构的函数hashFunction(key)
//2、根据hash值与哈希表掩码sizemask进行“与运算”得到下标,x=(0或1)=两张哈希表中用来正常存储节点的那张哈希表的下标
index = hash & dict->ht[x].sizemask;
字典中的负载因子及rehash

先来看看几个重要概念:

redis字典中哈希表的rehash=扩展或收缩哈希表中的table数组长度;

负载因子=dict结构的ht[0].used/dict结构的ht[0].size=哈希表的table中已有的节点数量/哈希表的table数组长度;

bgsave操作:将redis内存中的数据以rdb的形式持久化到磁盘;

bgrewriteaof操作:将redis内存中的数据以aof的形式持久化到磁盘中,持久化成功后旧的aof文件会被替换(redis2.4之后aof由redis自动触发,而bgrewriteaof需要手动地触发)

redis中的哈希表什么时候进行扩展操作?

两种情况:

当没有bgsave操作 && 没有bgrewriteaof操作 && 负载因子>=1时;

当(正在bgsave操作 || 正在bgrewriteaof操作) && 负载因子>=5时;

负载因子为什么会>=1?因为当hash冲突时,新节点会以头插法的形式插入哈希表的table数组某个位置中形成链表,就会使table中节点的总数量>table数组长度,进而负载因子>=1

redis中的哈希表什么时候进行扩展操作?

当负载因子<=0.1时(如table数组长度经过多次扩展变为了16,某时刻table只有1个元素,1/16=0.0625<0.1,则哈希表进行rehash收缩table长度)

redis中的哈希表进行扩展或缩收缩的过程?

扩展:dict数据结构中有两张哈希表ht[2],ht[0]拿来正常地装redis数据(key-value),ht[1]用来进行rehash扩展时存放ht[0]的元素,直到ht[0]中所有元素被重新计算下标存放到ht[1]中的table数组为止,ht[1]的大小=第一个大于等于ht[0].used的2^n

收缩:同"扩展"操作一样,ht[1]拿来装在收缩过程中ht[0]的元素,ht[1]的大小=第一个大于等于ht[0].used的2^n(公式与扩展一样)

特点:

  • 扩展或收缩完成后,释放哈希表ht[0],并把哈希表ht[1]置为ht[0],然后再重新分配一个新的空白ht[1]作为下次rehash使用
  • 在rehash(扩展或收缩)过程中有两张哈希表,并且字典会同时使用两张哈希表:查找、删除、更新会同时操作两张哈希表(先在ht[0]中找,找不到再去ht[1]中找),'插入’则只操作ht[1]
  • rehash过程是渐进式的,它采取分而治之的方法,以扩展为例,一开始先将rehashidx值由-1置为0代表rehash工作开始:

    此时rehashidx为0,第一次对redis字典进行【添加、删除、查找或者更新】操作,则对ht[0]中table[rehashidx]=table[0]的数据重新计算下标放到ht[1]的table数组中,然后rehashidx自增为1;

    此时rehashidx为1,第二次对redis字典进行【添加、删除、查找或者更新】操作,则对ht[0]中table[rehashidx]=table[1]的数据重新计算下标放到ht[1]的table数组中,然后rehashidx自增为1

    …以此类推,直到ht[0]中的所有table元素都被重新计算下标rehash到ht[1]中为止,最后把rehashidx置为-1,此时rehash完成;

渐进式rehash可以把这个过程中的计算压力分摊到每次对字典进行【添加、删除、查找或者更新】操作的时候,避免在某一时刻对整个hash类型数据(redis的5中数据类型之一)进行庞大的rehash计算,进而避免了redis阻塞,唯一不好的地方就是在rehash时同时使用两个哈希表,导致redis内部使用量暴增

OK,如果文章哪里有错误或不足,欢迎各位留言。

创作不易,各位的「三连」是二少创作的最大动力!我们下期见!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值