字典适用于保存键值对的抽象数据结构,一个键可以和一个值进行关联,在字典中,键是唯一的。Redis的数据库是用字典作为底层实现的,字典还是哈希键的底层实现之一。
1.字典的数据结构
Redis的字典使用哈希表作为底层实现,哈希表由一个或多个哈希节点构成,每个哈希节点保存了字典的一个键值对。
哈希表:
typedef struct dictht {
dictEntry **table; // 哈希表数组
unsigned long size; // 哈希表大小
unsigned long sizemask; // 掩码,用于计算索引值
unsigned long used; // 哈希表已有节点数量
} dictht;
哈希节点:
typedef struct dictEntry {
void *key; // 键
union { // 值
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next; // 下个哈希表节点
} dictEntry;
字典:持有大小为2的哈希表数组,一般情况下只用到ht[0],当执行rehash时才会用到ht[1]
typedef struct dict {
dictType *type; // 类型特定函数
void *privdata; // 私有数据
dictht ht[2]; // 哈希表
long rehashidx; // rehash索引,当rehash不在进行时,值为-1
int16_t pauserehash; // >0时表示rehash暂停
} dict;
2. 字典使用
(1)哈希算法:当要将一个新的键值放入字典时,需要根据键值对的键计算出哈希值和索引值。Redis通过MurmurHash2 算法来计算键的哈希值,然后将得到的哈希值与哈希表的sizemask进行与运算得到键的索引值,根据索引值决定包含键值对的哈希节点在哈希表的存放位置。
(2)键冲突:当两个或以上数量的键分配到哈希表的同一索引时,即产生了键冲突。Redis是通过链地址法解决键冲突的,索引值相同的哈希节点通过next指针连接构成一个单向链表。考虑到插入速度,一般将新插入的节点添加到链表的表头位置,复杂度为O(1)。
(3)rehash:为了让哈希表的负载因子维持在一个较为合理的范围内,当哈希表保存的键值对数量过多或过少时,都会对哈希表进行相应的扩展或收缩,该工作通过rehash完成。
- 首先为ht[1]分配空间,若为扩展操作,ht[1]的大小则为第一个大于ht[0].used*2的
;若为收缩操作,ht[1]大小则为第一个大于ht[0].used的
。
- 将ht[0]的所有键值对重新计算哈希值和索引值,并放到ht[1]中
- 所有键值对迁移完成后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]创建一个空的哈希表,为下一次rehash做准备。
(4)渐进式rehash:为了避免对Redis的性能造成影响,rehash是分多次、渐进式地完成的。字典中维护了一个rehashidx变量,其值为0时表示rehash工作正式开始,当rehash工作完成后会将变量的 值设置为-1。每次对字典执行增删改查操作时,执行完相应操作后会顺带将ht[0]哈希表在rehashidx索引上的所有键值对迁移至ht[1]哈希表,并将rehashidx的值+1。渐进式rehash避免了集中式rehash带来的庞大计算量。
4268

被折叠的 条评论
为什么被折叠?



