深入理解Redis 底层数据结构之QuickList与 ZipList

Redis列表结构详解
本文深入探讨Redis中的三种列表数据结构:链表(LinkedList)、压缩列表(ZipList)和快速列表(QuickList)。分析了每种数据结构的特点、应用场景以及转换条件,并提供了实际操作示例。
深入理解Redis 底层数据结构之QuickList 与ZipList

centos7安装redis6.25结合docker镜像配置方法(附redis.conf文件)

list底层的数据结构:

  1. linkedList 链表

  2. zipList 压缩列表

  3. quickList 快链表

在3,2版本之前,列表是通过双向链表或压缩链表来实现的。他们之间可以互相转换,在3.2版本之后又新加入了新的数据结构:QuickList,也就是双向链表和压缩列表的结合。

链表:LinkedList
image-20210920100036234
  • 数据结构中常用的带头节点的双向链表,并且带有指向头节点的头指针和指向最后一个节点的尾指针,双向链表保证从任意一个位置可以向前或者向后进行遍历,但要注意头节点的前指针为null,尾节点的后指针为空,这也就说明了redis中的LinkedList数据结构是一个无环链表,不支持逆序遍历

  • 第二点就是说,操作命令中的lpush(left push) 、lpop(left pop )等命令中的left代表从队头进行操作,right代表从队尾进行操作

  • 结构中的 len 字段记录了整个链表的长度。

  • 由于链表本身的特性,对链表进行增删操作所需的时间非常少,时间复杂度仅为O(1);但如果是需要凭借索引寻找链表中某个值,时间花销就显得有些高,时间复杂度为O(n)

  • dup函数用于对某个链节点进行复制操作

  • free函数用于对某个链节点进行释放操作,也就是删除

  • match函数用于判断某个链节点的值和输入的值是否相等。


压缩列表:ZipList
image-20210920120409330
  • redis中的压缩列表是用字节数组作为其数据结构。
  • 在zipList的数据结构中有以下字段:
    • zlbytes:整个压缩列表所占用的字节数,本身是4个字节即32位,因此压缩链表的最大长度为(2^32-1)个字节
    • zltail_offset:压缩列表尾部的元素到起始地址的偏移量,可以直接找到表尾元素,即支持逆序遍历
    • zlength:表中元素的个数,占2字节,如果列表中的元素个数超过(2^16-1)个,那么需要遍历整个列表才能得到元素的个数
    • entry:列表中存储的元素,可能是整数,也可能是字节数组,具体结果见下图
    • zlend:元素的结束标志,占一个字节,是一个常量OXFF
image-20210920122206052
  • entry保存中列表中的元素,可能是整数,也可能是字节数组。其结构体有三个字段:

    • pre_length:上一个元素的长度,方便逆序遍历的时候直接找到上个节点的起始位置。当前一个元素的长度小于254个字节的时候,用一个字节表示;当前一个元素的长度大于等于254个字节的时候,用5个字节表示,此时前一个字节存储固定值254

    • encoding: 长度为两个 bit 。 它的值可以是 00 、 01 、 10 和 11 。00 、 01 和 10表示 content 部分保存着字符数组,11表示 content 部分保存着整数。

    • content:保存真正的元素。

转换条件:
创建一个列表是,默认使用的数据结构是zipList,但在满足以下条件的时候,zipList会自动转化为双向链表:

  1. 压缩列表中插入了一个过长的字符串(长度可配置,默认65)
  2. 压缩列表中的节点数超过某一数量(默认512)

总结

  1. 当存储的元素很少的时候,通常采用zipList作为基础数据结构,而当数据元素较多时则采用LinkedList。

  2. 从数据结构本身的特点而言:LinkedList在插入和删除元素方面具有很大的优势。但其需要额外存储两个指针,存储开销比较大,而且每个元素都是单独的内存空间,地址不连续,容易造成内存碎片。

  3. 而压缩列表存储在一块连续的内存空间中,但是增加删除元素开销较大,在内存扩容执行realloc操作的时候需要进行大量的拷贝操作

快速列表:QuickList
image-20210920130149269

​ redis3.2之后,增加了新的数据结构:QuickList。它结合了LinkedList和zipList的优点。它将LinkedList的每个节点都用zipList的方式来存储。LinkedList节点之间依然采用双向链表连接起来,只

