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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
云解析 DNS,旗舰版 1个月
简介: 作者推荐 |【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
相关文章
|
12天前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
85 9
|
11天前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
53 16
|
1天前
|
存储 网络协议 安全
30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场
本文精选了 30 道初级网络工程师面试题,涵盖 OSI 模型、TCP/IP 协议栈、IP 地址、子网掩码、VLAN、STP、DHCP、DNS、防火墙、NAT、VPN 等基础知识和技术,帮助小白们充分准备面试,顺利踏入职场。
9 2
|
1天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
11天前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
55 8
|
14天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
41 4
|
15天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
15天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
8天前
|
安全 测试技术 Go
Go语言中的并发编程模型解析####
在当今的软件开发领域,高效的并发处理能力是提升系统性能的关键。本文深入探讨了Go语言独特的并发编程模型——goroutines和channels,通过实例解析其工作原理、优势及最佳实践,旨在为开发者提供实用的Go语言并发编程指南。 ####
|
14天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
32 0

推荐镜像

更多