redis中的rehash?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis中的rehash?相关的知识,希望对你有一定的参考价值。
参考技术A Redis中所有数据都有key-value,这是通过哈希表实现的,redis的字典数据结构保存了两张哈希表,采取了渐进哈希的方法。
字典与渐进式rehash
总结:
在此过程中,字典的增删改查操作会同时在ht[0],ht[1]两个表上进行,比如:
Redis在持久化时,如果是采用BGSAVE命令或者BGREWRITEAOF的方式,那Redis会fork出一个子进程来读取数据,从而写到磁盘中。
总体来看,Redis还是读操作比较多。如果子进程存在期间,发生了大量的写操作,那可能就会出现很多的分页错误(页异常中断page-fault),这样就得耗费不少性能在复制上。
而在rehash阶段上,写操作是无法避免的。所以Redis在fork出子进程之后, 将负载因子阈值提高,尽量减少写操作, 避免不必要的内存写入操作,最大限度地节约内存。
Redis中渐进式rehash
Redis一共支持5种数据结构,hash是其中的一种,在hash扩容的时候采用的是渐进式rehash的方式。要想深入理解渐进式rehash,首先要了解以下Redis中hash的数据结构。
哈希表节点
typedef struct dictEntry {
void *key; // 键
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v; // 值
struct dictEntry *next; // 下一个节点
} dictEntry;
哈希表
/* This is our hash table structure. Every dictionary has two of this as we
* implement incremental rehashing, for the old to the new table. */
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 掩码,计算索引值,size-1
unsigned long used; // 哈希表已有节点的数量
} dictht;
字典
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 哈希表
// rehash索引
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;
特定函数
typedef struct dictType {
// 计算哈希值的函数
uint64_t (*hashFunction)(const void *key);
// 复制键的函数
void *(*keyDup)(void *privdata, const void *key);
// 复制值的函数
void *(*valDup)(void *privdata, const void *obj);
// 对比键的函数
int (*keyCompare)(void *privdata, const void *key1, const void *key2);
// 销毁键的函数
void (*keyDestructor)(void *privdata, void *key);
// 销毁值的函数
void (*valDestructor)(void *privdata, void *obj);
} dictType;
字典中包含一个数据结构dictht
的ht
数组,一般情况下字典只是用ht[0]
用来存储数据,ht[1]
在rehash时使用。
哈希算法原理
当向字典中添加一个元素时(假设此时 rehashidx = -1
,也就是没有进行rehash),首先通过dict->type->hashFunction
计算该元素的hash
值,然后通过hash & dict->ht[x].sizemask
计算哈希地址index
。如果该元素对应的下标没有数据,则直接添加,否则采用链地址法添加到hash对应index
元素的链表尾部。
rehash原理
随着操作的不断执行,哈希表中的元素会逐渐增加或者减少,为了让哈希表的负载因子维持在一个合理的范围内,程序需要对哈希表的大小进行相应的扩容和收缩。步骤如下:
-
为
ht[1]
哈希表分配空间。如果是扩容操作,ht[1]
的大小为第一个大于等于ht[0].used*2
的2
的n
次方幂,如果是收缩操作,ht[1]
的大小为第一个大于等于ht[0].used
的2
的n
次方幂 -
将保存在
ht[0]
中的所有键值对rehash到ht[1]
:rehash指的是重新计算键的哈希值和索引值,然后将键值对放到ht[1]
对应位置上 -
当
ht[0]
包含的所有键值对都迁移到ht[1]
之后,释放ht[0]
,将ht[1]
设置为ht[0]
,并在ht[1]
新创建一个空白哈希表,为下一次rehash做准备
渐进式rehash原理
在扩容和收缩的时候,如果哈希字典中有很多元素,一次性将这些键全部rehash到ht[1]
的话,可能会导致服务器在一段时间内停止服务。所以,采用渐进式rehash的方式,详细步骤如下:
-
为
ht[1]
分配空间,让字典同时持有ht[0]
和ht[1]
两个哈希表 -
将
rehashindex
的值设置为0
,表示rehash工作正式开始 -
在rehash期间,每次对字典执行增删改查操作是,程序除了执行指定的操作以外,还会顺带将
ht[0]
哈希表在rehashindex
索引上的所有键值对rehash到ht[1]
,当rehash工作完成以后,rehashindex
的值+1
-
随着字典操作的不断执行,最终会在某一时间段上
ht[0]
的所有键值对都会被rehash到ht[1]
,这时将rehashindex
的值设置为-1
,表示rehash操作结束
渐进式rehash采用的是一种分而治之的方式,将rehash的操作分摊在每一个的访问中,避免集中式rehash而带来的庞大计算量。
需要注意的是在渐进式rehash的过程,如果有增删改查操作时,如果index
大于rehashindex
,访问ht[0]
,否则访问ht[1]
以上是关于redis中的rehash?的主要内容,如果未能解决你的问题,请参考以下文章