Redis之quicklist和listpack(四)

Redis之quicklist和listpack(四)

quicklist

quicklist 的结构体跟链表的结构体类似,都包含了表头和表尾,区别在于 quicklist 的节点是 quicklistNode。

typedef struct quicklist {
    //quicklist的链表头
    quicklistNode *head;      //quicklist的链表头
    //quicklist的链表头
    quicklistNode *tail; 
    //所有压缩列表中的总元素个数
    unsigned long count;
    //quicklistNodes的个数
    unsigned long len;       
    ...
} quicklist;

接下来看看,quicklistNode 的结构定义:

typedef struct quicklistNode {
    //前一个quicklistNode
    struct quicklistNode *prev;     //前一个quicklistNode
    //下一个quicklistNode
    struct quicklistNode *next;     //后一个quicklistNode
    //quicklistNode指向的压缩列表
    unsigned char *zl;              
    //压缩列表的的字节大小
    unsigned int sz;                
    //压缩列表的元素个数
    unsigned int count : 16;        //ziplist中的元素个数 
    ....
} quicklistNode;

可以看到,quicklistNode 结构体里包含了前一个节点和下一个节点指针,这样每个 quicklistNode 形成了一个双向链表。但是链表节点的元素不再是单纯保存元素值,而是保存了一个压缩列表,所以 quicklistNode 结构体里有个指向压缩列表的指针 *zl。

我画了一张图,方便你理解 quicklist 数据结构。

在这里插入图片描述

在向 quicklist 添加一个元素的时候,不会像普通的链表那样,直接新建一个链表节点。而是会检查插入位置的压缩列表是否能容纳该元素,如果能容纳就直接保存到 quicklistNode 结构里的压缩列表,如果不能容纳,才会新建一个新的 quicklistNode 结构。

quicklist 会控制 quicklistNode 结构里的压缩列表的大小或者元素个数,来规避潜在的连锁更新的风险,但是这并没有完全解决连锁更新的问题。

listpack

listpack 采用了压缩列表的很多优秀的设计,比如还是用一块连续的内存空间来紧凑地保存数据,并且为了节省内存的开销,listpack 节点会采用不同的编码方式保存不同大小的数据。

我们先看看 listpack 结构:

在这里插入图片描述

listpack 头包含两个属性,分别记录了 listpack 总字节数和元素数量,然后 listpack 末尾也有个结尾标识。图中的 listpack entry 就是 listpack 的节点了。

每个 listpack 节点结构如下:

在这里插入图片描述

主要包含三个方面内容:

  • encoding,定义该元素的编码类型,会对不同长度的整数和字符串进行编码;
  • data,实际存放的数据;
  • len,encoding+data的总长度;
listpack编码方法

首先先了解 listpack 元素的编码类型。如果你看了 listpack.c 文件,你会发现该文件中

有大量类似 LP_ENCODING__XX_BIT_INT 和 LP_ENCODING__XX_BIT_STR 的宏定义,

如下所示:

#define LP_ENCODING_7BIT_UINT 0 

#define LP_ENCODING_6BIT_STR 0x80 

#define LP_ENCODING_13BIT_INT 0xC0 

... 

#define LP_ENCODING_64BIT_INT 0xF4 

#define LP_ENCODING_32BIT_STR 0xF0

这些宏定义其实就对应了 listpack 的元素编码类型。具体来说,listpack 元素会对不同长

度的整数和字符串进行编码,这里我们分别来看下。

首先,对于整数编码来说,当 listpack 元素的编码类型为 LP_ENCODING_7BIT_UINT时,表示元素的实际数据是一个 7 bit 的无符号整数。又因为 LP_ENCODING_7BIT_UINT本身的宏定义值为 0,所以编码类型的值也相应为 0,占 1 个 bit。此时,编码类型和元素实际数据共用 1 个字节,这个字节的最高位为 0,表示编码类型,后续的 7 位用来存储 7 bit 的无符号整数,如下图所示:

在这里插入图片描述

而当编码类型为 LP_ENCODING_13BIT_INT 时,这表示元素的实际数据是 13 bit 的整数。同时,因为 LP_ENCODING_13BIT_INT 的宏定义值为 0xC0,转换为二进制值是1100 0000,所以,这个二进制值中的后 5 位和后续的 1 个字节,共 13 位,会用来保存13bit 的整数。而该二进制值中的前 3 位 110,则用来表示当前的编码类型。我画了下面这张图,你可以看下。

在这里插入图片描述

好,在了解了 LP_ENCODING_7BIT_UINT 和 LP_ENCODING_13BIT_INT 这两种编码类型后,剩下的 LP_ENCODING_16BIT_INT、LP_ENCODING_24BIT_INT、LP_ENCODING_32BIT_INT 和 LP_ENCODING_64BIT_INT,你应该也就能知道它们的编码方式了。这四种类型是分别用 2 字节(16 bit)、3 字节(24 bit)、4 字节(32 bit)和 8 字节(64 bit)来保存整数数据。同时,它们的编码类型本身占 1 字节,编码类型值分别是它们的宏定义值。

然后,对于字符串编码来说,一共有三种类型,分别是 LP_ENCODING_6BIT_STR、LP_ENCODING_12BIT_STR 和 LP_ENCODING_32BIT_STR。从刚才的介绍中,你可以看到,整数编码类型名称中 BIT 前面的数字,表示的是整数的长度。因此类似的,字符串编码类型名称中 BIT 前的数字,表示的就是字符串的长度。

比如,当编码类型为 LP_ENCODING_6BIT_STR 时,编码类型占 1 字节。该类型的宏定义值是 0x80,对应的二进制值是 1000 0000,这其中的前 2 位是用来标识编码类型本身,而后 6 位保存的是字符串长度。然后,列表项中的数据部分保存了实际的字符串。

下面的图展示了三种字符串编码类型和数据的布局,你可以看下。

在这里插入图片描述

listpack 避免连锁更新的实现方式

可以看到,listpack 没有压缩列表中记录前一个节点长度的字段了,listpack 只记录当前节点的长度,当我们向 listpack 加入一个新元素的时候,不会影响其他节点的长度字段的变化,从而避免了压缩列表的连锁更新问题

不过,你可能会有疑问:如果 listpack 列表项只记录当前项的长度,那么 listpack 支持

**从左向右正向查询列表,或是从右向左反向查询列表吗?**下节会解释

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值