作者推荐 |【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(链表)(一)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 作者推荐 |【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(链表)

【专栏简介】

随着数据需求的迅猛增长,持久化和数据查询技术的重要性日益凸显。关系型数据库已不再是唯一选择,数据的处理方式正变得日益多样化。在众多新兴的解决方案与工具中,Redis凭借其独特的优势脱颖而出。

【技术大纲】

为何Redis备受瞩目?原因在于其学习曲线平缓,短时间内便能对Redis有初步了解。同时,Redis在处理特定问题时展现出卓越的通用性,专注于其擅长的领域。深入了解Redis后,您将能够明确哪些任务适合由Redis承担,哪些则不适宜。这一经验对开发人员来说是一笔宝贵的财富。



在这个专栏中,我们将专注于Redis的6.2版本进行深入分析和介绍。Redis 6.2不仅是我个人特别偏爱的一个版本,而且在实际应用中也被广泛认为是稳定性和性能表现都相当出色的版本

【专栏目标】

本专栏深入浅出地传授Redis的基础知识,旨在助力读者掌握其核心概念与技能。深入剖析了Redis的大多数功能以及全部多机功能的实现原理,详细展示了这些功能的核心数据结构和关键算法思想。读者将能够快速且有效地理解Redis的内部构造和运作机制,这些知识将助力读者更好地运用Redis,提升其使用效率。

将聚焦于Redis的五大数据结构,深入剖析各种数据建模方法,并分享关键的管理细节与调试技巧。

【目标人群】

Redis技术进阶之路专栏:目标人群与受众对象,对于希望深入了解Redis实现原理底层细节的人群

1. Redis爱好者与社区成员

Redis技术有浓厚兴趣,经常参与社区讨论,希望深入研究Redis内部机制、性能优化和扩展性的读者。

2. 后端开发和系统架构师

在日常工作中经常使用Redis作为数据存储和缓存工具,他们在项目中需要利用Redis进行数据存储、缓存、消息队列等操作时,此专栏将为他们提供有力的技术支撑。

3. 计算机专业的本科生及研究生

对于学习计算机科学、软件工程、数据分析等相关专业的在校学生,以及对Redis技术感兴趣的教育工作者,此专栏可以作为他们的学习资料和教学参考。

无论是初学者还是资深专家,无论是从业者还是学生,只要对Redis技术感兴趣并希望深入了解其原理和实践,都是此专栏的目标人群和受众对象

让我们携手踏上学习Redis的旅程,探索其无尽的可能性!


链表

链表具备出色的节点重排与顺序访问能力,并且能够通过灵活地增删节点来调整链表的长度,从而满足各种实际应用需求。作为一种重要的数据结构,链表在多种高级编程语言中得到了内置支持。然而,由于Redis所依赖的C语言并未内置链表数据结构,Redis团队便自行实现了链表功能。

使用场景

在Redis中,链表的应用场景十分广泛,特别是在处理列表键时。当列表键包含的元素数量较多,或元素本身较长(如字符串)时,Redis会选择使用链表作为其底层实现。这是因为链表能够高效地处理大量数据,并且在处理长字符串时具有优势,能够确保数据的完整性和处理速度。通过利用链表的这些特性,Redis在处理复杂数据结构时表现出色,为用户提供了稳定、高效的数据存储和访问体验。

List(列表)和 链表的关系

列表(List)的底层实现是一个链表,其中的每个节点保存了一个值。除了用于链表之外,发布与订阅、慢查询、监视器等功能也利用了链表。Redis服务器本身还利用链表来保存多个客户端的状态信息,并构建客户端输出缓冲区(output buffer)。

链表的实现

链表是有多个链表节点组成,这一设计使得链表节点的数据结构和功能得到了清晰和统一的定义,进而确保了链表操作的准确性和一致性。下图是一个多个listNode组成的双端链表:



通过采用这种标准化的结构表示,我们能够实现更加高效和可靠的链表管理,为后续的链表操作和应用提供了坚实的基础。

链表的节点

每个链表节点均通过adlist.h中定义的listNode结构来具体表示,如下图源码所示:



多个listNode通过其内部的prev和next指针相互连接,从而构成了一个双端链表。这种链表结构在源代码中的实现如下所示,通过灵活使用这些指针,我们能够实现在链表中向前或向后遍历的能力,增强了链表的灵活性和实用性。

c

复制代码

/* Node, List, and Iterator are the only data structures used currently. */
typedef struct listNode {
    // 前置节点
    struct listNode *prev;
    // 后置节点
    struct listNode *next;
    // 节点的值
    void *value;
} listNode;

这种设计不仅优化了链表的性能,同时也提高了链表操作的效率,使得我们可以更轻松地对其进行增删改查等操作。

list的源码实现

