凡是总须研究,才会明白。------鲁迅
redis数据库底层通过多个数据结构,最终实现了其对外服务的强大功能,其中dict是基础而核心的功能。dict通过哈希表模式,并结合链表实现了键值存储功能。我们在redis命令行界面,很清晰可以看到自己存储的键值数据:
127.0.0.1:6379> set book learn redis
(error) ERR syntax error
127.0.0.1:6379> set book "learn redis"
OK
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379>
127.0.0.1:6379> get book
"learn redis"
可以看到,book就是我们设置的键,“learn redis”就是我们设置的值。
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64;
double d;
} v;
struct dictEntry *next;
} dictEntry;
typedef struct dictht {
dictEntry **table;
PORT_ULONG size;
PORT_ULONG sizemask;
PORT_ULONG used;
} dictht;
typedef struct dict {
dictType *type;
void *privdata;
dictht ht[2];
PORT_LONG rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
一、结构体成员解析:
我们观看本文开始图片及结合redis dict源码:
dict结构体:
type是一个指向dictType结构体的指针,该结构体中声明了多个函数。(该结构体不再详述)
privdata则是指定一个私有数据,它通过和type结合,可以为多类型的数据服务。
ht[2],这是一个类型为dictht的数组,通常情况下ht[1]为空,不使用,所有数据存放在ht[0]中。
rehashidx,代表是否在进行重构hash表
iterators,记录多少在用的迭代器。
dictht结构体:
table是一个指向dictEntry结构体指针的指针,也可以理解为指向含有多个dictEntry结构体指针的数组的指针。
size:指明hash表大小。
sizemask:代表哈希表大小掩码,总是等于size-1;
used:代表已有节点数量。
dictEntry结构体:
key:用来存放键值
v:用了一个union结构,一来可以节省空间,二来其中多个变量类型使得可以存储多种类型。
next:用来指向下一个dictEntry节点的指针(当通过hash算法算出两个键的索引值一样时候,将存放在链表中)。
二、重点解析
1,dict结构体中rehashidx值不等于-1时,代表hash表在进行重构(键值哈希值冲突过多)。rehash过程中,ht[1]将被启用:
如果是进行扩展,ht[1]的大小为第一个大于等于ht[0].used*2的2的n次方幂。
如果是进行收缩,ht[1]的大小为第一个大于等于ht[0].used的2的n次方幂。
2,dict扩展的因子由参数dict_force_resize_ratio决定,它的值为5,代表当used的值/size的值大于5时,将进行扩展操作。
3,hash表进行rehash过程中,使用的渐进式的流程,慢慢将ht[0]中的数据通过重新计算hash值写入到ht[0]中,该过程中,新加的数据将只在ht[1]中进行,读取将在ht[0]和ht[1]中进行。等待rehash过程完成,ht[0]将置空,ht[1]将变为ht[0]。
4,redis哈算算法使用了MurmurHash2(by Austin Appleby),算法如下:
unsigned int dictGenHashFunction(const void *key, int len) {
/* 'm' and 'r' are mixing constants generated offline.
They're not really 'magic', they just happen to work well. */
uint32_t seed = dict_hash_function_seed;
const uint32_t m = 0x5bd1e995;
const int r = 24;
/* Initialize the hash to a 'random' value */
uint32_t h = seed ^ len;
/* Mix 4 bytes at a time into the hash */
const unsigned char *data = (const unsigned char *)key;
while(len >= 4) {
uint32_t k = *(uint32_t*)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
/* Handle the last few bytes of the input array */
switch(len) {
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0]; h *= m;
};
/* Do a few final mixes of the hash to ensure the last few
* bytes are well-incorporated. */
h ^= h >> 13;
h *= m;
h ^= h >> 15;
return (unsigned int)h;
}
喜欢的朋友可以扫描以下二维码进行关注,公众号将经常更新文章: