深入理解Redis基本数据结构(list)——Redis深度历险笔记2

铁汁们,咱们又见面了

今天来讲list,相当于Java里的LinkedList,也就是链表,而且是双向链表。类似于Deque,两端都可以操作。

同样的,list在不同大小的情况下,和String一样,有不同的数据结构。

还记得么,embstr和raw,忘记了的话,去翻看讲解string的那一章吧。

好了,list对应的两个数据结构,分别叫做ziplist(压缩列表)quicklist

但不管怎样,别忘了我们的对象头

struct RedisObject {
	int4 type;//4bit,对象类型,那最多最多16种对象咯
	int4 encoding;//存储形式
	int24 lru;//Least Recently Used,最近最少使用信息
	int32 refcount;//暂未知,之后补上,todo
	void *ptr;//8byte 指针对象,这个值是个指针啊,存的是目标的地址!
}

这回,ziplist没说跟对象头紧紧的贴在一起了。

struct RedisObject {
	int4 type;//4bit,对象类型,那最多最多16种对象咯
	int4 encoding;//存储形式
	int24 lru;//Least Recently Used,最近最少使用信息
	int32 refcount;//暂未知,之后补上,todo
	void *ptr;//8byte 指针对象,这个值是个指针啊,存的是目标的地址!
}
//没说跟对象头贴在一起
struct ziplist<T>{
	int32 zlbytes;//整个压缩列表占用字节数
	int32 zltail_offset;//最后一个元素距离压缩列表起始位置的偏移量
	int16 zllength;//元素的个数
	T[] entries;//元素内容列表,依次紧凑存储
	int8 zlend;//标志压缩列表的结束,值恒为0xFF
}

咱注意,entries是紧凑存储的,而且entries类型未定,多少长咱都不知道的啊。假如entries是字符串类型的,每个entries可能长度都不一样的。
ziplist图示
我们看下entry的结构到底长啥样

struct entry{
	int<var> prevlen;//前一个entry的长度
	int<var> encoding;//表示content的类型和长度,如果content很短的话,甚至直接表示值了
	optional byte[] content;//可选的
}

entry的prevlen是可变长的,如果前面的entry长度小于254,就是2的8次方-2呗,那么prevlen就是1字节长度。

如果超过254字节,那么prevlen就会占用5字节长度(突然变得这么长)!并且第一个字节的值始终为0xFE。

主要是为了倒着取值的时候,能快速取到上一个的位置。想起来了没,虽然list是双向链表,但是在ziplist的情况下,咱们没有前后指针,只能用这种标记长度的方法来了。

encoding存储了后面content的编码类型信息。

这个设计厉害了,encoding需要表达2个意思,类型+大小。

类型可以是字符串或者是数字

00,01,10开头的是字符串,00xx xxxx表示最大长度为63的字符串,后面那6位表示字符串的长度
01xx xxxx xxxx xxxx 表示中等长度的字符串,后面14位表长度
1000 0000 aaaaaaaa bbbbbbbb cccccccc dddddddd是特大字符串,不过这个数据类型是没机会使用的,因为压缩列表通常只用来存储小数据。

(1100-1111)前面这4位+(0000)后四位,分别表示2、4、8、16个字节长的整数。
11110000表示3字节的整数
11111110表示1个字节的整数
11111111表示zlend的值,也就是0xFF
1111xxxx表示1~12的极小值。

我都震惊了。不过对于作者来说,应该是常规操作了。常规的通信设计。

这么紧挨着在一起,肯定是有问题的啊,新增,修改的时候,因为紧挨在一起,如果变动长度,免不了后面的数据全部移动。

所以,这个只能存点少的元素。级联更新太可怕了。最好不要变动。

也无怪,叫做压缩(zip)列表。

快速列表 quicklist

以前,如果内容超过了ziplist的范围,则会变成普通的双向链表linkedlist。

struct listNode{
	listNode *pre;
	listNode *next;
	T value;
}
struct list{
	listNode *head;
	listNode *tail;
	long length;
}

看listNode可知,两个指针太浪费了,64位操作系统,就要占去2*8字节,整整16字节啊,这太浪费了。而且listNode分散在内存里,加剧内存碎片化,影响内存管理效率。

所以现在quicklist代替了linkedlist

quicklist其实就是把listNode类型,换成了ziplist。。。
一个个ziplist串在了一起。。。

struct ziplist<T>{
	int32 zlbytes;//整个压缩列表占用字节数
	int32 zltail_offset;//最后一个元素距离压缩列表起始位置的偏移量
	int16 zllength;//元素的个数
	T[] entries;//元素内容列表,依次紧凑存储
	int8 zlend;//标志压缩列表的结束,值恒为0xFF
}
struct ziplist_compressed {
	int32 size;
	byte[] compressed_data;
}
struct quicklistNode {
	quicklistNode* pre;
	quicklistNode* next;
	ziplist* zl;//指向压缩列表
	int32 size;//ziplist的字节总数
	int16 count;//ziplist的元素数量
	int2 encoding;//存储形式,原生数组还是LZF压缩存储
}

struct quicklist {
	quicklistNode* head;
	quicklistNode* tail;
	long count;//元素总数
	int node;//ziplist节点个数
	int compressDepth;//LZF算法压缩深度
}

LZF看起来很厉害的样子。。。

每个ziplist默认存储8KB,受list-max-ziplist-size决定

quicklist默认的压缩深度是0,也就是默认不压缩。压缩深度也是可控制的,list-compress-depth。压缩深度的意思,就是,除了0以外,从1开始,代表的都是从首尾开始,几个ziplist不压缩,其他的都压缩。

listpack

5.0版本引进了新的数据结构,是对ziplist的改进。
我们还记得,ziplist是一种紧凑的数组结构,每个entry都存了上一个entry的length,如果上一个entry的长度从253变成了254,那么prevlen 字段就要从1字节扩展到5字节,那么整个后面的entry都要更新prevlen。就是所谓的级联更新

正是因为级联更新太可怕了,所以新来了listpack。

取消了entry里记录prevlen的操作。

struct listpack<T> {
	int32 total_type;//占用的总字节数;
	int16 size;//元素个数
	T[] entries;//紧凑排列的元素列表
	int8 end;//同zlend一样,恒为0xfFF
}
struct ziplist<T>{
	int32 zlbytes;//整个压缩列表占用字节数
	int32 zltail_offset;//最后一个元素距离压缩列表起始位置的偏移量
	int16 zllength;//元素的个数
	T[] entries;//元素内容列表,依次紧凑存储
	int8 zlend;//标志压缩列表的结束,值恒为0xFF
}

比较一下,这两个数据结构少了一个zltail_offset,当然这个可以计算得到。

那listpack是如何消除级联更新的呢?
struct lpentry {
	int<var> encoding;
	optional byte[] content;
	int<var> length;当前entry的长度
}

struct entry{
	int<var> prevlen;//前一个entry的长度
	int<var> encoding;//表示content的类型和长度,如果content很短的话,甚至直接表示值了
	optional byte[] content;//可选的
}

对比一下,将prevlen改成了length,这样确实,上一个entry长度变化,对当前的entry的属性不会影响。

看到这里,我不得不佩服redis的作者是个人才,扣的厉害。老钱总结的也很棒。推荐大家去买老钱的书~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

rgbhi

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

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

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

打赏作者

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

抵扣说明:

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

余额充值