定义
压缩列表ziplist是Redis中列表和哈希键的底层实现方式之一。
当一个列表只包含少量列表项,并且每个列表项要么是小整数值,要么是较短的字符串时,那么Redis就会使用压缩列表来作为列表的底层实现。
另外,当一个哈希表中只包含少量键值对时,并且每个每个键值对的键key和值value要么是小整数值,要么是短的字符串时,也会使用压缩列表作为实现方式
压缩列表是Redis为了节约内存而设计的,由一系列特殊编码的连续内存组成的顺序型的数据结构,一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值。
压缩列表类似于数组,在内存中是连续的,每个节点用来存储数组,不过有一点不同是,压缩列表允许每个节点的空间大小是可以不相同。
数组在内存中的分布是:
压缩列表可以看做是对数组的压缩,压缩列表在内存中的结构是这样的:
压缩列表节点
压缩列表中的节点才是真正存储数据的,但是压缩列表不是数组,数组中每个元素占用的空间大小是固定的,因为可以随机访问;但是压缩列表中每个节点的大小是不固定的,怎么才能知道从哪到哪是一个节点的内容呢?
这就需要看节点的内存结构的设计了,每个节点可以保存一个字节数组或者一个整数值。
其中字节数组的长度可以是以下三种长度之一:
- 长度小于等于63(2^6 - 1)字节的字节数组
- 长度小于等于16383(2^14 - 1)字节的字节数组
- 长度小于等于(2^32 - 1)字节的字节数组
而整数值可以是: - 4位长的无符号整数
- 1字节、3字节、4字节、8字节的整数
压缩列表节点的内存结构是:
previous_entry_length记录了前一个节点的长度,通过这个属性,我们根据当前节点的起始地址就能计算出前一个节点的起始地址,从而就可以遍历整个压缩列表,压缩列表的遍历是从表尾到表头,就是基于这一个原理实现的。 - 如果前一个节点的长度小于等于254字节,那么previous_entry_length属性占用1字节就可以。
- 如果前一个字节的长度大于254字节,那么previous_entry_length需要用5字节来保存前一个节点的长度。
encoding属性记录了节点内容content属性所保存的数据类型以及长度,具体的数据类型及长度,了解即可。
content保存中真实的数据内容。
连锁更新
考虑这样一种特殊情况:有一个压缩列表,每个节点的大小都是在250~254之间,因此节点记录的上一个节点的长度是小于254字节的,所以previous_entry_length只需要一个字节就能记录,但是当在此节点前插入一个大于254字节的节点时,麻烦就要产生了,因为此时当前节点来说,一个字节无法记录前驱节点的长度,需要5字节才可以。此时当前节点就要发生扩容操作,同时后续的节点的previous_entry_length也要发生变化,由于在内存中每个节点是连续的,所以后续的每个节点都要重新分配空间。
当在列表内部或头部,插入或删除一个节点时,可能会影响到后续节点的空间大小,同时还需要将后续的每个节点重新分配空间。此时,程序会从受影响的这个节点开始,为这个节点扩容,然后继续下一个节点,逐个扩容,直到最后一个节点为止。
这种在特殊情况下产生的连续多次空间扩展操作称之为“连锁更新”。
连锁更新在最坏的情况下的时间复杂度是O(N),连锁更新十分损耗性能。
- 在一般情况下,不会发生连锁更新
- 其次,即使出现了连锁更新,只要被更新的节点数量不多,也不会性能造成影响
参考文章
- 《Redis设计与实现》
- Redis数据结构——压缩列表 - 随心所于 - 博客园