redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对
typedef struct dictht {
dictEntry **table; //hash table 采用开链来解决hash冲突
unsigned long size; //哈希表大小
unsigned long sizemask; //哈希表掩码,总是等于size-1
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;
字典:
typedef struct dict {
dictType *type; //操作函数
void *privdata; //数据
dictht ht[2]; //hash表
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
int iterators; /* number of iterators currently running */
} dict;
typedef struct dictType {
unsigned int (*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;
字典的ht属性是一个包含两个项的数组,数组中每个项都是一个dictht哈希表,一般情况下,字典只使用ht[0]哈希表,ht[1]哈希表只会在ht[0]哈希表进行rehash时使用。
字典可能在rehash,当字典的rehashidx值为-1的时候,字典没有rehash,当为非-1的时候rehashidx记录着哈希表rehash的bucket id
rehash:
随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让hash表的负载因子(load factor)维持在一个合理范围之内,需要rehash
1 为字典的ht[1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对的数量,如果是扩展操作ht[1]大小为第一个大于等于ht[0].used*2的2的n次幂,
2 将保存在ht[0]中的所有键值对rehash到ht[1]上面:rehash指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表指定位置上。
3 当ht[0]包含的所有键值对都迁移到了ht[1]之后,释放ht[0], 将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下次rehash做准备
rehash触发
其中负载因子:
load_factor = ht[0].used/ht[0].size
rehash触发函数调用:
dictAddRaw -> _dictExpandIfNeeded -> dictExpand
static int _dictExpandIfNeeded(dict *d)
{
//如果正在rehash,那么不扩展大小
/* Incremental rehashing already in progress. Return. */
if (dictIsRehashing(d)) return DICT_OK;
//如果为空,那么初始化为4个bucket 大小
/* If the hash table is empty expand it to the initial size. */
if (d->ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);
//dict_can_resize 全局变量设置
if (d->ht[0].used >= d->ht[0].size &&
(dict_can_resize ||
d->ht[0].used/d->ht[0].size > dict_force_resize_ratio))
{
return dictExpand(d, d->ht[0].used*2); //申请空间,并且设置d->rehashidx=0
}
return DICT_OK;
}
扩展(满足其中一条就触发)
1 服务器目前没有执行bgsave或者bgrewriteaof命令,并且字典负载因子大于等于1
2 服务器目前正在执行bgsave或者bgrewriteaof命令,并且字典负载因子大于5
收缩:
当负载因子小于0.1时,程序自动开始对哈希表进行收缩操作
rehash 渐进式
在dict每次api调用都户判断rehash,来渐进式迁移。
//d为rehash 字典,n是迁移多少个bucket
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0;
while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde;
assert(d->ht[0].size > (unsigned long)d->rehashidx);
//跳过bucket为空
while(d->ht[0].table[d->rehashidx] == NULL) {
d->rehashidx++;
if (--empty_visits == 0) return 1;
}
de = d->ht[0].table[d->rehashidx];
/*迁移*/
while(de) {
unsigned int h;
nextde = de->next;
/* Get the index in the new hash table */
h = dictHashKey(d, de->key) & d->ht[1].sizemask;
de->next = d->ht[1].table[h];
d->ht[1].table[h] = de;
d->ht[0].used--;
d->ht[1].used++;
de = nextde;
}
d->ht[0].table[d->rehashidx] = NULL;
d->rehashidx++;
}
/*迁移完ht[0]指向ht[1] */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
}
/* More to rehash... */
return 1;
}
字典API
略