在Redis中,list数据结构并非仅由简单的listNode模型构成的单一链表结构。实际上,它结合了多种操作元素,形成了一个更为丰富和灵活的数据结构。通过采用adlist.h/list来持有和管理这个链表,Redis不仅实现了基本的链表操作,还加入了一系列优化和特性,如下面的源码所示:

c

复制代码

typedef struct list {
    // 表头节点
    listNode *head;
    // 表尾节点   
    listNode *tail;   
    // 节点值复制函数
    void *(*dup)(void *ptr);
    // 节点值释放函数   
    void (*free)(void *ptr);
    // 节点值对比函数   
    int (*match)(void *ptr, void *key);
    unsigned long len;
} list;

上面的list模型结构提供了更高效、更便捷的数据处理能力。这种设计不仅增强了链表的实用性,也为Redis的广泛应用提供了强大的支持。

结构模型分析

List结构精心设计了表头指针head和表尾指针tail来指明链表的起始与终结,同时配备了链表长度计数器len来快速获取链表中元素的数量。此外,为了满足多态链表的需求,List结构还引入了dupfreematch这三个类型特定的函数成员:

  • dup函数:它负责复制链表节点中存储的值,确保在链表复制或扩展时能够准确地复制数据。
  • free函数:它负责释放链表节点中存储的值所占用的内存空间,防止内存泄漏,保持系统的稳定性。
  • match函数:它用于比较链表节点中存储的值与给定的输入值是否相同,为链表元素的查找和比较操作提供了便利。

这三个函数的引入不仅丰富了List结构的功能,还提高了其灵活性和可扩展性,使得Redis的list数据结构能够应对各种复杂的应用场景。

list和listNode和逻辑结构

由list结构和listNode结构共同构筑的链表,展现出一种既严谨又灵活的数据组织形式。


  • list结构:链表的整体视图,包括表头指针、表尾指针以及长度计数器,为链表的操作和管理提供了便利。
  • listNode结构:链表的基本组成单元,通过prev和next指针实现节点间的双向连接,形成了链表的骨架。
链表结构的特性

  • 双向性:链表中的每个节点均配备prev和next指针,使得无论是查找某个节点的前驱节点还是后继节点,操作复杂度均保持在O(1)水平,极大地提升了链表的遍历效率。
  • 无环设计:链表的表头节点的prev指针和表尾节点的next指针均指向NULL,这种设计确保了链表的访问始终有明确的终点,从而避免了循环引用导致的潜在问题。
  • 便捷的表头尾访问:Redis链表通过list结构中的head和tail指针,使得程序能够直接定位到链表的起始和结束节点,获取表头节点和表尾节点的操作复杂度同样为O(1),大大简化了链表的操作流程。
  • 高效的长度计数:list结构内置的len属性用于记录链表中的节点数量,这使得程序在需要获取链表长度时,无需遍历整个链表,即可直接获取结果,操作复杂度保持在O(1)。
  • 多态支持:链表节点采用void*指针来存储节点值,同时,list结构提供了dup、free和match三个属性,允许用户为节点值设置类型特定的函数。这种设计使得Redis的链表能够灵活地保存和处理各种不同类型的值,实现了多态链表的功能。
其余方法介绍和说明

以下是关于相关方法的介绍,这些方法定义在adlist.h头文件中,如以下源码所示。但值得注意的是,.h头文件通常只包含函数的声明和定义概念,而真正的函数实现机制则位于adlist.c源文件中。

c

复制代码

/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))
#define listGetDupMethod(l) ((l)->dup)
#define listGetFreeMethod(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)
/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
void listEmpty(list *list);
list *listAddNodeHead(list *list, void *value);
list *listAddNodeTail(list *list, void *value);
list *listInsertNode(list *list, listNode *old_node, void *value, int after);
void listDelNode(list *list, listNode *node);
listIter *listGetIterator(list *list, int direction);
listNode *listNext(listIter *iter);
void listReleaseIterator(listIter *iter);
list *listDup(list *orig);
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, long index);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
void listRotateTailToHead(list *list);
void listRotateHeadToTail(list *list);
void listJoin(list *l, list *o);
void listInitNode(listNode *node, void *value);
void listLinkNodeHead(list *list, listNode *node);
void listLinkNodeTail(list *list, listNode *node);
void listUnlinkNode(list *list, listNode *node);
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
#endif /* __ADLIST_H__ */

通过头文件和源文件的配合,我们得以清晰地划分函数的接口与实现,从而确保代码的可读性、可维护性和可重用性。在adlist.c中,每个函数的具体实现都详细描述了其功能机制,使得我们能够深入理解并应用这些函数。

