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_lengthencodingcontent三部分组成

previous_entry_length

以字节为单位,记录了压缩列表中前一个节点的长度
本身的长度可以是1字节或者5字节

  • 如果前一个节点长度小于254字节,那么该属性长度为1字节,前一字节的长度保存在这个字节里面
  • 如果前一个节点长度大于等于254字节,那么该属性长度为5字节。属性的第一字节会被设置为0xFE,之后的4个字节用来存储前一个节点的长度

通过previous_entry_length,可以很方便地通过节点自身的地址推算出前一个节点的地址

encoding

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

  • 1字节、2字节、5字节长,值最高位为00、01或者10的是字节数组编码,表示content保存着字节数组,数组的长度由编码除去最高两位之后的其他位记录

java redis value 压缩_java redis value 压缩

java redis value 压缩_底层实现_02

  • 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

java redis value 压缩_字节数组_03