Redis学习之路(2)---- 字典与渐进式rehash

一、了解字典

字典,是一种用于保存键值对(key-value)的抽象数据结构,可以简单的理解为hashmap。

字典在Redis中的应用十分广泛,比如Redis的数据库就是使用字典来作为底层实现的,对数据库的增删改查也是建立在对字典的操作之上的。

redis> SET a “b”

OK

例如执行上面的命令,在数据库中创建一个键为"a",值为"b"的键值对时,这个键值对就是保存在代表数据库的字典里面的。

二、字典的实现

Redis的字典是用哈希表作为底层实现的,一个哈希表中可以有多个哈希表节点,每个哈希表节点保存字典中的一个键值对。

1、哈希表结构

typedef struct dictht{
​	dictEntry **table;//哈希表数组
​	unsigned long size;//哈希表大小
​	unsigned long sizemask;//哈希表大小掩码,用于计算索引值,总是等于size-1
​	unsigned long used;//该哈希表已有的节点数量
} dictht;

在这里插入图片描述

2、哈希表节点

typedef struct dictEntry{
​	void *key;//键
​	union{//值
​		void *val;
​		uint64_t u64;
​		int64_t s64;
​	} v;
​	struct dictEntry *next;//指向下个哈希表节点,形成链表
} dictEntry;

next属性是指向另一个哈希表节点的指针,作用是将多个哈希值相同的键值对连接在一起,形成链表(如下图)。

3、字典

typedef struct dict{
​	dictType *type;//类型特定函数
​	void *privdata;//私有数据
​	dictht ht[2];//哈希表
​	int rehashidx;//rehash索引,当rehash不在进行时,值为-1
} dict;
  • type 和privdata属性是针对不同类型的键值对,为创建多态字典而设置的。
  • ht属性是一个包含两个dicyht哈希表的数组,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用。
  • rehashidx属性记录了rehash目前的进度,如果目前没有进行rehash,那么其值为-1。
    在这里插入图片描述

4、哈希算法

当要将一个新的键值对添加到字典中时,需要根据键计算出哈希值和索引值,然后根据索引值将包含新键值对的哈希表节点放到哈希表数组的指定索引上。

哈希值:

hash = dict->type->hashFunction(key) //使用字典设置的hashFunction函数计算key的哈希值

索引值:

index = hash & dict->ht[x].sizemask //使用哈希值和sizemask计算索引值,ht[x]根据情况不同可以是ht[0]或ht[1]

5、解决键冲突

类似hashmap使用链地址法解决冲突。

新节点添加到链表的表头位置(头插法)

6、rehash

随着操作的不断执行,键值对会逐渐增多或减少,为了将负载因子维持在合理范围,需要对哈希表大小进行相应的扩展或收缩。

扩展或收缩通过rehash操作来完成,步骤如下:

(1)为ht[1]哈希表分配空间,空间大小取决于ht[0].used属性的值

  • 若是扩展操作,那么ht[1]的大小为第一个>=ht[0].used*2的2^n (如果ht[0].used=4,那么4乘2=8,而8恰好是第一个>=4的2的n次方,那么ht[1]的大小就应该分配为8)
  • 若是收缩操作,那么ht[1]的大小为第一个>=ht[0].used的2^n

(2)将保存在ht[0]中的所有键值对rehash到ht[1]中,rehash指重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。

(3)当ht[0]的所有键值对都迁移到了ht[1]之后(ht[0]变为空表),释放ht[0],将ht[1]设置为ht[0],新建空白的哈希表ht[1],以备下次rehash使用

扩展与收缩的条件

(1)当以下条件满足任意一个时,程序就会对哈希表进行扩展操作

  • 服务器目前没有执行bgsave或bgrewriteaof命令,并且哈希表的负载因子>=1
  • 服务器目前正在执行bgsave或bgrewriteaof命令,并且哈希表的负载因子>=5

(2)当负载因子的值小于0.1时,程序就会对哈希表进行收缩操作

负载因子的计算
load_factor = ht[0].used / ht[0].size

7、渐进式rehash

扩展或收缩哈希表需要将 ht[0] 里面的所有键值对 rehash 到 ht[1] 里面, 但是, 这个 rehash 动作并不是一次性、集中式地完成的, 而是分多次、渐进式地完成的。

原因在于如果键值对的数量太多的话,要一次性将所有键值对全部rehash到ht[1]中的话,庞大的计算量可能会导致服务器再一段时间内停止服务。

渐进式rehash的步骤

  • 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表
  • 维持索引计数器变量rehashidx,并将它的值设置为0,表示rehash开始
  • 每次对字典执行增删改查时,将ht[0]的rehashidx索引上的所有键值对rehash到ht[1],将rehashidx值+1。
  • 当ht[0]的所有键值对全部被rehash到ht[1]中,程序将rehashidx的值设置为-1,表示rehash操作完成

渐进式rehash的好处在于它采取分而治之的方式,将rehash键值对的计算均摊到每个字典的增删改查操作上,避免了集中式rehash的庞大计算量。

一次完整的渐进式rehash过程如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在此过程中,字典的增删改查操作会同时在ht[0],ht[1]两个表上进行,比如:

  • 查找一个键,会先在ht[0]中查找,没找到再到ht[1]中找。
  • 新添加到字典的键值对一律会被保存到ht[1]中而不是ht[0].

三、总结

(1)redis字典使用哈希表作为底层实现,哈希表使用链地址法解决键冲突

(2)每个字典有两个哈希表,ht[0]在平常使用,ht[1]在rehash时使用

(3)rehash并不是一次性完成的,而是渐进式的进行。

四、参考

Redis设计与实现 黄健宏 机械工业出版社

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis渐进式rehash是一种在进行哈希表扩容或收缩时的一种渐进式处理方式。它避免了Redis阻塞的问题,但也带来了一些其他的问题。在进行渐进式rehash时,Redis需要分配一个新的哈希表,并为该哈希表分配新的大小的内存。这导致了Redis内存使用量瞬间增加,并且在Redis满容状态下,rehash操作会导致大量的Key被驱逐。 为了解决这个问题,Redis实现了辅助服务器来在读写操作时进行渐进式rehash操作。但是如果服务器比较空闲,Redis数据库将会长时间同时使用两个哈希表。为了处理这种情况,Redis周期函数在发现字典正在进行渐进式rehash操作时,会花费1毫秒的时间来帮助进行渐进式rehash操作。 总之,Redis渐进式rehash是一种有效避免阻塞的哈希表扩容或收缩方式,但在操作过程中可能会导致内存使用量增加和大量Key被驱逐的问题。为了处理这些问题,Redis使用辅助服务器和周期函数来优化渐进式rehash操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Redis详解(六)渐进式rehash机制](https://blog.csdn.net/fedorafrog/article/details/104633237)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值