redis底层数据结构 压缩列表ZipList

ZipList

我们都知道Dict中是数组加链表的方式实现的,但是链表是一个个指针指向,内存不连续且指针还需要占据一定的空间,那么由此redis引出一个ZipList压缩列表

ZipList是一个特俗的“双端链表”,由一系列特殊编码的的连续内存块组成。可以在任意一端进行压入和弹出操作,并且该操作的时间复杂度是O(1)的。(是没有指针的)

既然这种数据结构没有指针,那么是如何实现压入和弹出操作的

数据结构如下:
在这里插入图片描述

  • zlbytes: 总的字节数,占4字节,
  • zltail: 尾偏移量(这个直接拿到尾节点的数据)占4字节,
  • zllen: entry个数,占2字节,
  • entry: 具体的数据,字节数不确定,长度由内容决定
  • zlend: 结束标志:0xff, 占1字节

头部分(zlbytes,zltail,zllen)和尾部分(zlend)占的字节数是固定的,直接可以拿到头节点和尾节点的数据的,这就实现了压入和弹出操作

ZipListEntry

ZipList中的Entry并不像普通的链表那样记录前后节点的指针,因为记录两个指针需要16字节,浪费内存,而是采用了下面的结构
在这里插入图片描述

  • previous_entry_length: 前一节的长度,占一个或5个字节:
    1. 如果前一节的长度小于254字节,则采用1个字节保存这个长度值
    2. 如果前一节的长度大于254字节,则采用5个字节保存这个长度值,第一个字节是0xfe,后4个字节才是真实长度
  • encoding: 编码属性,记录content的数据类型(字符串还是整数)以及长度,占用1个、2个或5个字节
  • content: 负责保存节点的数据,可以是字符串或整数

这样我们可以根据前一个节点知道后一个节点数据,我们知道节点起始位置 + previous_entry_length字节 + encoding字节 + content字节,就可以知道下一个节点数据的位置,实现了正序遍历。
也可以进行倒叙遍历,zltail记录了最后一个节点偏移量,那么我们可以得到最后一个节点的起始地址,最后一个节点的起始地址 - 当前节点previous_entry_length,就可以得到前一个节点的起始地址。
redis这样设计没有通过指针就实现了寻址操作
注意:ZipList中所有的存储长度采用了小端字节序,即低位字节在前,高位字节在后,例如:0x1234,采用小端字节序后实际存储的值是:0x3412

Encoding编码

ZipListEntry中的Encoding编码分为字符串和整数两种:

  • 字符串: 如果是以“00”,“01”,或者 “10”开头,则证明content是字符串
编码编码长度字符串大小
|00pppppp|1字节<=63字节
|01pppppp| |qqqqqqqq|2字节<=16383字节
|10000000| |qqqqqqqq| |rrrrrrrr| |ssssssss| |tttttttt|5字节<=4294967295字节

1字节和2字节除了高两位,后续低位记录就是content的长度
5字节的前面1个字节就是标识后续的4个字节表是长度

例如:我们要保存字符串“ab”和“bc”

“ab”和“bc”的字节占2,都是字符串所以encoding是:00000010,对应的16进制就是0x02
在这里插入图片描述

  • 整数: 如果encoding是以11开头,则证明content是整数,且content固定只占用1个字节
编码编码长度整数类型
|11000000|1字节int16(2字节)
|11010000|1字节int32(4字节)
|11100000|1字节int64(8字节)
|11110000|1字节24位有符整数(3字节)
|11111110|1字节8位有符整数(1字节)
|1111xxxx|1字节直接在xxxx位置保存数据,范围从0001- 1101, 减1后结果尾实际值

例如:一个ZipList中包含两个整数值: “2”和“5”
2和5的数值比较小,都在0001- 1101这个范围,那么就采用最后一种方式 :
2和5的entry结构如下:
在这里插入图片描述
ZipList如下:
在这里插入图片描述

ZipList连锁更新问题

ZipList的每个Entry都包含previous_entry_length来记录上一个节点的大小,长度是1个或5个字节:

  • previous_entry_length: 前一节的长度,占一个或5个字节:
    1. 如果前一节的长度小于254字节,则采用1个字节保存这个长度值
    2. 如果前一节的长度大于等于254字节,则采用5个字节保存这个长度值,第一个字节是0xfe,后4个字节才是真实长度

现在假设我们有N个连续、长度尾250-253字节之间的entry,因此entry的previous_entry_length属性用一个字节即可表示,如图所示:
在这里插入图片描述
这时队首插入一个新的节点,字节长度超过了等于254节点,那么后一个节点previous_entry_length用5个字节表示,那么这个节点也超过了254,后续节点的previous_entry_length都变成了5个字节,这时候触发了后续节点的连续更新,导致连锁更新,
ZipList的删除和新增操作都可能导致连锁更新,不过这种触发的条件概率是很低的

总结

  1. 压缩列表可以看作是一种连续内存空间的“双向链表”
  2. 列表的节点之前不是通过指针,而是记录上一个节点和本节点长度来寻址,内存占用比较低
  3. 如果列表数据过多,导致列表过长,可能影响查询性能
  4. 增或删可能会触发连锁更新的问题
  • 25
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值