redis哈希键底层-字典

一. 结构

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

 

二. 哈希算法

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

  redis使用的算法:MurmurHash2

 

三. 解决键冲突

  键冲突:当有两个或者两个以上数量的键被分配到了哈希表数组的同一个索引上面。

  解决冲突的方式:链地址法,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来。(因为组成的链表没有指向链表表尾的指针,所以为了速度考虑,总是将新节点添加到表头的位置,排在其他已有节点的前面)

 

四. rehash

  随着操作的不断执行,哈希表保存的键值对会逐渐的增多或者减少,为了让哈希表的负载因子维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序要对哈希表的大小进行相应的扩展或者收缩。

  步骤:

    1)为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(即ht[0].used属性的值)

        a. 如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht[0].used*2 的2^n

        b. 如果执行的收缩操作,那么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.

    2)服务器目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于等于5.

  负载因子可以通过公式:负载因子 = 哈希表已保存节点数量 / 哈希表大小

  

  当哈希表的负载因子小于0.1时,程序自动开始对哈希表执行收缩操作

 

六. 渐进式rehash

  rehash动作不是一次性,集中式的完成的,而是分成多次,渐进式地完成。

  原因:键值对过多,庞大的计算量可能会导致服务器停止服务

  步骤:

    1) 为ht[1]分配空间,让字典同时持有ht[0]和ht[1]两个哈希表

    2)在字典中维持一个索引计数器变量rehashidx,并将它的值设为0,表示rehash工作正式开始。

    3)在rehash期间,每次对字典执行添加,删除,查找或者更新操作时,程序出了执行特定的操作以外,还会顺带将ht[0]哈希表在rehashidx索引上的所有键值对rehash到ht[1],这时程序将rehashidx属性的值加一。

    4)随着字典操作的不断执行,最终在某个时间点上,ht[0]的所有键值对都会被rehash至ht[1],这时程序将rehashidx属性的值设为-1,表示rehash操作已完成。

  渐进式rehash的好处在于它采取的是分而治之的方式,将rehash键值对所需的计算工作均摊到字典的每个添加,删除,查找和更新操作

 

七. 渐进式rehash执行期间的哈希表操作

  在渐进式rehash进行期间,字典的删除,查找,更新等操作会在两个哈希表上进行。例如:要在字典里面查找一个键的话,程序会先在ht[0]里面进行查找,如果没找到,机会继续在ht[1]里面进行查找。另外,新添加到字典的键值对一律会被保存到ht[1]里面,而ht[0]则不再进行任何添加操作,保证ht[0]只减不增,最终变为空表

 

posted @ 2020-06-29 22:39  zjmbk  Views(304)  Comments(0)    收藏  举报