文章目录
- 1. 引言
- 2. redis 源码下载
- 3. zipList 数据结构
- 3.1 整体
- 3.2 entry 数据结构分析
- 3.3 连锁更新
- 4. 参考
1. 引言
前情提要:
《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
《redis 从0到1完整学习 (六):Hash 表数据结构》
本文主要结合源码来介绍 ZipList 的数据结构
2. redis 源码下载
Redis 源码可以点击这里下载,方便查看其中定义的一些数据结构。
3. zipList 数据结构
3.1 整体
zipList 是为了提高存储效率而设计的一种特殊编码的双向链表,由一系列特殊编码的连续内存块组成。可以在任意一端进行 push 和 pop 操作,并且该操作的时间复杂度为 O(1)。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。
zipList 数据结构示意图:
名称 | 类型 | size | 用途 |
---|---|---|---|
zlbytes | uint32_t | 4 字节 | 记录整个 zipList 占用的字节总数,即图中 zlbytes 的起始 到 zlend 的末尾 |
zltail | uint32_t | 4 字节 | 记录 zipList 尾节点距离起始地址有多少字节,即图中 zlbytes 起始 到 tail 起始的偏移量,通过这个偏移量,可以确定尾节点的地址 |
zllen | uint16_t | 2 字节 | 记录了压缩列表包含的节点数量。 最大值为 UINT16_MAX (65534)。如果超过这个值,会记录为65535。 |
entry | 不定 | 压缩列表包含的各个节点,节点的长度由节点保存的内容决定。 | |
zlend | uint8_t | 1 字节 | 特殊值 0xFF (十进制 255 ),用于标记压缩列表的末端。 |
3.2 entry 数据结构分析
ziplist 中的每个 entry 都以包含两条信息的元数据作为前缀。首先,存储上一个 entry 的长度,以便能够从后向前遍历列表。其次,提供 entry encoding,表示 entry 类型,整数或字符串,如果是字符串,它还表示字符串有效负载的长度。因此,完整的 entry 存储如下:
- prevlen:前一个 entry 的长度,占1个或5个字节。
- 如果前一个 entry 的长度 < 254字节,则采用1个字节来保存这个长度值
- 如果前一个 entry 的长度 > 254字节,则采用5个字节来保存这个长度值,第一个字节为 0xfe,后四个字节才是真实长度数据
- encoding:编码属性,记录数据类型(字符串还是整数)以及长度,占用1个、2个或5个字节
- entry-data:负责保存 entry 数据,可以是字符串或整数
根据 redis 源码的注解来看,encoding 的编码如下:
(1)字符串编码
encoding 格式 | 编码长度 | 字符串大小 |
---|---|---|
|00pppppp| | 1 bytes | <= 63 bytes |
|01pppppp|qqqqqqqq| | 2 bytes | <= 16383 bytes |
|10000000|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt| | 5 bytes | <= 4294967295 bytes |
例如,下面保存 ab
、bc
字符串示例:
* [13 00 00 00] [0e 00 00 00] [02 00] [00 02 61 62] [04 02 62 63] [ff]
* | | | | | |
* zlbytes zltail zllen "ab" "bc" end
*
a
的 ASCII 码是97,对应的16进制是61
;
b
的 ASCII 码是98,对应的16进制是62
;
c
的 ASCII 码是99,对应的16进制是63
;
ab
的编码是 6162
,长度是2字节,所以 encoding 是 00000010
即 16进制02
,而前一个 entry 不存在,因而 prevlen 为 0,所以 ab
编码为 00026162
;同样的 bc
前一个 entry 为 ab
,字节一共为4,所以 bc
编码为 04026263
。
(2)数字编码
encoding 格式 | 编码长度 | 整数类型 |
---|---|---|
11000000 | 1 | int16_t(2 bytes) |
11010000 | 1 | int32_t(4 bytes) |
11100000 | 1 | int64_t(8 bytes) |
11110000 | 1 | 24位有符整数(3 bytes) |
11111110 | 1 | 8位有符整数(1 bytes) |
1111xxxx | 1 | 在xxxx位置保存数值,范围从0001~1101,减1后结果为实际值 |
例如,下面是 redis 源码给出的保存 2
、5
整数示例:
* [0f 00 00 00] [0c 00 00 00] [02 00] [00 f3] [02 f6] [ff]
* | | | | | |
* zlbytes zltail zllen "2" "5" end
*
2
的长度在 0001~1101
,对应上面表格的最后一种,所以 encoding 为 11110011
,即16进制的 f3
(这里的3,实际为2+1);同样 5
encoding 为 11110110
,即 f6
3.3 连锁更新
zipList 的每个 entry 都包含 prevlen 来记录上一个节点的大小,长度是1个或5个字节:
- 如果前一节点的长度 < 254字节,则采用1个字节来保存这个长度值
- 如果前一节点的长度 >= 254字节,则采用5个字节来保存这个长度值,第一个字节为0xfe,后四个字节才是真实长度数据
当一种边界场景下,插入了一个 entry,entry 的大小超过了 254 字节,导致后续大范围的 entry 的 prevlen 由1字节转为用5个字节来保存,触发了连锁更新,同时删除也可能触发连锁更新。
4. 参考
《redis 从0到1完整学习 (一):安装&初识 redis》
《redis 从0到1完整学习 (二):redis 常用命令》
《redis 从0到1完整学习 (三):redis 数据结构》
《redis 从0到1完整学习 (四):字符串 SDS 数据结构》
《redis 从0到1完整学习 (五):集合 IntSet 数据结构》
《redis 从0到1完整学习 (六):Hash 表数据结构》