Redis数据结构-Hash
1、Redis的hash结构简介
Redis使用**一个全局Hash表来保存所有的键值对具体值的指针(*key, *value 不是数据本身)**,从而既满足应用存取Hash结构数据需求,又能提供快速查询功能。
一个哈希表,其实就是一个数组,数组的每个元素称为一个哈希桶。所以,我们常说,一个哈希表是由多个哈希桶组成的,每个哈希桶中保存了键值对数据。
2、hash冲突
Redis会对哈希表做rehash操作。rehash也就是增加现有的哈希桶数量,让逐渐增多的entry元素能在更多的桶之间分散保存,减少单个桶中的元素数量,从而减少单个桶中的冲突。那具体怎么做呢?
其实,为了使rehash操作更高效,Redis默认使用了两个全局哈希表:哈希表1和哈希表2。一开始,当你刚插入数据时,默认使用哈希表1,此时的哈希表2并没有被分配空间。随着数据逐步增多,Redis开始执行rehash,这个过程分为三步:
- 给哈希表2分配更大的空间,例如是当前哈希表1大小的两倍;
- 把哈希表1中的数据重新映射并拷贝到哈希表2中;
- 释放哈希表1的空间。
这个过程看似简单,但是第二步涉及大量的数据拷贝,如果一次性把哈希表1中的数据都迁移完,会造成Redis线程阻塞,无法服务其他请求。此时,Redis就无法快速访问数据了。
3、渐进式rehash
3.1背景(why)
Hash表在执行rehash时,由于Hash表空间扩大,原本映射到某一位置的键可能会被映射到一个新的位置上,因此,很多键就需要从原来的位置拷贝到新的位置。而在键拷贝时,由于Redis主线程无法执行其他请求,所以键拷贝会阻塞主线程,这样就会产生rehash开销
3.2描述(what)
上述第二步拷贝数据时,Redis仍然正常处理客户端请求,每处理一个请求时,从哈希表1中的第一个索引位置开始,顺带着将这个索引位置上的所有entries拷贝到哈希表2中;等处理下一个请求时,再顺带拷贝哈希表1中的下一个索引位置的entries
具体到代码,它的过程是这样的:
- 在字典中维持一个索引计数器变量 rehashidx,并将设置为 0,表示 rehash 开始。
- 在 rehash 期间,客户端每次对字典进行 CRUD 操作时,会将 ht [0] 中 rehashidx 索引上的值 rehash 到 ht [1],操作完成后 rehashidx+1。
- 字典操作不断执行,最终在某个时间点,所有的键值对完成 rehash,这时将 rehashidx 设置为 - 1,表示 rehash 完成
3.3 渐进式rehash触发条件(when)
- 服务器目前没有在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于或等于1。
- 服务端目前正在执行BGSAVE命令或者BGREWRITEAOF命令,并且哈希表的负载因子大于或等于5。
- 当哈希表的负载因子小于0.1时,redis会自动开始对哈希表进行缩容操作。
其中哈希表的负载因子可以通过公式:
# 负载因子 = 哈希表以保存节点数量/哈希表大小 load_factor = ht[0].used / ht[0].size