Redis 底层数据结构之字典

文章参考 《Redis 设计与实现》黄建宏

字典

在字典中,每个键都是独一无二的,程序可以在字典中根据键查找与之相关联的值,或者通过键来更新和删除值。

字典在 Redis 中的应用相当广泛,比如 Redis 的数据库就是使用字典来作为底层实现的,例如:

redis> SET msg "hello world"
OK

在数据库中创建一个键为 “msg” 值为 “hello world” 的键值对, 这个键值对就保存在数据库的字典里面。

哈希键的底层实现之一也是字典

哈希表

Redis 字典所使用的是哈希表

typedef struct dictht {
  // 哈希表数组
  dictEntry **table;
  // 哈希表大小
  unsigned long size;
  // 哈希表大小掩码,用于计算索引值
  // 总是等于 size-1
  unsigned long sizemask;
  // 该哈希表已有节点的数量
  unsigned long used;
} dictht;
  • table 属性是一个数组, 数组中的每个元素都是指向 dictEntry 结构的指针, 每个 dictEntry 结构保存着一个键值对
  • size 属性记录了 table 数组的大小
  • used 属性记录了哈希表目前已有节点的数量
  • sizemask 属性的值总是等于 size - 1, 这个属性和哈希值一起决定一个键该被放到 table 数组的哪个索引上面

image-20210705151431904

哈希表节点 dictEntry
typedef struct dictEntry {
  // 键
  void *key;
  
  // 值
  union {
    void *val;
    uint64_tu64;
    int64_ts64;
  } v;
  
  // 指向下个 dictEntry, 形成链表
  struct dictEntry *next;
} dictEntry;
  • key 属性保存键值对的键, v 属性保存键值对的值,其中键值对的值可以是一个指针,或者是一个 uint64_t 整数 或 int64_t 整数
  • next 指向另一个哈希表节点的指针,这个指针将多个哈希值相同的键值对连接在一起, 以此来解决哈希冲突(拉链法)

image-20210705151932571

字典
typedef struct dict {
  // 类型特定函数
  dictType *type;
  // 私有数据
  void *privdata;
  // 哈希表
  dictht ht[2];
  // rehash 索引
  // 当 rehash 不在进行时,值为-1
  in trehashidx;
} dict;

type 属性和 privdata 属性是针对不同类型的键值对,为创建多态字典而设置的。

ht 属性时一个包含两个项的数组,数组中的每个项都是一个 dictht 哈希表,一般情况下, 字典只使用 ht[0] 的哈希表,ht[1] 哈希表只会在对 ht[0] 进行 rehash 时使用。

rehashidx 属性记录了 rehash 目前的进度,如果目前没有在进行的 rehash, 那么它的值为 -1。

image-20210705152553052

rehash

随着操作的不断执行,哈希表保存的键值对会逐渐增多或减少,为了让哈希表的负载因子 load factor 维持在一个合理的范围内,需要对哈希表进行合理的扩展或者收缩,即通过 rehash (重新散列) 操作来完成

  1. 为字典 ht[1] 哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及 ht[0] 当前包含的键值对数量( ht[0].used 的值 )
    1. 如果执行的是扩展操作,那么 ht[1] 的大小为第一个大于等于 ht[0].used * 2 的 2^n, 举例, 如果当前 used 为 5, 那么 ht[1] 的大小是16 即 2^4
    2. 如果执行的是收缩操作,那么 ht[1] 的大小为第一个大于等于 ht[0].used 的 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

  1. 服务器目前没有在执行 BGSAVE 或者 BGREWRITEAOF 命令,并且哈希表的负载因子 >= 1

  2. 服务器目前正在执行 BGSAVE 或 BGREWRITEAOF 命令,并且哈希表负载因子 >= 5

    负载因子 = used / size

  3. 当负载因子 < 0.1 时,程序自动开始对哈希表执行收缩操作

渐进式 rehash:

rehash动作并不是一次性完成的,防止庞大的计算可能导致服务器在一定时间内停止服务。所以在渐进式 rehash 的过程中,字典会同时使用 ht[0] 和 ht[1] 两个哈比表,其中查找会先查找 ht[0], 再查找 ht[1], 添加操作则只对 ht[1] 添加,这一措施保证了 ht[0] 包含的键值对数量会只减不增,并随着 rehash 结束变成空表。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值