Redis 对象内部数据结构及存储方式解析

Redis 是一种常见的 NoSQL 数据库,被广泛应用于缓存、消息队列、计数器等领域。在 Redis 中,每个键都对应一个值,值可以是五种不同的数据结构类型,即字符串、列表、集合、有序集合和哈希。在底层实现中,Redis 使用对象(object)来表示键值对中的值,而对象又由底层的数据结构组成。本文将深入探讨 Redis 对象的内部数据结构及存储方式,以便更好地理解 Redis 的工作原理。

Redis 对象的内部数据结构

Redis 对象数据结构由类型、编码和值三部分组成,其中类型和编码可分别用 redisObject.typeredisObject.encoding 进行访问。值是底层数据结构的具体实现,不同数据类型的值有不同的内部数据结构。

字符串类型

在 Redis 中,字符串类型的值最常用,也是最基本的数据类型。Redis 通过 sds(simple dynamic string)数据结构实现字符串的动态分配、扩容和内存释放。sds 在底层使用 char 数组存储字符串的实际内容,并使用 int 类型的 len 属性存储字符串的长度。sds 预留了额外的空间,以便在字符串长度变化时不用频繁地调整内存大小,这样可以提高字符串的操作效率。因此,每个 sds 对象实际上由一个 char 数组、三个 int 属性(len、free、alloc),以及一些方法组成。

列表类型

在 Redis 中,列表类型即可作为队列,也可作为栈使用。Redis 通过双指针链表和 ziplist 两种数据结构实现列表类型的值。其中,双指针链表存储较大的列表,每个节点包含前驱指针 prev,后继指针 next,以及 value 属性指向节点存储的值对象。双指针链表作为纯链表实现的数据结构,支持 O(1) 的头部和尾部插入、删除操作,但对于任意位置的插入、删除操作时需要遍历链表。相反地,ziplist 是一种压缩列表,它将多个小值对象压缩到一起,减少内存占用。ziplist 的每个节点包含一个整数类型的标识码(encoding)和一个字节数组(entry),其中 encoding 用于表示 entry 存储的值的类型和大小,entry 是一个无符号字符数组,可以存储不同类型的值(如整数、字符串)。压缩列表支持 O(1) 的头部插入、删除、尾部插入、查询操作,但不适用于任意位置的操作。

集合类型

在 Redis 中,集合类型的值为无序、不可重复的元素集合。Redis 通过哈希表和 intset 两种数据结构实现集合类型的值。哈希表通过键来查找值,是一种散列表数据结构,每个节点包含键、值和链指针等信息。哈希表需要维护每个节点的哈希桶位置(由哈希函数决定),以及不同键产生哈希冲突时的链表关系。哈希表支持 O(1) 的查找、插入、删除操作,但需消耗较多内存用于维护节点(尤其是链表节点),因此适用于键值对较多的情况。相反地,intset 是一种有序、唯一的整数元素集合。intset 存储整数元素的内存占用较小,因为它采用不同编码(int16、int32、int64)来存储整数类型的值,在存储整数数量较少或较小的情况下,内存使用效率较高。但 intset 不适用于存储字符串或其他类型的数据。

有序集合类型

在 Redis 中,有序集合类型的值和集合类型类似,也是元素集合,但它的元素具有权重(score)属性,并按权重排序。Redis 通过跳跃表和 ziplist 两种数据结构实现有序集合类型的值。其中,跳跃表是一种有序链表,每个节点记录元素的键值对和若干个指向其他节点的指针,通过多级索引实现 O(log N) 的查找、插入、删除操作。ziplist 同样被用于存储元素较少的情况,只不过每个节点存储的不是一个值,而是一个键值对,其中键借助 encodeing 字段存储元素权重,Value 部分保存对应的元素。跳跃表对于元素数量较少(少于 10)时,其检索的性能优于普通的有序列表。而且,相比于红黑树等数据结构,跳跃表的插入、删除操作时间复杂度也更简单。

哈希类型

在 Redis 中,哈希类型的值为字段和值的映射,类似于面向对象编程中的对象或字典数据结构。Redis 通过哈希表实现哈希类型的值。哈希表的每个节点存储一个字段-值对,相比于键值对,字段-值对更加灵活,可以动态地添加或删除字段而不影响其他字段的存储。哈希表的每个节点同样包含键、值和指针等属性,与普通哈希表类似。

Redis 对象的存储方式

Redis 的对象可以被持久化保存到磁盘上,以便在服务器宕机或重启时仍能恢复数据。Redis 的持久化机制包括两种方式:RDB(Redis DataBase)和 AOF(Append Only File)。

RDB 持久化

RDB 持久化即将 Redis 的数据整个存储在硬盘上。Redis 通过 fork() 函数创建子进程,由子进程负责将数据写入磁盘。整个过程中,Redis 主进程不会阻塞,可以继续接受客户端请求。RDB 持久化有两种触发方式:手动执行 SAVE 或 BGSAVE 命令,或按照配置文件中指定的条件进行自动触发。手动执行 SAVE 命令时,主进程会阻塞,直到子进程将数据写入磁盘。执行 BGSAVE 命令时,主进程不会阻塞,而是将该操作放入后台执行队列,在后台执行完毕后主进程会获得一条通知,表示数据已经保存完毕了。

AOF 持久化

AOF 持久化是将 Redis 的写操作以日志的形式保存到硬盘上。每个写操作都被追加到 AOF 文件的末尾,因此 AOF 文件总是包含一个 Redis 服务器所有的写操作。AOF 持久化机制有三种写入方式:always(默认), everysec, no。always 的写入方式会在每次执行写入命令时都将日志写入文件,保证了最强的安全性。everysec 的写入方式, 会在1秒内将所有执行的写命令缓存下来,然后批量写到文件中去,在保证高性能的同时,保证了较好的安全性。no 的写入方式,主要是为了追求高性能而设计的,这种方式下Redis只有在服务器关闭时才会将缓存中的数据写入AOF文件中。

总结

Redis 是一种常用的 NoSQL 数据库,支持多种数据类型和持久化机制,并提供了丰富的命令和 API,方便应用开发和管理。了解 Redis 对象的内部数据结构和存储方式,可以更好地理解 Redis 的工作原理,从而帮助我们更好地使用 Redis。对于遇到的问题,我们通过查看 Redis 的源码,可以更深入地理解实现细节,更好地进行调试和优化。

以下是一个 Redis 列表类型的示例代码:

------ ----- ---- --------

-- -- ----- -----
----- ------ - ---------------------

-- ----
----------------------- --------- --------- --------- ----- ------ -- -
  -- ----- -
    ---------------------- -----
    -------
  -
  ---------------------- -------

  -- ----
  ------------------------ -- --- ----- ------ -- -
    -- ----- -
      ---------------------- -----
      -------
    -
    ---------------------- -------  -- - --------- --------- -------- -
  ---
---

-- -- ----- ---
--------------

来源:JavaScript中文网 ,转载请联系管理员! 本文地址:https://www.javascriptcn.com/post/651d346095b1f8cacd4bb573


猜你喜欢

相关推荐

    暂无文章