简介
ziplist(压缩列表)是redis较为常见的数据结构,zset和hash容器对象在储存较少的元素时采用压缩列表进行储存,压缩列表是一块连续的内存,没有任何空间冗余。
hash例子:
127.0.0.1:6379> HSET hash_user_age user1 19
(integer) 1
127.0.0.1:6379> HSET hash_user_age user2 20
(integer) 1
127.0.0.1:6379> DEBUG OBJECT hash_user_age
Value at:0x7fce1360d6d0 refcount:1 encoding:ziplist serializedlength:32 lru:14035605 lru_seconds_idle:10
zset例子:
127.0.0.1:6379> ZADD zset_user_age 19 user1
(integer) 1
127.0.0.1:6379> ZADD zset_user_age 20 user2
(integer) 1
127.0.0.1:6379> DEBUG OBJECT zset_user_age
Value at:0x7fce1351c660 refcount:1 encoding:ziplist serializedlength:32 lru:14035733 lru_seconds_idle:9
上述例子可以看出当zset、hash元素个数较少的时候,其内部用的是压缩列表进行储存的,zset、hash容器元素较多的时候底层的储存结构会变为跳表和hashtable。
压缩列表结构
压缩列表是一块连续的内存,整个部分分为5个数据类型。
- zlbytes,该字段表示整个压缩列表占据了多少字节,用int值储存。
- zltail_offset,该字段记录了压缩列表的尾结点距离压缩列表的起始地址有多少字节。
- zllength,记录元素的个数。
- entrys,储存元素的内容,是一个数组类型,所以是紧凑储存。
- zlend,压缩列表末尾标志。
entry数据结构组成:
- prevlen,上一个entry占用了多少个字节,当上一个entry小于254字节的时候prevlen占用一个字节,当上一个entry大于等于254字节的时候prevlen占用五个字节,该字段主要是列表倒叙遍历的时候需要用到。
- encoding,元素类型编码。
- content[],元素内容。
级联更新
在压缩列表结构里面我们说到entry里面有一个字段是记录上一个entry占用的字节数的,该字段会根据上一个entry的大小去调整prevlen字段占用内存大小,所以可能会出现级联更新的情况,接下来我们看下需要级联更新的两种情况。
第一种情况:
如上图所示,如果之前e2-e5都占用253bytes,这个时候e2-e5内部的prevlen则都是占用1bytes的,这个时候新进来一个e1,这个entry占用255bytes,那e2的prevlen则需要从原来的1bytes --> 5bytes,那么e2的entry占用大小则由原来的253 --> 257bytes,这个时候e3又需要重新分配内存了,依次内推那么需要一直更新到e5,这就是级联更新情况。
第二种情况:
如上图所示,如果之前e2-e5都占用253bytes,e1则占用255bytes,这个时候e3-e5内部的prevlen则都是占用1bytes的,e2内部的prevlen占用5bytes,这个时候我们将e2删除,那么e3内部的prevlen则需要从原来的1bytes --> 5bytes,那么e3占用的内存大小将会由原来的253bytes --> 257bytes,这个时候e4则也需要重新分配内存,一次内推一直需要更新到e5。
注意:由上述几个情况我们可以看出级联更新是非常消耗性能,但是级联更新触发条件也是较为极端,他需要多个连续、长度恰好介于250-253之间的节点,级联更新才会有可能被触发,这种情况在实际坏境中也很少能遇见。
紧凑列表-listpack
redis5.0版本引入了一个新的数据结构-listpack,这是一个紧凑型数据结构,该数据结构与压缩列表区别不是很大,里面有两点不太一样。
- 一是紧凑列表去掉了zltail_offset。
- 二是将entry内部的prevlen去掉了,然后新增了一个length字段放在entry结构末尾处 ,该字段表示当前entry的长度,并且该字段用变长字段varint修饰,改变了之前的不是1bytes就是5bytes的设计思路。
redis5.0版本中紧凑列表并未替代压缩列表,只是压缩列表的优化版本,目前只在stream中被使用到,并未替代压缩列表。