Redis的字典(dict)rehash过程源代码解析
2016-01-15 20:53
1031 查看
Redis的内存存储结构是个大的字典存储,也就是我们通常说的哈希表。Redis小到能够存储几万记录的CACHE,大到能够存储几千万甚至上亿的记录(看内存而定),这充分说明Redis作为缓冲的强大。Redis的核心数据结构就是字典(dict),dict在数据量不断增大的过程中。会遇到HASH(key)碰撞的问题,假设DICT不够大,碰撞的概率增大,这样单个hash 桶存储的元素会越来愈多,查询效率就会变慢。假设数据量从几千万变成几万,不断减小的过程。DICT内存却会造成不必要的浪费。Redis的dict在设计的过程中充分考虑了dict自己主动扩大和收缩,实现了一个称之为rehash的过程。
使dict出发rehash的条件有两个:
1)总的元素个数 除 DICT桶的个数得到每一个桶平均存储的元素个数(pre_num),假设 pre_num > dict_force_resize_ratio,就会触发dict 扩大操作。dict_force_resize_ratio = 5。
2)在总元素 * 10 < 桶的个数,也就是,填充率必须<10%,
DICT便会进行收缩。让total / bk_num 接近 1:1。
dict rehash扩大流程:
源码函数调用和解析:
dictAddRaw->_dictKeyIndex->_dictExpandIfNeeded->dictExpand,这个函数调用关系是须要扩大dict的调用关系,
_dictKeyIndex函数代码:
.sizemask;
// 在节点链表里查找给定 key
// 由于链表的元素数量通常为 1 或者是一个非常小的比率
// 所以能够将这个操作看作 O(1) 来处理
he = d->ht.table[idx];
while(he) {
// key 已经存在
if (dictCompareKeys(d, key, he->key))
return -1;
he = he->next;
}
// 第一次进行执行到这里时,说明已经查找完 d->ht[0] 了
// 这时假设哈希表不在 rehash 其中。就没有必要查找 d->ht[1]
if (!dictIsRehashing(d)) break;
}
return idx;
} _dictExpandIfNeeded函数代码解析:
使dict出发rehash的条件有两个:
1)总的元素个数 除 DICT桶的个数得到每一个桶平均存储的元素个数(pre_num),假设 pre_num > dict_force_resize_ratio,就会触发dict 扩大操作。dict_force_resize_ratio = 5。
2)在总元素 * 10 < 桶的个数,也就是,填充率必须<10%,
DICT便会进行收缩。让total / bk_num 接近 1:1。
dict rehash扩大流程:
源码函数调用和解析:
dictAddRaw->_dictKeyIndex->_dictExpandIfNeeded->dictExpand,这个函数调用关系是须要扩大dict的调用关系,
_dictKeyIndex函数代码:
static int _dictKeyIndex(dict *d, const void *key) { unsigned int h, idx, table; dictEntry *he; // 假设有须要。对字典进行扩展 if (_dictExpandIfNeeded(d) == DICT_ERR) return -1; // 计算 key 的哈希值 h = dictHashKey(d, key); // 在两个哈希表中进行查找给定 key for (table = 0; table <= 1; table++) { // 依据哈希值和哈希表的 sizemask // 计算出 key 可能出如今 table 数组中的哪个索引 idx = h & d->ht