1.Redis 字典 Hash算法
根据key计算hash值
hash = dict -> type -> hashFunction(key);
根据sizemask属性和哈希值,计算出索引,ht[x]可以是ht[0]或ht[1]
index = hash & dict -> ht[x].sizemask;
Redis使用MurmurHash2算法计算键的哈希值,优点在于即使输入的键是有规律的,算法仍能给出一个很好的随机分布性。
2.解决Hash冲突
当2个或多个不同对象的Hash值相同时,会被分配到哈希表的同一个索引上,此时就称之为哈希冲突。
Redis采用链地址法解决键冲突,每个哈希节点都有一个next指针,由此指针构成一个单向链表,新添加的元素在链表表头。
3.rehash
负载因子 = 哈希表节点数量/哈希表大小
load_factor = ht[0].used / ht[0].size
当哈希表的操作不断进行,需要把哈希表的负载因子保持在一个合理范围内,需要对哈希表进行扩容或缩容操作,2种操作都通过执行再散列操作来执行(rehash):
a.为字典的ht[1]分配空间,如果是扩容操作,ht[1]的空间为ht[0]的节点数量2倍;如果是缩容,ht[1]的空间为ht[0]节点数量的一半。
b.将ht[0]中的所有键值对rehash到ht[1]上面:rehash需要重新计算键的哈希值和索引值,然后放在ht[1]的指定位置上。
c.当ht[0]所有键值对都迁移到ht[1]上之后,释放ht[0],就是将ht[0]表置为空表,再将ht[1]置为ht[0],并将ht[1]新建一个大小为0的空哈希表。
4.渐进式rehash
上面讲到rehash过程中,需要将ht[0]上的键值对迁移到ht[1]上,但这个过程并不是一次性操作完成的。如果需要一次性完成,就必须对当前哈希表进行锁表操作,锁表期间会导致服务的不可用,所以rehash操作需要分多次、渐进式的完成。
a.为ht[1]分配空间,字典同时持有ht[0]和ht[1]两个哈希表
b.将rehashidx值置为0,表示rehash工作开始。
c.rehash期间,所有对字典执行的读写操作,都会在ht[0]和ht[1]两个表上操作,读操作如果在ht[0]上找到,会把结果rehash到ht[1]上,如果未找到会到ht[1]上去找,保存操作只会存在ht[1]上,当rehash工作完成之后,rehashidx++;
d.随着字典操作的不断执行,最终ht[0]上所有节点会全部rehash到ht[1]上,这时将rehashidx值置为-1,表示rehash完成。