函数介绍说明
方法 说明
list *listCreate(void); 创建一个新的空链表,并返回链表的指针。
void listRelease(list *list); 释放给定链表的内存,包括链表中的所有节点。
void listEmpty(list *list); 清空给定链表中的所有节点,但不释放链表本身的内存。
list *listAddNodeHead(list *list, void *value); 在链表的头部添加一个新节点,节点值为给定值,并返回更新后的链表指针。
list *listAddNodeTail(list *list, void *value); 在链表的尾部添加一个新节点,节点值为给定值,并返回更新后的链表指针。
list *listInsertNode(list *list, listNode *old_node, void *value, int after); 在给定旧节点之后(如果after为1)或之前(如果after为0)插入一个新节点,节点值为给定值,并返回更新后的链表指针。
void listDelNode(list *list, listNode *node); 从链表中删除给定的节点。
listIter *listGetIterator(list *list, int direction); 为链表创建一个迭代器,并返回迭代器的指针。迭代方向由direction参数指定。
listNode *listNext(listIter *iter); 获取迭代器当前指向的下一个节点的指针。
void listReleaseIterator(listIter *iter); 释放给定迭代器的内存。
list *listDup(list *orig); 复制给定的链表,并返回新链表的指针。
listNode *listSearchKey(list *list, void *key); 在链表中搜索具有给定键值的节点,并返回找到的节点的指针。如果未找到,则返回NULL。
listNode *listIndex(list *list, long index); 根据给定的索引获取链表中的节点。如果索引超出范围,则返回NULL。
void listRewind(list *list, listIter *li); 重置给定的迭代器,使其指向链表的头部。
void listRewindTail(list *list, listIter *li); 重置给定的迭代器,使其指向链表的尾部。
void listRotateTailToHead(list *list); 将链表的尾部节点移动到头部。
void listRotateHeadToTail(list *list); 将链表的头部节点移动到尾部。
void listJoin(list *l, list *o); 将另一个链表o的所有节点添加到链表l的尾部。
void listInitNode(listNode *node, void *value); 初始化给定的链表节点,设置其值为给定值。
void listLinkNodeHead(list *list, listNode *node); 将给定的节点链接到链表的头部。
void listLinkNodeTail(list *list, listNode *node); 将给定的节点链接到链表的尾部。
void listUnlinkNode(list *list, listNode *node); 从链表中取消链接给定的节点,但不释放节点的内存。


作者推荐 |【Redis技术进阶之路】「底层源码解析」揭秘高效存储模型与数据结构底层实现(链表)(二)https://developer.aliyun.com/article/1471149

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
1天前
|
存储 消息中间件 监控
Redis Stream:实时数据流的处理与存储
通过上述分析和具体操作示例,您可以更好地理解和应用 Redis Stream,满足各种实时数据处理需求。
26 14
|
17天前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
127 6
|
1月前
|
存储 NoSQL 算法
Redis分片集群中数据是怎么存储和读取的 ?
Redis集群采用哈希槽分区算法,共有16384个哈希槽,每个槽分配到不同的Redis节点上。数据操作时,通过CRC16算法对key计算并取模,确定其所属的槽和对应的节点,从而实现高效的数据存取。
49 13
|
1月前
|
存储 消息中间件 NoSQL
Redis数据结构:List类型全面解析
Redis数据结构——List类型全面解析:存储多个有序的字符串,列表中每个字符串成为元素 Eelement,最多可以存储 2^32-1 个元素。可对列表两端插入(push)和弹出(pop)、获取指定范围的元素列表等,常见命令。 底层数据结构:3.2版本之前,底层采用**压缩链表ZipList**和**双向链表LinkedList**;3.2版本之后,底层数据结构为**快速链表QuickList** 列表是一种比较灵活的数据结构,可以充当栈、队列、阻塞队列,在实际开发中有很多应用场景。
|
1月前
|
存储 NoSQL Redis
【赵渝强老师】Redis的存储结构
Redis 默认配置包含 16 个数据库,通过 `databases` 参数设置。每个数据库编号从 0 开始,默认连接 0 号数据库,可通过 `SELECT <dbid>` 切换。Redis 的核心存储结构包括 `dict`、`expires` 等字段,用于处理键值和过期行为。添加键时需指定数据库信息。视频讲解和代码示例详见内容。
|
1月前
|
存储 NoSQL 关系型数据库
Redis的ZSet底层数据结构,ZSet类型全面解析
Redis的ZSet底层数据结构,ZSet类型全面解析;应用场景、底层结构、常用命令;压缩列表ZipList、跳表SkipList;B+树与跳表对比,MySQL为什么使用B+树;ZSet为什么用跳表,而不是B+树、红黑树、二叉树
|
1月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
172 9
|
1月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
30 1
|
21天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
43 5
|
1月前
|
存储 算法 Java
数据结构的栈
栈作为一种简单而高效的数据结构,在计算机科学和软件开发中有着广泛的应用。通过合理地使用栈,可以有效地解决许多与数据存储和操作相关的问题。

推荐镜像

更多