Redis基础数据结构之 ziplist 压缩列表 源码解读

news2024/11/15 9:54:50

目录标题

  • ziplist 是什么?
  • ziplist 特点
  • ziplist 数据结构
    • ziplist 节点
    • pre_entry_length
    • encoding 和 length
    • content
  • ziplist 基本操作
    • 插入(Insertion)
    • 删除(Deletion)
    • 查找(Search)
    • 更新(Update)
    • 示例

ziplist 是什么?

压缩列表,内存紧凑的数据结构,占用一块连续的内存空间。一个 ziplist 可以包含多个节点(entry), 每个节点可以保存一个长度受限的字符数组(不以 \0 结尾的 char 数组)或者整数, 包括:

  • 字符数组
    • 长度小于等于 63 (2^6-1)字节的字符数组
    • 长度小于等于 16383 (12^14-1) 字节的字符数组
    • 长度小于等于 4294967295 (2^32-1)字节的字符数组
  • 整数
    • 4 位长,介于 0 至 12 之间的无符号整数
    • 1 字节长,有符号整数
    • 3 字节长,有符号整数
    • int16_t 类型整数
    • int32_t 类型整数
    • int64_t 类型整数
    • Redis 哪些数据结构使用了 ziplist?
    • 哈希键
    • 列表键
    • 有序集合键

ziplist 特点

  • 优点
    • 节省内存
  • 缺点
    • 不能保存过多的元素,否则访问性能会下降
    • 不能保存过大的元素,否则容易导致内存重新分配,甚至引起连锁更新

ziplist 数据结构

// ziplist 中的元素,是 string 或者 integer
typedef struct {
    // 如果元素是 string,slen 就表示长度
    unsigned char *sval;
    unsigned int slen;
    // 如果是 integer,sval 是 NULL,lval 就是 integer 的值
    long long lval;
} ziplistEntry;

在这里插入图片描述

为了方便地取出 ziplist 的各个域以及一些指针地址, ziplist 模块定义了以下宏:

// 取出 zlbytes 的值
#define ZIPLIST_BYTES(zl)       (*((uint32_t*)(zl)))

// 取出 zltail 的值
#define ZIPLIST_TAIL_OFFSET(zl) (*((uint32_t*)((zl)+sizeof(uint32_t))))

// 取出 zllen 的值
#define ZIPLIST_LENGTH(zl)      (*((uint16_t*)((zl)+sizeof(uint32_t)*2)))

// 返回 ziplist header 部分的长度,总是固定的 10 字节
#define ZIPLIST_HEADER_SIZE     (sizeof(uint32_t)*2+sizeof(uint16_t))

// 返回 ziplist end 部分的长度,总是固定的 1 字节
#define ZIPLIST_END_SIZE        (sizeof(uint8_t))

// 返回到达 ziplist 第一个节点(表头)的地址
#define ZIPLIST_ENTRY_HEAD(zl)  ((zl)+ZIPLIST_HEADER_SIZE)

// 返回到达 ziplist 最后一个节点(表尾)的地址
#define ZIPLIST_ENTRY_TAIL(zl)  ((zl)+intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl)))

// 返回 ziplist 的末端,也即是 zlend 之前的地址
#define ZIPLIST_ENTRY_END(zl)   ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)

ziplist 节点

typedef struct zlentry {
    // 前一个节点的长度,通过这个值,可以进行指针计算,从而跳转到上一个节点
    unsigned int prevrawlen;
    unsigned int prevrawlensize;
    // entry 的编码方式
    // 1. entry 是 string,可能是 1 2 5 个字节的 header
    // 2. entry 是 integer,固定为 1 字节
    unsigned int lensize;
    // 实际 entry 的字节数
    // 1. entry 是 string,则表示 string 的长度
    // 2. entry 是 integer,则根据数值范围,可能是 1, 2, 3, 4, 8
    unsigned int len;
    // prevrawlensize + lensize
    unsigned int headersize;
    // ZIP_STR_* 或者 ZIP_INT_*
    unsigned char encoding;
    unsigned char *p;            /* Pointer to the very start of the entry, that
                                    is, this points to prev-entry-len field. */
} zlentry;

