目录

压缩列表(ziplist)

1 压缩列表结构

2 压缩列表节点结构

3 encoding编码类型

1) 字节数组类型

2) 整数类型

4 压缩列表示意图

5 连锁更新现象

压缩列表(ziplist)

压缩列表(ziplist)是redis 为了节约内存而开发的,由连续内存块组成的顺序型数据结构,适用于长度较小的值

存取的效率高,内存占用小,但由于内存是连续的,在修改的时候要重新分配内存

同时满足以下两个条件时,使用ziplist:

  1) 元素长度都小于64Byte

  2) 元素数量小于512个

1 压缩列表结构

struct ziplist<T> {
    int32 zlbytes;
    int32 zltail_offset;
    int16 zllength;
    T[] entries;
    int8 zlend;
}

其中:

zlbytes:整个压缩列表占用的字节数,占4Byte
zltail_offset:最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个元素,占4Byte
zllength:压缩列表的元素个数,占2Byte
entries:压缩列表的元素,可以包含多个节点,每个节点可以保存一个字节数组或者一个整数值
zlend:压缩列表结束标志,值等于 0xFF,占1Byte

2 压缩列表节点结构

typedef struct zlentry { 
    unsigned int prevrawlensize, prevrawlen;
    unsigned int lensize, len;
    unsigned int headersize;
    unsigned char encoding;
    unsigned char *p;
} zlentry;

其中:

prevrawlen:前一个节点的长度
prevrawlensize:存储前一个节点长度(prevrawlen属性)所需的字节数
len:当前节点长度
lensize:储当前节点长度(len属性)所需的字节数
headersize:当前节点的header大小
encoding:节点的编码方式
p:指向节点的指针

虽然redis定义了节点zlentry结构体,但是redis却没有用zlentry结构来存储节点,因为,这个结构存小整数或短字符串太浪费空间

zlentry结构体在32位系统占用28Byte,在64位系统占用32Byte,这不符合压缩列表提高内存利用率的设计目的,因此,在redis中,并没有使用zlentry结构,而是定义了宏来表示压缩列表的节点

压缩列表的节点真正的结构如下图所示:

redisinsight压缩包 redis的压缩列表_redis

 其中:

 previous_entry_length:前一个节点的长度,占1Byte或5Byte

  如果前一个节点的长度小于254Byte,则需要1Byte来保存前一个节点的长度

  如果前一个节点的长度大于等于254Byte,则需要5Byte来保存前一个节点的长度,第一个Byte固定为0xfe(254),后四个Byte表示前一个节点的长度

encoding:编码类型(字节数组,整数),保存了content的数据类型和长度,占用1Byte、2Byte或者5Byte

content:节点数据,节点数据类型和长度由encoding决定

当前entry的总字节数 = 下一个entry的previous_entry_length的值 = previous_entry_length字节数 + encoding字节数 + content字节数

3 encoding编码类型

1) 字节数组类型

encoding

encoding长度

content字节数组长度

说明

00xxxxxx

1Byte

小于等于63(2^6-1)Byte

encoding的第一个字节最高两位是00,剩余的6位用来表示字节数组的长度

01xxxxxx|xxxxxxxx

2Byte

小于等于16383(2^14-1)Byte

encoding的第一个字节最高两位是01,剩余的14位用来表示字节数组的长度

10xxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx

5Byte

小于等于4294967295(2^32-1)Byte

encoding的第一个字节最高两位是10,剩余的4Byte共32位(第一个字节剩余的6位舍弃)用来表示字节数组的长度

保存"redis"和"ab"两个字符串的示意图

其中:

字符串"redis"占7Byte = (previous_entry_length = 1Byte)  + (encoding = 1Byte) + (content = 5Byte)

字符串"ab"占4Byte = (previous_entry_length = 1Byte) + (encoding = 1Byte) + (content = 2Byte)

redisinsight压缩包 redis的压缩列表_redisinsight压缩包_02

2) 整数类型

encoding

encoding长度

content整数类型

说明

11000000

1Byte

2Byte的int16_t类型, -2^15~2^15-1

encoding的值恒为0xC0

11010000

1Byte

4Byte的int32_t类型,-2^31~2^31-1

encoding的值恒为0xD0

11100000

1Byte

8Byte的int64_t类型,-2^63~2^63-1

encoding的值恒为0xE0

11110000

1Byte

3Byte的整数,-2^23~2^23-1

encoding的值恒为0xF0

11111110

1Byte

1Byte的整数,-2^6~2^6-1

encoding的值恒为0xFE

1111xxxx

1Byte

 无content字段

xxxx只能取0001~1101,表示0~12的整数,4位之和减去1表示真正的整数,此时不需要content

11111111

1Byte

  无content字段

ziplist结束标志

保存2,10,1024三个整数的示意图

2,10的content不占用空间,1024的content占2字节

其中:

整数2占2Byte = (previous_entry_length = 1Byte)  + (encoding = 1Byte) + (content = 0Byte)

整数10占2Byte = (previous_entry_length = 1Byte)  + (encoding = 1Byte) + (content = 0Byte)

整数1024占4Byte = (previous_entry_length = 1Byte)  + (encoding = 1Byte) + (content = 2Byte)

redisinsight压缩包 redis的压缩列表_redis_03

4 压缩列表示意图

存有字符串"redis"和整数2共2个节点的ziplist示意图如下:

type = REDIS_LIST 或 REDIS_ZSET

redisinsight压缩包 redis的压缩列表_数据结构_04

5 连锁更新现象

previous_entry_length 记录了上一个entry 的长度,极端情况下: 如果每个entry的长度都是250~253Byte,那么如果在头部插一个节点的长度大于254Byte的entry节点,那么后一个节点的previous_entry_length值就从1Byte变为 5Byte,那么后一个entry节点的长度就大于了254Byte,再后面一个entry节点的previous_entry_length值也需要更新,引发连锁反应,后面所有的entry节点变大,previous_entry_length也变大,此时会频繁的进行数据迁移,申请内存,销毁动作,使性能受到很大影响

连锁更新在最坏情况下需要对压缩列表执行 N 次空间重分配操作,而每次空间重分配的最坏复杂度为 O(N),所以连锁更新的最坏复杂度为 O(N^2) 

尽管连锁更新的复杂度较高,但它触发的概率很低