vlambda博客
学习文章列表

Redis-ziplist压缩列表底层实现

小闭环,正反馈,持续输出第 11/10天 | 深度 | 实践


面粉到了之后,大哥首次尝试的自制面条还是相当成功的。


下面步入正题。

概览

前文提到,Redis 在有序集合和哈希的数据规模较小时,底层会采用 ziplist 压缩列表进行数据存储。

当有序集合的数据规模满足以下条件时,会使用 ziplist 作为底层数据结构;

Redis-ziplist压缩列表底层实现

当哈希的数据规模满足以下条件时,,会使用 ziplist 作为底层数据结构;

Redis-ziplist压缩列表底层实现

可以在 redis.conf 配置文件中配置该属性。

压缩列表作为 Redis 底层核心的数据结构,重点在于如何节省内存空间,以及提升内存操作效率,下文会详细说明它的实现原理。

原理

压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构,有点类似 java 中 ArrayList 的底层数组实现,能够很好地利用空间局部性提升内存访问效率。

另外它通过对数据的动态编码,能够很大程度节省内存使用,这对 Redis 来说非常重要。

压缩列表整体的数据结构如下:Redis-ziplist压缩列表底层实现

zlbytes,长度为 4 字节,记录整个压缩列表占用的内存字节数,在进行内存重分配或者计算 zlend 位置时使用zltail,长度为 4 字节,记录压缩列表尾节点距离起始位置的偏移字节数,可以通过它快速定位到尾结点,而无需遍历列表zllen,长度为 2字节,记录了列表包含的节点数量,但需要注意的是当节点数量大于 65535 时,节点数量需要遍历整个列表才能计算得到entry,长度不定,列表中的节点,长度由节点中保存的数据决定zlend,长度 1 字节,特殊字符标识列表结尾

节点构成

前面提到压缩列表节点的长度是不定的,因为它可以根据具体存储的内容,动态地调整其占用的空间大小。

节点可以保存一个字节数组或者一个整数值,其中字节数组可以是以下长度的其中一种:

长度小于等于63(2^6-1)字节的字节数组长度小于等于16383(2^14–1)字节的字节数组长度小于等于4294967295(2^32–1)字节的字节数组

整数值则可以是以下六种长度的其中一种:

4位长,介于0至12之间的无符号整数1字节长的有符号整数3字节长的有符号整数int16_t类型整数int32_t类型整数int64_t类型整数

节点的数据结构如下:

Redis-ziplist压缩列表底层实现

previous_entry_length属性记录了前一个节点的长度:

如果前一个节点长度 < 254 字节,previous_entry_length 属性长度为 1 字节表示如果前一个节点长度 >= 254 字节,则 previous_entry_length 属性长度为 1 字节表示

encoding属性记录了节点的content属性所保存数据的类型以及长度:

encodings属性为一字节长,值的最高位以11开头的是整数编码,即 content存储的是整数内容encodings属性为一字节、两字节或者五字节长,值的最高位为00、01或者10的是字节数组编码,即content存储的是字符内容;数组的长度由编码除去最高两位之后的其他位记录

content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

举个例子:

连锁更新

前面提到节点的 previous_entry_length 属性是变长的,会根据前一个节点的长度动态变化。

而连锁更新指的是极端情况下,由于节点的插入或者删除操作,导致列表中节点的前驱节点发生变化,进而影响 previous_entry_length 属性发生变化,最终导致多个节点长度发生变化的连锁反应。

因节点长度发生变化时需要执行空间的重分配,因此如果大面积发生连锁更新,对于 Redis 服务器的整体性能会有一定影响。

当然,尽管连锁更新的复杂度较高,但真正造成性能问题的概率还是很低的,原因如下:

首先,压缩列表里要恰好有多个连续的、长度介于250字节至253字节之间的节点,连锁更新才有可能被引发,在实际中,这种情况并不多见(如果实际真的存在,应尽量避免)其次,即使出现连锁更新,但只要被更新的节点数量不多,就不会对性能造成任何影响,比如说,对三五个节点进行连锁更新是绝对不会影响性能的

因此实际的使用场景中,基本不必担心压缩列表的性能问题。




推荐阅读