在这里插入图片描述

pre_entry_length

记录前一个节点的长度。通过这个值,可以进行指针计算,从而跳转到上一个节点。


area        |<---- previous entry --->|<--------------- current entry ---------------->|

size          5 bytes                   1 byte             ?          ?        ?
            +-------------------------+-----------------------------+--------+---------+
component   | ...                     | pre_entry_length | encoding | length | content |
            |                         |                  |          |        |         |
value       |                         | 0000 0101        |    ?     |   ?    |    ?    |
            +-------------------------+-----------------------------+--------+---------+
            ^                         ^
address     |                         |
            p = e - 5                 e

以上图为例,从当前节点的指针 e,减去 pre_entry_length 的值(0000 0101 的十进制值,5),就可以得到指向前一个节点的地址 p。

encoding 和 length

encoding 和 length 两部分一起决定了 content 部分所保存的数据的类型(以及长度)。

其中, encoding 域的长度为两个 bit , 它的值可以是 00 、 01 、 10 和 11 :

  • 00 、 01 和 10 表示 content 部分保存着字符数组。
  • 11 表示 content 部分保存着整数。

以 00 、 01 和 10 开头的字符数组的编码方式如下:
在这里插入图片描述
表格中的下划线 _ 表示留空,而变量 b 、 x 等则代表实际的二进制数据。为了方便阅读,多个字节之间用空格隔开。

11 开头的整数编码如下:

在这里插入图片描述

content

content 部分保存着节点的内容,类型和长度由 encoding 和 length 决定。

以下是一个保存着字符数组 hello world 的节点的例子:


area      |<---------------------- entry ----------------------->|

size        ?                  2 bit      6 bit    11 byte
          +------------------+----------+--------+---------------+
component | pre_entry_length | encoding | length | content       |
          |                  |          |        |               |
value     | ?                |    00    | 001011 | hello world   |
          +------------------+----------+--------+---------------+

在这里插入图片描述

encoding 域的值 00 表示节点保存着一个长度小于等于 63 字节的字符数组, length 域给出了这个字符数组的准确长度 —— 11 字节(的二进制 001011), content 则保存着字符数组值 hello world 本身(为了方便表示, content 部分使用字符而不是二进制表示)。

以下是另一个节点,它保存着整数 10086 :


area      |<---------------------- entry ----------------------->|

size        ?                  2 bit      6 bit    2 bytes
          +------------------+----------+--------+---------------+
component | pre_entry_length | encoding | length | content       |
          |                  |          |        |               |
value     | ?                |    11    | 000000 | 10086         |
          +------------------+----------+--------+---------------+

ziplist 基本操作

在这里插入图片描述

插入(Insertion)

在Ziplist中插入新元素时,Redis需要找到合适的位置并将新元素添加进去。由于Ziplist是连续存储的,插入操作通常会导致整个列表或部分列表的重新分配和复制,这是因为新元素的插入会改变后续元素的位置。

删除(Deletion)

删除Ziplist中的元素同样涉及到内存的重新分配和复制,因为删除后的元素位置需要被前面或后面的元素覆盖。

查找(Search)

查找特定元素时,Redis需要从Ziplist的开头开始逐个元素地遍历,直到找到目标元素为止。由于Ziplist中的每个元素都有长度信息,查找时可以快速定位到下一个元素的起始位置,但依然需要逐个元素地检查。

更新(Update)

更新Ziplist中的元素涉及到内存的重新分配。如果更新后的元素大小发生了变化,那么更新操作可能会导致该元素后面的所有元素需要重新分配和复制。

示例

假设我们有一个Ziplist,其中包含以下元素:“key1:value1”, “key2:value2”, “key3:value3”。每个元素的长度信息和实际内容是紧密相连的。

  • 插入新元素
    如果我们想要在"key2:value2"之前插入一个新的元素"key1.5:value1.5",Redis需要找到"key2:value2"的位置,并在其前面插入新元素。这意味着Redis需要重新分配内存,将"key2:value2"及其后面的元素向后移动。

  • 删除元素
    如果我们要删除"key2:value2",Redis需要找到该元素的位置,并将其后面的元素向前移动以填补空缺。

  • 更新元素
    如果我们将"key2:value2"更新为"key2:updated_value2",并且更新后的字符串长度与原字符串长度不同,Redis需要重新分配内存,并将更新后的元素替换原有元素的位置。

