Redis设计与实现——数据结构与对象(二)
3、 字典
-
字典的实现方法
- 最简单的就是使用链表或数组,但是这种方式只适用于元素个数不多的情况下;
- 要兼顾高效和简单性,可以使用哈希表;
- 如果追求更为稳定的性能特征,并希望高效地实现排序操作的话,则可使用更为复杂的平衡树;
-
redis中的实现犯法,选择了第二种
/* * 字典 * * 每个字典使用两个哈希表,用于实现渐进式 rehash */ typedef struct dict { // 特定于类型的处理函数 dictType *type; // 类型处理函数的私有数据 void *privdata; // 哈希表(2 个) dictht ht[2]; // 记录 rehash 进度的标志,值为 -1 表示 rehash 未进行 int rehashidx; // 当前正在运作的安全迭代器数量 int iterators; } dict;
-
整个字典结构
-
使用 的哈希算法
- MurmurHash2 32 bit 算法:这种算法的分布率和速度都非常好
- 基于 djb 算法实现的一个大小写无关散列算法
算法使用取决于具体应用所处理的数据
- 命令表以及lua脚本缓存用的2
- 算法 1 的应用则更加广泛:数据库、集群、哈希键、阻塞操作等功能都用到了这个算法
-
具体的操作流程
-
rehash操作
为了让哈希表的负载因子维持在一个合理的范围内,在哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展和收缩
具体步骤
- 创建一个比
ht[0]->table
更大的ht[1]->table
大小至少为ht[0]->used
的两倍;; - 将
ht[0]->table
中的所有键值对迁移到ht[1]->table
; - 将原有
ht[0]
的数据清空,并将ht[1]
替换为新的ht[0]
- 创建一个比
-
渐进式rehash操作
假设这样一个场景:在一个有很多键值对的字典里, 某个用户在添加新键值对时触发了 rehash 过程, 如果这个 rehash 过程必须将所有键值对迁移完毕之后才将结果返回给用户, 这样的处理方式将是非常不友好的。
另一方面, 要求服务器必须阻塞直到 rehash 完成, 这对于 Redis 服务器本身也是不能接受的。
过程:
字典会同时使用ht[0]和ht[1]这两个哈希表,字典的操作会在两个哈希表上进行操作,新增的会直接添加到h[1]中,有一个rehashidx=0时开始rehash,进行一次会增加1;而rehashidx = -1 时结束rehash操作。