压缩列表(ziplist)是列表和哈希键的底层实现之一。
1 压缩列表的构成
压缩列表是Redis节约成本而开发,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或一个整数值。
下图表示压缩的各个组成部分及含义
下图表示一个包含三个节点的压缩列表,列表的起始地址的指针为p,那么列表表尾节点的地址就是p加上zltail的值,即p+60。
包含是三个节点的压缩列表
2 压缩列表节点的构成
每个压缩列表节点可以保存一个字节数组或者一个整数值。下图表示压缩列表的组成部分及含义。
压缩列表节点各个组成部分和含义
2.1 previous_entry_length
节点的previous_entry_length属性以字节为单位,长度为1字节或5字节,记录压缩列表前一个节点的长度。
(1) 如果前一个节点的长度小于254字节,那么previous_entry_length属性长度为1字节。
(2) 如果前一个节点的长度大于等于254字节,那么previous_entry_length属性长度为5字节。
因为节点的previous_entry_length记录了前一个节点的长度,所以程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址。压缩列表的从表尾向表头遍历操作就是通过这一原理实现的。只要拥有了一个指向某个节点起始地址的指针,就可以通过这个指针和这个节点的previous_entry_length属性,一个一个向前遍历,最终达到列表的表头节点。
2.2 encoding
节点的encoding属性记录了节点的content属性所保存数据的类型和长度:
(1) 如果是一个字节长,值的最高位是11开头的是整数编码,表示content保存的是整数值。
(2) 如果是一个字节、两个字节或者五个字节长,值的最高位为00、01或10的是字节数组编码,表示content保存的是字节数组。
2.3 content
节点的content属性负责保存的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。
如下图,表示一个保存字节数组的列表节点,encoding属性的最高两个二进制位00表示节点保存的是一个字节数组。后六个二进制位001011表示字节数组的长度,即“hello world”的长度。
一个保存字节数组的列表节点
如下图,表示一个保存整数值的列表节点,encoding属性的最高两个二进制位11表示节点保存的是一个整数值,content属性保存节点的值10086。
一个保存整数值的列表节点
3 连锁更新
前面说过,previous_entry_length属性的长度是1个字节或5个字节,如果前一个节点的长度小于254字节,就使用1个字节保存这个长度值,反之使用5个字节保存这个长度值。
假设在压缩列表中,有多个连续的、长度介于250字节到253字节之间的节点e1至eN,如下图所示:
由于每个节点的长度均小于254,所以每个节点的previous_entry_length属性所需的长度都是1个字节。
此时,在表头插入一个新节点,这个节点的长度大于254,由于 e1的previous_entry_length属性仅长1字节,没有办法保存新节点的长度,所以长须需要将e1节点的previous_entry_length属性从原来的1个字节扩展为5个字节长。
由于e1的previous_entry_length属性添加了4个字节,导致e1节点的长度也大于254,所以导致e2节点previous_entry_length属性也要从原来的1个字节扩展为5个字节长...直到eN为止。
Redis在特殊情况下产生连续多次空间扩展操作称之为“连锁更新”(cascade update),下图表示了这一过程。
连锁更新过程
除了新添加节点会造成连锁跟新之外,删除节点也可能引发连锁更新。
如下图所示的压缩列表,e1至eN都是大小介于250字节到253字节的节点,big节点时长度大于254的节点,small节点也是小于254字节的节点。small节点使用5个字节保存big节点的长度,如果此时将small节点删除,那么需要e1节点保存big节点的长度,但是e1节点的previous_entry_length属性是1个字节,需要扩展...,导致的情况和插入的情况一致。
删除节点引发的连锁更新
因为连锁更新在最坏的情况下需要对压缩列表执行N次空间重分配操作,而每次空间分配的最坏复杂度为O(N),所以连锁更新的最坏复杂度为O(N2)。
但是,要造成连锁更新的可能性非常小。首先需要多个连续的、长度均介于250字节至253字节的节点,这种情况实际上并不多见。同时,即使出现了连锁更新,但是只要被更新的节点数量不多,就不会对性能造成任何影响。
4 小结
(1) 压缩列表是一种为节约内存而开发的顺序型数据结构,被作为列表键和哈希键的底层实现。
(2) 压缩节点可以包含任意个节点,每个节点可以保存一个字节数据或整数值。
(3) 添加新节点到压缩列表,或者从压缩列表中删除节点,可能引发连锁更新操作,但是这操作出现的几率并不高。
本文完
注:本文参考《Redis设计与实现》,如发现错误,请指正!
网友评论