Redis的设计与实现——字典
绝大多数语言中的字典底层实现基本上都是哈希表。哈希表中用 “负载因子” 来衡量哈希表的 空/满 程度。为了让负载因子在一定的合理范围之内,提高查询的性能,一般的做法是让哈希表扩容,然后rehash一把。
but,扩容也不一定就能解决负载因子过大的问题。Redis作为一款成熟的非关系型数据库,肯定有他的独到解决办法!!!能够保证rehash是有效的。
讲解rehash之前看下redis里面的一些数据结构:字典的底层有哈希表实现,ht存放的是哈希表。
Redis对字典的rehash的步骤如下:
①为字典的ht[1]分配空间,空间的大小取决于要执行的操作(扩展还是收缩)以及ht[0]的used属性。
扩展:ht[1]的大小为 第一个大于等于 ht[0].used*2 的 2n。
收缩:ht[1]的大小为 第一个大于等于 ht[0].used 的 2n。
②将ht[0]里面的键值对rehash到ht[1]。
③都迁移到ht[1]之后,释放ht[0],将ht[1]设置为ht[0],然后在ht[1]新建空白哈希表。为下一次rehash做准备。
写道这里,我还是没有搞清楚这个redis的哈希设计高效在哪里?直到我看到了渐进式hash!!!
渐进式hash
何为渐进式hash?在上面提到的rehash中,不是一次性将ht[0]的数据送入ht[1],如果数据量过大,将会严重影响redis数据库的性能,所以采用了一种方法渐进式。
渐进式具体操作呢?
①给ht[1]分配空间,让字典同时持有两个哈希表ht[0]和ht[1]。
②利用一个巧妙地数据设计,rehashidx,初值设为-1,表示不需要rehash;一旦需要rehash的时候,设置为0,表示rehash工作正式开始。
③在rehash进行期间,除了执行指定的操作(增删改查)之外,还会附加做一些工作,就是将ht[0]上的rehashidx对应的所有键值对rehash到ht[1]上,移完之后,rehashidx加1.
④随着字典操作(增删改查)的不断执行,最终会在某个时间点上,ht[0]的所有键值对都会被rehash到ht[1]上,这时程序将rehashidx设置为-1,表示rehash操作已全部完成。rehash的过程中,添加操作只会在ht[1]上进行,这样能够保证ht[0]的键值对只减不增。
渐进式rehash操作的好处是,避免了集中式rehash操作,将rehash的操作分摊到每次的数据库操作上。