-- 《Redis设计与实现》
ziplist .c
数据结构概述说明:
- ziplist是 列表list 或 hash 的底层实现之一,只有数据量小的时候才会用,举个例子如下:
-- hash结构:键值均<64字节 && 键值对数量 < 512
- 目的是为了节省内存,在数据量少的时候就省内存。由一系列特殊编码连续内存组成的顺序性数据结构
- 时间复杂度O(N),只有极端情况下会造成O(N^2) // 比如插入或删除极端情况导致的连锁更新
-- 比如原本用一个字节能存储0~254字节长度,但是这时候将一个长度大于254字节的插入,这时候就会导致所有都要重新分配。
- 可保存多个节点,节点数据结构可以是字节数组 或 整数值
其核心结如下:
占用的内存字节数。进行内存 重分配或计算 zlend位置用到。
压缩列起始地址到列表尾结点偏移量。举个例子:zlbyte地址为p,zltail=60,那entryN的地址(p+60)
列表节点数量
- entryX:长度不定。列表对应的节点
标记压缩列表尾端
其节点entry结构如下 :
typedef struct zlentry { //
保存
ziplist
节点信息的结构
unsigned int prevrawlensize; //
前置节点的长度
unsigned int prevrawlen; //
编码
prevrawlen
所需的字节大小
unsigned int lensize; //
编码
len
所需的字节大小
unsigned int len; //
当前节点值的长度
unsigned int headersize; //
当前节点
header
的大小
,
等于
prevrawlensize + lensize
unsigned char encoding; //
当前节点值所使用的编码类型
unsigned char *p; //
指向当前节点的指针
} zlentry;
content部分:
每个节点可以保存一个字节数组或整数值(content部分)
- 字节数组长度有如下三种
-- 长度 <= 63(2^6)-1
-- 长度 <= 16383(2^14)-1
-- 长度 <= 4294967295(2^32)-1
- 整数值有如下六种
-- 4bit(0~12无符号整数)、1字节、3字节、int16_t整数、int32_t整数、int64_t整数
==========================================================================
previous_entry_length部分:
其中previous_entry_length(下面会简写为 prevLen )记录了前一个节点的长度情况
- 分为如下两种情况
-- 前一entry节点长度 < 254字节:prevLen=1字节,前一节点长度保存在这1字节里。举例如下:
---- 0x05表示0000 0101,说明上一节点长度=5字节
-- 前一entry字节长度 >= 254字节:prevLen=5字节,第一个字节设置为0xFE,后面4个节点保存前一entry节点长度。举例如下:
---- 0xFE 00 00 27 66,0xFE表示前一节点>=254字节,00 00 27 66=10086字节表示前一节点为10086字节长
如何从尾节点遍历到头节点?步骤如下
- 1 假设zlbytes起始地址为p,zltail为60,有三个entry节点。那最后一个entry3节点起始地址=p+60 = p3。
- 2 由于prevLen记录上一个节点长度,那entry2 = p3 - prevLen3记录前一节点的长度 = p2
- 3 那entry1 = p2 - prevLen3记录前一节点的长度 = p1
以上就从尾遍历到头节点
===================================================================
encoding部分:
记录entry节点中content长度、数据类型。含两种数据结构 字节数组、整数如下:
- 字节数组:1字节-00开头,2字节-01开头,5字节-10开头
- 整数:均是1字节-11xxxxxx开头
==========================================================================
content部分:
保存节点的值,可以是字节数组 或 整数
- 字节数组:00001011,其中00表示长度小于63字节数组,001011=11长度
- 整数:11000000,其中11表示保存int16_t整数
什么是压缩列表连锁更新?
什么情况下会触发连锁更新?
添加或删除节点都可能会导致连锁更新,不过可能性都比较小
举例:添加可能导致的连锁更新是如何发生的?
有连续多个长度介于250~253字节的e1~eN节点。
这时候在头节点插入一个大于254字节的新节点。由于e1长度<1字节,所以他的previous_entry_length只有1字节,是无法记录前一个节点的大小的,这时候prevLen会扩容成5个字节(就够存储大于254字节的前一个节点了)。而后面e2~eN都是这种情况,这时候都需要更新,就会导致连锁更新的情况了。
为什么是250~253字节之间了??
因为 250 + (5 - 1) = 254 >= 254,其实就是刚好上面e1扩容了4个字节,导致e2的prevLen无法存储e1的长度,从而导致e2也要更新。
总结
- 连锁更新复杂度O(n^2),但出现几率低,并且出现了因为压缩列表不长所以不影响(平时几个节点连锁更新也不影响性能)
- 连锁更新触发条件:多个连续长度介于250~253字节之间