Redis/压缩列表
- 压缩列表
- 压缩列表的构成
- 压缩列表节点的构成
- previous_entry_length
- encoding
- content
- 连锁更新
- 压缩列表API
压缩列表
压缩列表(ziplist)是列表键和哈希键的底层实现之一。
当一个列表键只包含少量列表项,且每个列表项是小整数值或者长度较短的字符串时,Redis就会使用压缩列表来做列表键的底层实现
当一个哈希键只包含少量键值对,且每个键值对的键和值是小整数值或者长度较短的字符串时,Redis使用压缩列表来做哈希键的底层实现
压缩列表的构成
压缩列表是为了节约内存开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。
一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值
压缩列表的各个组成部分:
属性 | 类型 | 长度 | 用途 |
zlbytes | uint32_t | 4字节 | 记录整个压缩列表占用的内存字节数。对压缩列表内存重分配或者计算zlend的位置时使用 |
zltail | uint32_t | 4字节 | 记录表尾节点距离起始地址有多少个字节 |
zllen | uint16_t | 2字节 | 记录压缩列表包含的节点数量,当这个值等于UINT16_MAX时,节点数量需要遍历整个压缩列表才能得出 |
entryX | 列表节点 | 不定 | 压缩列表的各个节点,长度由节点保存的内容决定 |
zlend | uint8_t | 1字节 | 特殊值0xFF,用于标记压缩列表末端 |
压缩列表节点的构成
压缩列表节点可以保存一个字节数组或一个整数值。
字节数组可以保存3种长度中的1种
- 长度小于等于2^6 - 1字节
- 长度小于等于2^14 - 1字节
- 长度小于等于2^32 -1字节
整数值可以是6种长度中的一种
- 4位长,0-12的无符号整数
- 1字节长 有符号整数
- 3字节长 有符号整数
- int16_t
- int32_t
- int64_t
每个压缩节点由previous_entry_length
、encoding
、content
三部分组成
previous_entry_length
以字节为单位,记录了压缩列表中前一个节点的长度
本身的长度可以是1字节或者5字节
- 如果前一个节点长度小于254字节,那么该属性长度为1字节,前一字节的长度保存在这个字节里面
- 如果前一个节点长度大于等于254字节,那么该属性长度为5字节。属性的第一字节会被设置为0xFE,之后的4个字节用来存储前一个节点的长度
通过previous_entry_length
,可以很方便地通过节点自身的地址推算出前一个节点的地址
encoding
encoding
记录了节点的content
属性所保存数据的类型和长度
- 1字节、2字节、5字节长,值最高位为00、01或者10的是字节数组编码,表示
content
保存着字节数组,数组的长度由编码除去最高两位之后的其他位记录
- 1字节长,值最高位以11开头的是整数编码,表示保存了整数值,整数值的类型和长度由编码除去最高两位之后的其他位记录
content
content
负责保存节点的值
连锁更新
产生的原因是previous_entry_length
属性记录了前一个节点的长度。
假设e1 - eN 这N个节点的previous_entry_length
都是一个字节长且本身长度在250-253字节之间,此时在e1前方插入一个长度不小于254字节的节点,此时e1的previous_entry_length
就需要更新为5字节,此时e1又将不小于254字节,因此也需要更新e2…直到不需要更新为止,这就是连锁更新
删除节点也可能导致连锁更新
每次更新节点都将产生内存重分配,且最坏情况下连锁更新的复杂度为O(N^2)
但是连锁更新造成性能问题的几率很低:
- 要有多个连续的且长度在250-253字节之间的节点才可能引发连锁更新,实际中这种情况并不常见
- 连锁更新时,只要被更新的节点数不多,也不会对性能有所影响
压缩列表API