Ziplist在存储小数据集时非常高效,但由于其紧凑存储的特性,在进行插入、删除和更新操作时可能会涉及到内存的重新分配和复制。因此,Ziplist最适合用于内存敏感的应用场景,特别是当数据集较小且操作以读取为主时。对于大型数据集或频繁的写操作,Redis可能会选择其他更适合的数据结构。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2141539.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Qt多元素控件——QTableWidget

文章目录 QTabWidget核心属性、方法和信号使用示例 QTabWidget核心属性、方法和信号 QTableWidget表示一个表格控件&#xff0c;一个表格中包含若干行&#xff0c;每一行包含若干列。 表格中的每一个单元格&#xff0c;是一个QTableWidgetItem对象。 QTableWidget核心方法&a…

Java 每日一刊(第9期):数组

文章目录 前言什么是数组初始化数组如何访问和操作数组遍历数组多维数组数组的常见操作复制数组排序数组搜索数组 数组的长度和异常处理Arrays 工具类本期小知识 “简单是效率的灵魂。” 前言 这里是分享 Java 相关内容的专刊&#xff0c;每日一更。 本期将为大家带来以下内…

云计算和虚拟化技术 背诵

https://zhuanlan.zhihu.com/p/612215164 https://zhuanlan.zhihu.com/p/612215164 云计算是指把计算资源、存储资源、网络资源、应用软件等集合起来&#xff0c;采用虚拟化技术 &#xff0c;将这些资源池化&#xff0c;组成资源共享池&#xff0c;共享池即是“云”。 云计算…

从零开始学习Linux(12)---进程间通信(信号量与信号)

1.信号量 信号量是计算机科学中用于同步和互斥的一种抽象数据类型。在并发编程中&#xff0c;当多个进程或线程需要访问共享资源时&#xff0c;信号量用来确保资源在同一时刻只被一个进程或线程访问&#xff0c;从而避免竞争条件。 信号量通常具有以下特性&#xff1a; 整…

Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约

Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约 文章目录 Fisco Bcos 2.11.0配置console控制台2.10.0及部署调用智能合约前言版本适配一、启动FIsco Bcos区块链网络二、获取控制台文件三、配置控制台3.1 执行download_console.sh脚本3.2 拷贝控制台配置文件3.3 修…

读构建可扩展分布式系统:方法与实践06异步消息传递

1. 异步消息传递 1.1. 通信是分布式系统的基础&#xff0c;也是架构师需要纳入其系统设计的主要问题 1.2. 客户端发送请求并等待服务器响应 1.2.1. 这就是大多数分布式通信的设计方式&#xff0c;因为客户端需要得到即时响应后才能继续 1.2.2. 并非所有系统都有这个要求 1…

数据时代,职场离不开的远程控制工具

中秋了大概率是在正常放假了吧&#xff0c;如果突发遇到需要你处理的文件怎么办呢&#xff1f;其实有远程操作工具你就不用到办公室了。向日葵远程控制软件这些工具就可以帮我们远程实现控制电脑操作。如果你也有这方面需求就继续看吧&#xff0c;这次我将介绍几款我用过效果比…

Redis常见应用场景

目录 一、实现博客点赞功能 二、实现博客点赞用户列表功能 三、好友关注和取关以及求共同关注 四、实现关注推送 1、拉模式 2、推模式 3、推拉结合 四、三种模式对比 这里简单记录一下&#xff0c;没有实现方法&#xff0c;只是帮助记忆 一、实现博客点赞功能 可以通…

