1.为什么使用
前面介绍了ziplist压缩列表,他主要在zset和hash这两种数据类型中,并且当zset和hash中的数据元素较少的时候使用.它的特点是提高了内存使用率,并且可以方便的从两边遍历数据,但是因为它把数据保存在一块连续的内存空间中,所以它并不适合频繁的删除,插入,修改操作,这会造成频繁的申请内存和复制数据,而且极端情况下还会引起级联更新.为了解决级联更新的问题,redis引入了一种新的数据类型listpack.
listpack的出现并不能解决链表中频繁插入和删除造成的重新申请空间和复制数据的问题,主要为了解决级联更新的问题,未来redis可能会使用listpack完全替代ziplist.
2.内部结构
2.1 listpack
struct listpack<T> { int32 total_bytes; // 整个listpack,占用的总字节数 int16 size; // 整个listpack中元素个数 T[] entries; // 紧凑排列的元素列表 int8 end; // listpack的结束符,恒为 0xFF }
total_bytes:和ziplist中一样,listpack中也记录了整个listpack的总字节数
size:和ziplist中一样,listpack中也记录了整个listpack的总元素的个数
但是在listpack中并没有记录最后一个元素的地址,这个是和ziplist中不同的.
2.2 lpentry
struct lpentry { int<var> encoding; //entry中数据的的编码方式,具体在下面介绍 optional byte[] content; //存放的具体的内容 int<var> length; // 当前lpEntry的长度 }
length:和ziplist中的最大不同支出在于,listpack把当前长度放到了最后,并且这里存放的也不是前一个entry的长度,而是当前entry的长度.这样llistpack不需要保存最后一个元素的地址,只需要根据整个listpack的长度和最后一个entry的长度就可以计算出最后一个entry的地址.因为entry中不保存其他entry中的长度了,所以当对listpack进行增删改的时候并不会出现级联更新的情况.为了提高内存使用率,redis把length的长度设计为不固定的,它会随着当前entry长度的改变而改变.编码方式和utf8一样,先读取最后一个字节的最高位如果是1,则表示下一个字节也属于长度的一部分,知道找到第一个位不是1的字节为止,但是length的最大长度位5个字节.
encoding:同样是为了提高内存使用率,reids也对encoding经过了复杂的设计.
1、0xxxxxxx 表示非负小整数,可以表示 0~127。
2、10xxxxxx 表示小字符串,长度范围是 0~63,content 字段为字符串的内容。
3、110xxxxx yyyyyyyy 表示有符号整数,范围是-2048~2047。
4、1110xxxx yyyyyyyy 表示中等长度的字符串,长度范围是 0~2047,content 字段为字符串的内容。
5、11110000 aaaaaaaa bbbbbbbb cccccccc dddddddd 表示大字符串,四个字节表示长度,content 字段为字符串内容。
6、11110001 aaaaaaaa bbbbbbbb 表示 2 字节有符号整数。内容保存在content中
7、11110010 aaaaaaaa bbbbbbbb cccccccc 表示 3 字节有符号整数。直接保存在encoding中
8、11110011 aaaaaaaa bbbbbbbb cccccccc dddddddd 表示 4 字节有符号整数。直接保存在encoding中
9、11110011 aaaaaaaa ... hhhhhhhh 表示 8 字节有符号整数。直接保存在encoding中
10、11111111 表示 listpack 的结束符号,也就是 0xFF
3.使用数据类型
在redis6.*中listpack已经被用在在hash类型中替代ziplist了,暂时只有hash中使用