Redis之List数据结构底层原理

1:Redis链表实现的特性

双端:链表节点带有 prev 和 next 指针,获取某个节点的前置节点和后置节点复杂度都是O(1)。

无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问以NULL为终点。

带表头指针和表尾指针:通过list结构的 head 和 tail 指针,程序获取链表的表头节点和表尾结点的复杂度都是O(1)。

带链表长度计数器:程序使用 list 结构的 len属性对 list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1)。

多态:链表节点使用 void* 指针来保存节点值,并且通过 list 结构的 dup、 free、match 三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

2: 应用场景

lpush+lpop=Stack(栈)

lpush+rpop=Queue(队列)

lpush+ltrim=Capped Collection(有限集合)

lpush+brpop=Message Queue(消息队列)

排行榜,数据最新列表等等

集合设置过期,只能给整个集合设置,不能单独给某一个元素设置,没有给单独元素设置过期时间的策略

3:存储方式

其底层有linkedList、zipList和quickList这三种存储方式

1:linkedList

与Java中的LinkedList类似,Redis中的linkedList是一个双向链表,也是由一个个节点组成的。Redis中借助C语言实现的链表节点结构如下所示

 

pre指向前一个节点,next指针指向后一个节点,value保存着当前节点对应的数据对象。listNode的示意图如下所示

链表的结构如下:

head指向链表的头节点

tail指向链表的尾节点

dup函数用于链表转移复制时对节点value拷贝的一个实现,一般情况下使用等号足以,但在某些特殊情况下可能会用到节点转移函数,默认可以给这个函数赋值NULL即表示使用等号进行节点转移

free函数用于释放一个节点所占用的内存空间,默认赋值NULL的话,即使用Redis自带的zfree函数进行内存空间释放

match函数是用来比较两个链表节点的value值是否相等,相等返回1,不等返回0

len表示这个链表共有多少个节点,这样就可以在O(1)的时间复杂度内获得链表的长度

链表的结构如下:

2:zipList类型

Redis的zipList结构如下所示

 

zipList的结构如下所示

注意到zltail_offset这个参数,有了这个参数就可以快速定位到最后一个entry节点的位置,然后开始倒序遍历,也就是说zipList支持双向遍历。

3:linkList与zipList的对比

1:当列表对象中元素的长度较小或者数量较少时,通常采用zipList来存储;当列表中元素的长度较大或者数量比较多的时候,则会转而使用双向链表linkedList来存储。

①列表对象保存的所有字符串元素的长度都小于64字节

②列表元素保存的元素数量小于512个

2:双向链表linkedList便于在表的两端进行push和pop操作,在插入节点上复杂度很低,但是它的内存开销比较大。首先,它在每个节点上除了要保存数据之外,还有额外保存两个指针;其次,双向链表的各个节点都是单独的内存块,地址不连续,容易形成内存碎片。

3:zipList存储在一块连续的内存上,所以存储效率很高。但是它不利于修改操作,插入和删除操作需要频繁地申请和释放内存。特别是当zipList长度很长时,一次realloc可能会导致大量的数据拷贝。

4:为什么有了linkedList还有设计一个zipList呢?就像zipList的名字一样,它是一个压缩列表,是为了节约内存而开发的。相比于linkedList,其少了pre和next两个指针。在Redis中,pre和next指针就要占用16个字节(64位系统的一个指针就是8个字节)。

另外,linkedList的每个节点的内存都是单独分配,加剧内存的碎片化,影响内存的管理效率。与之相对的是,zipList是由连续的内存组成的,这样一来,由于内存是连续的,就减少了许多内存碎片和指针的内存占用,进而节约了内存。

4:quickList

1:起源

在Redis3.2版本之后,list的底层实现方式又多了一种,quickList。qucikList是由zipList和双向链表linkedList组成的混合体。它将linkedList按段切分,每一段使用zipList来紧凑存储,多个zipList之间使用双向指针串接起来。

示意图如下所示:

2:每个zipList可以存储多少个元素

redis.conf文件,在DVANCED CONFIG下面有着清晰的记载

quickList内部默认单个zipList长度为8k字节,即list-max-ziplist-size的值设置为-2,超出了这个阈值,就会重新生成一个zipList来存储数据。

根据注释可知,性能最好的时候就是就是list-max-ziplist-size为-1和-2,即分别是4kb和8kb的时候,当然,这个值也可以被设置为正数,当list-max-ziplist-szie为正数n时,表示每个quickList节点上的zipList最多包含n个数据项。

4:小结

1:linkedList

1:linkedList便于在表的两端进行 push 和 pop 操作,但是它的内存开销比较大

2:linkedList每个节点上除了要保存数据之外,还要额外保存两个指针

3:linkedList的各个节点是单独的内存块,地址不连续,节点多了容易产生内存碎片

2:ziplist

1:ziplist 由于是一整块连续内存,所以存储效率很高

2:ziplist 不利于修改操作,每次数据变动都会引发一次内存的 realloc

3:当 ziplist 长度很长的时候,一次 realloc 可能会导致大批量的数据拷贝,进一步降低性能

3:quicklist

1:空间效率和时间效率的折中

2:结合了双端链表和压缩列表的优点

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不要迷恋发哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值