redis中的哈希表怎么实现rehash

1. 哈希表的结构设计

redis的哈希表结构如下:

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

 

可以看到,哈希表是一个数组,数组的每个元素是一个指向哈希表节点的指针。

 

2. 哈希冲突

哈希表实际上是一个数组,数组里每一个元素就是一个哈希桶

当一个键值对的键经过hash函数计算后得到哈希值,再将哈希值进行取模运算,得到的结果就是该key对应的数组元素位置,也就是第几个哈希桶

什么时候会产生哈希冲突呢?

举个例子,有一个可以存放8个哈希桶的哈希表。key1经过哈希函数计算后,再将哈希值%8进行取模运算,结果值为1,那么就对应哈希桶1,类似地,key9和jey10分别对应哈希桶1和6

 

当key1和key9分配到了相同的哈希桶中,就发生了哈希冲突

因此,当有两个以上数量的key被分配到了一个哈希桶中,此时称这些key发生了冲突

 

3. 链式哈希

redis采用链式哈希来解决哈希冲突

 

链式哈希局限性也很明显,随着链表⻓度的增加,在查询这⼀位置上的数据的耗时就会增加,毕竟链表的查询的时间复杂度是 O(n)
 

4. rehash

在哈希表实际使用时,redis定义了一个dict结构体,这个结构体定义了两个哈希表

typedef struct dict {
  ...
  // 两个哈希表,交替使用,用于rehash
  dictht ht[2];
  ...
} dict;

 

在正常服务请求阶段,插入的数据都会写到ht1中,此时ht2h还没有被分配空间。

随着数据的逐步增多,触发了rehash,这个过程分为三步:

  • 给ht2分配空间,一般会比ht1大2倍
  • 将ht1的数据迁移到ht2中
  • 迁移完成后,ht1将会被释放,并把ht2设置成ht1,然后ht2为下次rehash作准备

这个过程看起来跟简单,但是数据迁移存在很大的问题,如果ht1的数量非常大,那么在迁移至ht2的时候,会因为大量数据的拷贝造成redis阻塞,无法服务其他请求。

 

5. 渐进式rehash

为了避免 rehash 在数据迁移过程中,因拷⻉数据的耗时,影响 redis 性能的情况,所以 redis 采⽤了渐进式 rehash,也就是将数据的迁移的⼯作不再是⼀次性迁移完成,⽽是分多次迁移。
  • 给ht2分配空间
  • 在 rehash 进⾏期间,每次哈希表元素进⾏新增、删除、查找或者更新操作时,redis 除了会执⾏对应的操作之外,还会顺序将ht1中索引位置上的所有 key-value 迁移到ht2上
  • 随着处理客户端发起的哈希表操作请求数量越多,最终在某个时间,会把ht1的所有key-value 迁移到ht2,从⽽完成 rehash 操作

 

6. rehash触发条件

rehash 的触发条件跟负载因⼦(load factor)有关系
 
负载因⼦可以通过下⾯这个公式计算
负载因子 = 哈希表已保存的节点数量/哈希表大小

 

 
触发 rehash 操作的条件,主要有两个
  • 当负载因⼦⼤于等于 1 ,并且 redis 没有在执⾏ bgsave 命令或者 bgrewiteaof 命令,也就是没有
    执⾏ RDB 快照或没有进⾏ AOF 重写的时候,就会进⾏ rehash 操作
  • 当负载因⼦⼤于等于 5 时,此时说明哈希冲突⾮常严重了,不管有没有有在执⾏ RDB 快照或 AOF
    重写,都会强制进⾏ rehash 操作

 

posted @ 2022-03-19 15:27  aganippe  阅读(651)  评论(0编辑  收藏  举报