热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

深入解析Redis的数据结构与对象系统

Redis是一个高性能的开源键值存储系统,支持多种数据结构。本文将详细介绍Redis中的六种底层数据结构及其在对象系统中的应用,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。通过12张图解,帮助读者全面理解Redis的数据结构和对象系统。

Redis 是一个高性能的开源键值存储系统,支持多种数据结构。本文将详细介绍 Redis 中的六种底层数据结构及其在对象系统中的应用,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。通过12张图解,帮助读者全面理解 Redis 的数据结构和对象系统。

本文内容如下:

  • 首先介绍六种基础数据结构:动态字符串、链表、字典、跳跃表、整数集合和压缩列表。
  • 其次介绍 Redis 的对象系统中的字符串对象(String)、列表对象(List)、哈希对象(Hash)、集合对象(Set)和有序集合对象(ZSet)。
  • 最后介绍 Redis 的键空间和过期键(expire)实现。

数据结构

简单动态字符串

Redis 使用动态字符串 SDS 来表示字符串值。下图展示了一个值为 Redis 的 SDS 结构:

  • len: 表示字符串的实际长度(不包含 NULL 结束符)。
  • alloc: 表示字符串的最大容量(不包含最后一个字节)。
  • flags: 占用一个字节,其中最低3位表示 header 的类型。
  • buf: 字符数组。

SDS 结构可以减少修改字符串时的内存重分配次数,主要依赖于内存预分配和惰性空间释放机制。当 SDS 需要扩展时,Redis 会分配额外的未使用空间:

  • 如果修改后的 SDS 长度小于 1MB,预分配与 len 相同大小的未使用空间。
  • 如果修改后的 SDS 长度大于 1MB,预分配 1MB 的未使用空间。

当 SDS 缩短其保存的字符串长度时,并不会立即释放多出来的字节,而是保留以供后续使用。

链表

链表在 Redis 中应用广泛,例如列表对象的底层实现之一就是链表。此外,发布和订阅、慢查询、监视器等功能也使用了链表。Redis 的链表是双向链表,如下图所示:

链表结构的 dup、free 和 match 成员属性用于实现多态链表所需的类型特定函数:

  • dup 函数用于复制链表节点所保存的值,实现深度拷贝。
  • free 函数用于释放链表节点所保存的值。
  • match 函数用于对比链表节点所保存的值和另一个输入值是否相等。

字典

字典广泛用于实现 Redis 的各种功能,包括键空间和哈希对象。Redis 使用 MurmurHash2 算法计算键的哈希值,并使用链地址法解决键冲突。被分配到同一个索引的多个键值对会连接成一个单向链表,如下图所示:

跳跃表

Redis 使用跳跃表作为有序集合对象的底层实现之一。跳跃表以有序的方式在层次化的链表中保存元素,效率与平衡树相当,但实现更为简单直观。查找、删除、添加等操作都可以在对数期望时间内完成。如下图所示:

跳表的节点 zskiplistNode 包含元素值 ele 和分值 score,节点按 score 值有序排列。每个节点的 level 数组大小不同,level 数组中的值是指向下一个节点的指针和跨度值(span)。越高层的 level 数组值的跨度值越大,底层的 level 数组值的跨度值越小。level 数组类似于不同刻度的尺子,度量长度时先用大刻度估计范围,再用小刻度进行精确逼近。

整数集合

整数集合 intset 是集合对象的底层实现之一,当集合只包含整数值元素且元素数量不多时,Redis 使用整数集合作为集合对象的底层实现。整数集合的 encoding 表示其类型,可以是 int16_t、int32_t 或 int64_t。每个元素都是 contents 数组的一个数组项,按值的大小从小到大有序排列,数组中不包含重复项。length 属性表示整数集合包含的元素数量,如下图所示:

压缩列表