[NSSRound#4 SWPU]hide_and_seek-用gdb调试

看反汇编 ; __unwind { .text:0000000000001514 F3 0F 1E FA endbr64 .text:0000000000001518 55 push rbp .text:0000000000001519 48 89 E5 mov rbp, rsp .text:000000000000151C 53 …

python tkinter

基本使用 基于tkinter创建 GUI基本四步&#xff1a;窗口->组件->布局->事件 1.创建窗口对象 from tkinter import *root Tk() # 创建窗口root.mainloop() # 进入事件循环 2.创建组件 按钮文本等组件 btn Button(root) # 创建Button组件&#xff0c;使组件在…

re题(25)BUUFCTF-[GUET-CTF2019]re

BUUCTF在线评测 (buuoj.cn) 查下壳&#xff0c;是upx壳 脱一下 查看字符串&#xff0c;定位到主函数&#xff0c;也可以用ctrlE的方式找到主函数 明显&#xff0c;sub_4009AE是对flag加密的关键函数 进入sub_4009AE看一下 看到这儿有一堆大数和方程&#xff0c;我们知道要用z…

Transformer模型详细步骤

Transformer模型是nlp任务中不能绕开的学习任务&#xff0c;我将从数据开始&#xff0c;每一步骤都列举出来&#xff0c;然后对应重点的代码进行讲解 ------------------------------------------------------------------------------------------------------------- Trans…

Skytower

一、安装配置靶机 下载地址: SkyTower: 1 ~ VulnHub 下载之后解压发现是VirtualBox格式的 我们下载一个VirtualBox&#xff0c;这是官网 Downloads – Oracle VirtualBox 安装到默认路径就 打开后点击注册 选择解压后的vbox文件 然后点击左上角管理 点击导出虚拟电脑&…

PCIe进阶之TL:Request Handling Rules

1 Handling of Received TLPs 本节介绍接收到的 TLP 在数据链路层经过完整性验证之后,这些 TLP 在事务处理层时的处理方式。这些规则如下图所示: 接收侧会忽略保留字段。如果 Fmt 字段显示存在至少一个 TLP Prefix : (1)通过检查后续 DWORD 的第一个字节中的 Fmt 字段,…

两个人群填充参考(CHN100K和NARD)

分别是中国人群和东北亚人群的填充参考&#xff0c;测试了下&#xff0c;中国人群的参考注册还是相对友好的&#xff0c;没有像有些网站一样严格限制。东北亚的没有测试&#xff0c;两个数据库的特点都是包含了少数民族&#xff0c;研究朝鲜或蒙古族或其他民族的同学&#xff0…

Java 枚举 新特性

Java 枚举&#xff08;enum&#xff09;自JDK 1.5引入以来&#xff0c;随着版本的升级不断增强。本文将回顾枚举的演进&#xff0c;尤其是结合switch语句的应用&#xff0c;展示枚举如何在现代Java中变得更加灵活。 1. JDK 1.5&#xff1a;Java 枚举的诞生 在JDK 1.5之前&…

Dbt基本概念与快速入门

在过去的几年里&#xff0c;数据科学界已经慢慢地接受了以数据为中心的范式。我们不仅关注日益复杂的机器学习模型&#xff0c;还要更多地关注数据质量。这使得数据工程、分析工程领域技术和工具成为热点。dbt(数据构建工具)是一个显著改善数据工程师生活的工具。它的目的是向数…

【漏洞复现】金某云星空ERP GetImportOutData .net反序列化漏洞

免责声明&#xff1a; 本文内容旨在提供有关特定漏洞或安全漏洞的信息&#xff0c;以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步&#xff0c;并非出于任何恶意目的。阅读者应该明白&#xff0c;在利用本文提到的漏洞信息或进行相关测…

Chinese Spelling Correction as Rephrasing Language Model(AAAI2024)

Chinese Spelling Correction as Rephrasing Language Model(AAAI2024) 一&#xff0e;概述 目前最先进的方法将CSC(Chinese Spelling Correction)作为序列标注任务&#xff0c;并在句子对上微调基于bert的方法。然而&#xff0c;我们注意到在将一个字符标注为另一个字符的过…

springboot+mybatis+mysql仿百度网盘系统2.0

springbootmybatismysql仿百度网盘系统2.0 一、系统介绍二、功能展示1.用户登陆2.主页3.全部文件4.文件上传5.文件分享6.文件分类 三、其它1.其他系统实现 一、系统介绍 系统主要功能&#xff1a; 普通用户&#xff1a;用户登陆、主页、全部文件、上传文件、文件分类、文件分部…