Redis底层数据结构的设计是其高性能的核心之一。Redis内部通过一系列高效的数据结构来实现对外暴露的数据类型(如string、list、hash、set、zset),这些底层结构包括但不限于sds(简单动态字符串)、dict(字典)、intset(整数集合)、ziplist(压缩列表)、quicklist(快速列表)、listpack和skiplist(跳跃表)等。 ### Redis底层数据结构架构设计 1. **SDS(Simple Dynamic String)** Redis使用SDS来替代传统的C语言字符串,解决了C字符串的一些局限性,例如无法高效地获取字符串长度、容易造成缓冲区溢出等。SDS的结构包含一个长度字段,使得Redis能够以O(1)的时间复杂度获取字符串长度[^1]。 2. **Dict(字典)** 字典是Redis中用于实现哈希表的数据结构,基于数组和链表的组合结构。它支持O(1)时间复杂度的读写操作。Redis中的哈希键(hash key)就是通过字典实现的。字典的底层实现依赖于哈希函数和链表解决冲突,从而保证高效的存储和检索[^3]。 3. **Ziplist(压缩列表)** Ziplist是一种紧凑的、连续内存块的结构,用于存储多个小数据项。它的内存布局包括zlbytes(总字节数)、zltail(最后一项的偏移量)、zllen(数据项数量)、entry(实际数据项)和zlend(结束标记)。每个entry包含前一个节点的长度、当前节点的数据长度和具体的数据内容。这种设计使得ziplist非常适合存储小型数据集合,同时减少了内存碎片[^4]。 4. **Listpack** Listpack是Redis 5.0引入的新结构,用于替代ziplistziplist不同的是,listpack中的每个节点不再记录前一个节点的长度,从而避免了连锁更新的问题。这种改进显著提高了内存使用的效率和性能[^2]。 5. **Quicklist(快速列表)** QuicklistRedislist数据类型的默认实现。它结合了ziplist和双向链表的优点,将多个ziplist节点通过双向链表连接起来,形成一个链表结构。这种设计既保持了内存的紧凑性,又避免了单个ziplist可能带来的性能问题,适合处理大数据量的列表操作。 6. **Intset(整数集合)** Intset是Redis中专门用于存储整数集合的数据结构。它通过动态调整存储方式(int16_t、int32_t或int64_t),尽可能减少内存占用。Intset支持高效的插入、删除和查找操作,适用于存储大量整数且不需要字符串功能的场景。 7. **Skiplist(跳跃表)** SkiplistRedis中zset(有序集合)的底层实现之一。跳跃表通过多层索引结构实现了高效的查找、插入和删除操作,平均时间复杂度为O(log n)。跳跃表的层级设计使得它在处理大规模数据时依然保持较高的性能。 ### Redis底层数据结构的组织关系 Redis底层数据结构之间存在一定的嵌套关系。例如,字典(dict)的实现依赖于哈希表(hashtable),而哈希表的底层实现可能涉及链表(用于处理哈希冲突)。类似地,quicklist的实现结合了ziplist和双向链表的特性,而zset则通过跳跃表(skiplist)和字典(dict)的组合实现了高效的有序集合操作。 ### 示例代码:Redis底层数据结构的模拟实现 以下是一个简化版的ziplist结构模拟实现,展示了如何通过紧凑的内存布局存储数据项: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct ziplist { unsigned int zlbytes; // 总字节数 unsigned int zltail; // 最后一个entry的偏移量 unsigned short zllen; // entry数量 char entries[0]; // 数据项 } ziplist; typedef struct entry { unsigned int prevrawlen; // 前一个entry的长度 unsigned int len; // 当前entry的长度 char data[0]; // 数据内容 } entry; // 创建一个新的ziplist ziplist* create_ziplist() { ziplist *zl = (ziplist*)malloc(sizeof(ziplist)); zl->zlbytes = sizeof(ziplist); zl->zltail = 0; zl->zllen = 0; return zl; } // 向ziplist中添加一个entry void add_entry(ziplist *zl, const char *data, unsigned int len) { entry *e = (entry*)malloc(sizeof(entry) + len); e->prevrawlen = zl->zltail; e->len = len; memcpy(e->data, data, len); // 更新ziplist的总字节数和最后一个entry的偏移量 zl->zlbytes += sizeof(entry) + len; zl->zltail = (char*)zl + zl->zlbytes - (char*)zl; zl->zllen++; // 将新entry添加到ziplist的末尾 memcpy(zl->entries + zl->zlbytes - sizeof(ziplist), e, sizeof(entry) + len); free(e); } int main() { ziplist *zl = create_ziplist(); add_entry(zl, "hello", 5); add_entry(zl, "world", 5); printf("Total bytes: %u\n", zl->zlbytes); printf("Number of entries: %u\n", zl->zllen); printf("Last entry offset: %u\n", zl->zltail); free(zl); return 0; } ``` 这段代码模拟了ziplist的基本结构和操作,展示了如何通过紧凑的内存布局存储数据项。 ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值