redis专题笔记 - 字典dict

底层数据结构

// 字典本身数据结构
struct dict {
    dictType *type;
    void *privdata; 
    dictht ht[2];     // 2个哈希表,真正存储数据的地方;正常情况只会用其中一个,另一个在渐进式扩容时使用
    int rehashidx;    // 渐进式hash的进度,其他情况下为-1
}

// 单个哈希表数据结构
struct dictht {
	dictEntry** table;    // dictEntry指针列表
	long size;            // 第一维数组的长度
	long used;            // hash表中的元素个数
}

// 单个哈希表中元素的数据结构
struct dictEntry {
	void* key;            // 当前元素的key值
	void* val;            // 当前元素的value值
	dictEntry* next;      //  指向下一个元素的指针
}

redis底层hashtable使用分桶的方式解决hash冲突。第一维是数组,第二维是链表,其hash结构大致如图:

在这里插入图片描述

渐进式rehash

渐进式rehash的原因

扩容/缩容的操作需要重新申请一块更大/更小的内存空间,然后将hash中的所有key一个个复制到新的hash中,这是一个O(n)的操作,如果遇到比较大的字典,整个复制过程耗时很久;redis又是一个单线程服务,如果线程陷于字典的复制,则不能处理其他的请求,所以需要把hash复制的操作分散开,尽量不影响redis单线程对外提供服务

rehash过程

redis的dict有两个哈希map: ht[2], 假设ht[0]正在使用,ht[1]为空,此时需要扩容,其过程如下:

  1. 为ht[1]分配空间,让dict字典同时持有 ht[0] 和 ht[1] 两个哈希表。
  2. 在字典中维持一个索引计数器变量rehashidx,并将它的值设置为0,表示rehash工作正式开始。
  3. 在rehash进行期间,每次对字典执行添加、删除、查找或者更新操作时,程序除了执行指定的操作以外,还会顺带将ht[0]哈希表在 rehashidx索引(table[rehashidx]桶上的链表)上的所有键值对rehash到ht[1]上,当rehash工作完成之后,将rehashidx属性的值增一,表示下一次要迁移链表所在桶的位置。
  4. 随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有桶对应的键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成。

定时rehash

上述rehash过程依赖于后续客户端对字典的增删改查,如果在rehash刚发生后,客户端停止请求这个map,这种情况会导致dict一致保持两个hashtable吗?不会,此时定时任务会对字典进行主动搬迁

关于定时任务,其执行时机可以在前一篇笔记io模型中查到踪迹;大致情形是,redis工作线程在每次事件循环执行完read和write后,会执行一些其他任务,其中就包括定时任务执行

注:知识点之间的连线很重要,可以让我们对其有更系统的了解,这里算是有所体会

rehash条件

redis的hashtable是基于分桶式链表解决hash冲突的,当哈希表中数据量过大是,桶链表很长,这样查询过程会变得很慢,此时需要对哈希表扩容;相反,如果哈希表中的元素非常少,过大的一维数组会浪费内存空间,此时会触发哈希表缩容;redis中对哈希表的扩容和缩容条件都有比较明确的标准

扩容条件

  1. 当hash表中元素个数等于第一维数组的长度时,会触发扩容,扩容的新数组是原数组两倍
  2. 当遇到Redis正在做rdb快照bgsave时,redis为了减少内存页复制(COW),会尽量不做扩容
    • 但,当元素个数超过第一维数组长度的5倍时,redis会强制哈希表扩容

缩容条件

  1. 当hash表元素个数小于第一维数组10%,redis触发hash缩容,缩容不考虑bgsave

Dict数据结构的使用

  1. 面向用户的hash结构,其实现即为一个dict

  2. 面向用户的set结构,其底层实现也是dict,只不过val = NULL

  3. 面向用户的zset结构,其底层实现是dictzskiplist的结合,其中dict存储value和score值的映射关系

    struct zset {
    	dict *dict;      // all values. value => socre
    	zskiplist *zsl; // 跳表
    }
    
  4. 整个Redis数据库的所有key/value也是一个全局字典,还有一个记录所有带过期时间的key与过期时间的映射也是使用dict

    struct RedisDb {
    	dict* dict;    // all keys. key => value
    	dict* expires; // all expired keys. key => timestamp
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值