返回

Redis 的哈希对象(z_hash)及其源码分析

后端

Redis 的哈希对象(hash 对象)采用两种实现方式,分别对应连续内存和非连续内存。连续内存方式简单高效,但存储容量有限;非连续内存方式存储容量不受限,但效率相对较低。

针对不同的应用场景,Redis 选择了不同的实现方式。在分析源码之前,我们先了解一下这两种实现方式的优缺点。

连续内存

优点:

  • 内存占用连续,查询效率高。
  • 适用于小规模数据集。

缺点:

  • 存储容量有限,容易发生哈希冲突。
  • 哈希扩容时需要重新分配内存,开销较大。

非连续内存

优点:

  • 存储容量不受限。
  • 避免哈希冲突,查询效率稳定。

缺点:

  • 内存占用不连续,查询效率较低。
  • 哈希扩容时只需调整指针,开销较小。

Redis 在 4.0 版本之前,hash 对象只采用连续内存的实现方式,称为 ziplist 。从 4.0 版本开始,Redis 引入了非连续内存的实现方式,称为 hash table ,也称为 z_hash

z_hash 的数据结构

z_hash 由两个哈希表组成:

  • dict :存储键值对。
  • zskiplist :存储键值对的指针。

dict 是一个哈希表,键为字符串,值为 zskiplistEntry 。zskiplistEntry 是一个双向链表节点,存储键值对和指向 zskiplist 的指针。

zskiplist 是一个跳表,键为字符串,值为 zskiplistNode 。zskiplistNode 是一个双向链表节点,存储键值对。

z_hash 的查询过程

查询 z_hash 中的键值对时,Redis 会先在 dict 中查找,如果找到,则直接返回。如果 dict 中没有找到,则在 zskiplist 中查找。

z_hash 的扩容过程

当 z_hash 中的键值对数量超过一定阈值时,Redis 会触发扩容。扩容过程包括两个步骤:

  1. 重新分配内存,创建一个新的 z_hash。
  2. 将原有的键值对迁移到新的 z_hash 中。

由于 zskiplist 的查询效率较低,所以 Redis 会在 z_hash 中设置一个 max_step 值。当 zskiplist 的层数超过 max_step 时,Redis 会将 zskiplist 转换为 ziplist。

z_hash 的源码分析

z_hash 的源码位于 redis/src/hash.c 文件中。主要函数如下:

  • zhashCreate :创建 z_hash。
  • zhashGet :查询 z_hash 中的键值对。
  • zhashPut :向 z_hash 中插入键值对。
  • zhashDel :从 z_hash 中删除键值对。
  • zhashResize :扩容 z_hash。
  • zhashConvert :将 z_hash 转换为 ziplist。

通过对 z_hash 源码的分析,我们可以深入了解 Redis 中哈希对象的实现原理,从而更好地使用 Redis。