背景
本章主要记录一下学习redis字典的一些总结
字典简介
字典,又称为符号表(symbol table)、关联数组(associative array)、映射(map),是一种用于保存键值对(key-value)的数据结构
redis底层字典的实现
redis的字典使用hash表作为底层实现,一个hash表里面可以有多个hash表节点,而每个hash表节点就保存了字段中的一个键值对。
redis数据结构
//字典对象
typeof struct dict {
//类型特定函数
dictType *type;
//私有数据
void *privdate;
//hash表,用于reindex的时候时候,扩表以后互相传递数据
dictht ht[2];
//当reindex不进行的时候数值是-1.
int rehashidx;
}
//提供了一些函数用户redis操作用
typeof struct dictType {
// 计算hash值
unsigned int (*hashFunction)(const void *key);
//复制key
void *(*keyDup)(void *privdata, const void *key);
//复制值
void *(*valDup)(void *privdata, const void *obj);
//对比键
int (*keyCompare)(void *privdata, void *key);
//销毁值
void (*valDestructor) (void *privdata, void *obj);
} dictType;
typedef struct dictht {
//hash表数组
dictEntry **table;
// hash表大小
unsigned long size;
//hash表大小掩码,用户计算hash值,总是等于size-1
unsigned long sizemark;
//hash表已使用节点数量
unsigned long used;
}
typedef struct dictEntry {
//键
void *key;
//值
union {
//指针,指向其他数据
void *val;
//整数
uint64_t u64;
//整数
int64_t s64;
} v;
//链表中下一个节点
struct disctEntry *next;
}
字典结构图
hash算法
当有新的key添加到字典的时候,会进行如下步骤
1.hash = dict -> type -> hashFunction(key);
2.index = hash & dict -> ht[x].sizemark;//根据情况的不同ht[x]是ht[0]或者ht[1],当reindex进行的时候就可能是ht[1]
得到的index就是dictht对象中的table的下标
hash冲突
其实跟hashmap里面的解决方案是一样的,这里不重复了。哈哈
rehash
出现场景
hash负载因子计算公式 = hash表中的key个数 / hash表大小
随着操作不断执行,hash表会增加或者减少,为了让hash表的负载因子(key的size/hashtable的size)在一个合理范围内,hash表会适当进行扩展或者收缩。
1.当服务器没有执行BGSAVE、BGREWRITEAOP,并且hash负载因子大于1的时候
2.当服务器正在执行BGSAVE、BGREWRITEAOP,并且hash负载因大于5的时候
因为当BGSAVE、BGREWRITEAOP进行时,redis服务器性能消耗比较大,不建议进行rehash操作,所以通过增加负载因子来提高rehash的门槛。
rehash步骤
1.为ht[1]分配空间为ht[0].used * 2的空间 2.将ht[0]上的值rehash到ht[1], 其中包含了重新计算hash值,然后放到ht[1]的指定位置 3.当所有的key迁移完了,将ht[1]设置成ht[0], 在ht[1]新建一个空的hash表,为下一次rehash做准备
在rehash的时候,数据如何保证一致性?(中途有数据变更怎么办)
由于hash表中的key可能非常多,几百万、甚至上千万,所以reindexhash操作是异步进行的。那么如何保证2个ht的数据一致性呢。dict结构中有一个字段是rehashidx,当ht[0]中某个key变更的时候,会将key对应的下标记录到rehashidx,那么rehash的时候会同事将hash表中下标为rehashidx的数据复制到ht[1]中。具体的逻辑图见下图
重点回顾
- redis字典已hash表作为底层实现,每个字典中有2个hash表,平时只只用一个,另外一个在reindex的时候使用
- hash表用链地址法解决hash冲突,也就是hash表中加一个单项链表
- redis中进行rehash的时候,并不是一次性完成的,因为key可能非常多,其中需要处理数据一致性的问题。