压缩列表 ziplist 是列表对象和哈希对象的底层实现之一。当满足一定条件时,列表对象和哈希对象会以压缩列表为底层实现。压缩列表是 Redis 为了节省内存而开发的顺序型数据结构,由一系列特殊编码的连续内存块组成。其属性值包括:

  • zlbytes: 记录整个压缩数组的内存字节数。
  • zltail: 记录压缩列表表尾节点距离压缩列表起始地址的字节数。
  • zllen: 包含的节点数,当属性值小于 INT16_MAX 时,该值就是节点总数,否则需要遍历整个列表才能确定总数。
  • zlend: 特殊值,用于标记压缩列表的末端。

每个节点 entry 由三部分组成:

  • previous_entry_length: 前一个节点的长度,用于计算前一个节点的起始地址。
  • encoding: 节点保存数据的类型和长度。
  • content: 节点值,可以是一个字节数组或整数。

对象系统

Redis 并没有直接使用上述六种底层数据结构实现键值数据库,而是基于这些数据结构创建了一个对象系统,包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象。每个对象都使用至少一种底层数据结构,根据不同的使用场景和内容大小选择合适的数据结构,以优化对象在不同场景下的使用效率和内存占用。

Redis 的 redisObject 结构定义如下:

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; 
    int refcount;
    void *ptr;
} robj;

其中 type 表示对象类型,包括 REDIS_STRING、REDIS_LIST、REDIS_HASH、REDIS_SET 和 REDIS_ZSET。encoding 表示对象使用的数据结构。

字符串对象

字符串对象的实现如下图所示。如果字符串对象保存的字符串长度大于 32 字节,使用 SDS 保存,编码设置为 raw。如果字符串长度小于 32 字节,使用 embstr 编码方式保存。embstr 编码通过一次内存分配创建两个结构,节省内存并提高缓存利用率,但不可修改。当 embstr 编码的字符串对象进行 append 操作时,Redis 会将其转换为 raw 编码。

列表对象

列表对象的编码可以是 ziplist 或 linkedlist。当列表对象满足以下条件时,使用 ziplist 编码:

  • 所有字符串元素的长度都小于 64 字节。
  • 元素数量小于 512 个。

否则使用 linkedlist 编码。

哈希对象

哈希对象的编码可以是 ziplist 或 dict。当哈希对象满足以下条件时,使用 ziplist 编码:

  • 所有键值对的键和值的字符串长度都小于 64 字节。
  • 键值对数量小于 512 个。

否则使用 dict 编码。

集合对象

集合对象的编码可以是 intset 或 dict。当集合对象满足以下条件时,使用 intset 编码:

  • 所有元素都是整数值。
  • 元素数量不超过 512 个。

否则使用 dict 编码。

有序集合对象

有序集合的编码可以是 ziplist 或 skiplist。当有序集合对象满足以下条件时,使用 ziplist 编码:

  • 元素数量少于 128 个。
  • 所有元素的长度都小于 64 字节。

否则使用 skiplist 编码。

数据库键空间

Redis 服务器有多个数据库,每个数据库有自己的键值空间。每个数据库使用 dict 保存所有键值对。键空间的键是字符串对象,值对象可以是字符串对象、列表对象、哈希表对象、集合对象或有序集合对象中的一种。Redis 也使用 dict 结构保存键的过期时间,键是键空间中的键值,值是过期时间。通过过期字典,Redis 可以直接判断一个键是否过期。


推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 探索电路与系统的起源与发展
    本文回顾了电路与系统的发展历程,从电的早期发现到现代电子器件的应用。文章不仅涵盖了基础理论和关键发明,还探讨了这一学科对计算机、人工智能及物联网等领域的深远影响。 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 本文探讨了如何在编程中正确处理包含空数组的 JSON 对象,提供了详细的代码示例和解决方案。 ... [详细]
  • Codeforces Round #566 (Div. 2) A~F个人题解
    Dashboard-CodeforcesRound#566(Div.2)-CodeforcesA.FillingShapes题意:给你一个的表格,你 ... [详细]
author-avatar
佩弦_秦子轩_188
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有