Redis ziplist源码解析

area        |<---- ziplist header ---->|<----------- entries ------------->|<-end->|size          4 bytes  4 bytes  2 bytes    ?        ?        ?        ?     1 byte+---------+--------+-------+--------+--------+--------+--------+-------+
component   | zlbytes | zltail | zllen | entry1 | entry2 |  ...   | entryN | zlend |+---------+--------+-------+--------+--------+--------+--------+-------+
            +--------------+------------+---------------+
entry       |    prevlen   |  encoding  | data          | +--------------+------------+---------------+

                       |<-前一项长度 ->|<-当前编码->|<-存储的数据  ->|

在这里插入图片描述

编码过程:

entry存储的键值对会根据数据进行转码,尝试转化为整数编码,这样可以节省更多的空间。

各个版本差异比较大,这里参考的是redis3.0版本
/** 整数编码类型*/
#define ZIP_INT_16B (0xc0 | 0<<4)
#define ZIP_INT_32B (0xc0 | 1<<4)
#define ZIP_INT_64B (0xc0 | 2<<4)
#define ZIP_INT_24B (0xc0 | 3<<4)
#define ZIP_INT_8B 0xfe/* 4 bit integer immediate encoding ** 4 位整数编码的掩码和类型*/
#define ZIP_INT_IMM_MASK 0x0f
#define ZIP_INT_IMM_MIN 0xf1    /* 11110001 */
#define ZIP_INT_IMM_MAX 0xfd    /* 11111101 */
#define ZIP_INT_IMM_VAL(v) (v & ZIP_INT_IMM_MASK)/* Check if string pointed to by 'entry' can be encoded as an integer.* Stores the integer value in 'v' and its encoding in 'encoding'. ** 检查 entry 中指向的字符串能否被编码为整数。** 如果可以的话,* 将编码后的整数保存在指针 v 的值中,并将编码的方式保存在指针 encoding 的值中。** 注意,这里的 entry 和前面代表节点的 entry 不是一个意思。** T = O(N)*/
static int zipTryEncoding(unsigned char *entry, unsigned int entrylen, long long *v, unsigned char *encoding) {long long value;// 忽略太长或太短的字符串if (entrylen >= 32 || entrylen == 0) return 0;// 尝试转换// T = O(N)if (string2ll((char*)entry,entrylen,&value)) {/* Great, the string can be encoded. Check what's the smallest* of our encoding types that can hold this value. */// 转换成功,以从小到大的顺序检查适合值 value 的编码方式if (value >= 0 && value <= 12) {*encoding = ZIP_INT_IMM_MIN+value;} else if (value >= INT8_MIN && value <= INT8_MAX) {*encoding = ZIP_INT_8B;} else if (value >= INT16_MIN && value <= INT16_MAX) {*encoding = ZIP_INT_16B;} else if (value >= INT24_MIN && value <= INT24_MAX) {*encoding = ZIP_INT_24B;} else if (value >= INT32_MIN && value <= INT32_MAX) {*encoding = ZIP_INT_32B;} else {*encoding = ZIP_INT_64B;}// 记录值到指针*v = value;// 返回转换成功标识return 1;}// 转换失败return 0;
}

缺点:

查找性能差

查找一块连续内存区域,当储存的元素过多时,需要从头到尾去遍历,每一个entry都需要编码过程,十分耗时

/* Find pointer to the entry equal to the specified entry. * * 寻找节点值和 vstr 相等的列表节点,并返回该节点的指针。* * Skip 'skip' entries between every comparison. ** 每次比对之前都跳过 skip 个节点。** Returns NULL when the field could not be found. ** 如果找不到相应的节点,则返回 NULL 。** T = O(N^2)*/
unsigned char *ziplistFind(unsigned char *p, unsigned char *vstr, unsigned int vlen, unsigned int skip) {int skipcnt = 0;unsigned char vencoding = 0;long long vll = 0;// 只要未到达列表末端,就一直迭代// T = O(N^2)while (p[0] != ZIP_END) {unsigned int prevlensize, encoding, lensize, len;unsigned char *q;ZIP_DECODE_PREVLENSIZE(p, prevlensize);ZIP_DECODE_LENGTH(p + prevlensize, encoding, lensize, len);q = p + prevlensize + lensize;if (skipcnt == 0) {/* Compare current entry with specified entry */// 对比字符串值// T = O(N)if (ZIP_IS_STR(encoding)) {if (len == vlen && memcmp(q, vstr, vlen) == 0) {return p;}} else {/* Find out if the searched field can be encoded. Note that* we do it only the first time, once done vencoding is set* to non-zero and vll is set to the integer value. */// 因为传入值有可能被编码了,// 所以当第一次进行值对比时,程序会对传入值进行解码// 这个解码操作只会进行一次if (vencoding == 0) {if (!zipTryEncoding(vstr, vlen, &vll, &vencoding)) {/* If the entry can't be encoded we set it to* UCHAR_MAX so that we don't retry again the next* time. */vencoding = UCHAR_MAX;}/* Must be non-zero by now */assert(vencoding);}/* Compare current entry with specified entry, do it only* if vencoding != UCHAR_MAX because if there is no encoding* possible for the field it can't be a valid integer. */// 对比整数值if (vencoding != UCHAR_MAX) {// T = O(1)long long ll = zipLoadInteger(q, encoding);if (ll == vll) {return p;}}}/* Reset skip count */skipcnt = skip;} else {/* Skip entry */skipcnt--;}/* Move to next entry */// 后移指针,指向后置节点p = q + len;}// 没有找到指定的节点return NULL;
}

连锁更新性能问题

更新或者删除某元素时,需要重新计算所需空间大小并且重新分配所需要的空间,性能肯定是不行的。详细请看下面ziplist增加元素的做法,一个 ziplist 元素包括了 prevlen(编码需要改变)、encoding 和实际数据 data 三个部分

/* Insert item at "p". */
/** 根据指针 p 所指定的位置,将长度为 slen 的字符串 s 插入到 zl 中。** 函数的返回值为完成插入操作之后的 ziplist** T = O(N^2)*/
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {// 记录当前 ziplist 的长度size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = 0;size_t offset;int nextdiff = 0;unsigned char encoding = 0;long long value = 123456789; /* initialized to avoid warning. Using a valuethat is easy to see if for some reasonwe use it uninitialized. */zlentry entry, tail;/* Find out prevlen for the entry that is inserted. */if (p[0] != ZIP_END) {// 如果 p[0] 不指向列表末端,说明列表非空,并且 p 正指向列表的其中一个节点// 那么取出 p 所指向节点的信息,并将它保存到 entry 结构中// 然后用 prevlen 变量记录前置节点的长度// (当插入新节点之后 p 所指向的节点就成了新节点的前置节点)// T = O(1)entry = zipEntry(p);prevlen = entry.prevrawlen;} else {// 如果 p 指向表尾末端,那么程序需要检查列表是否为:// 1)如果 ptail 也指向 ZIP_END ,那么列表为空;// 2)如果列表不为空,那么 ptail 将指向列表的最后一个节点。unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);if (ptail[0] != ZIP_END) {// 表尾节点为新节点的前置节点// 取出表尾节点的长度// T = O(1)prevlen = zipRawEntryLength(ptail);}}/* See if the entry can be encoded */// 尝试看能否将输入字符串转换为整数,如果成功的话:// 1)value 将保存转换后的整数值// 2)encoding 则保存适用于 value 的编码方式// 无论使用什么编码, reqlen 都保存节点值的长度// T = O(N)if (zipTryEncoding(s,slen,&value,&encoding)) {/* 'encoding' is set to the appropriate integer encoding */reqlen = zipIntSize(encoding);} else {/* 'encoding' is untouched, however zipEncodeLength will use the* string length to figure out how to encode it. */reqlen = slen;}/* We need space for both the length of the previous entry and* the length of the payload. */// 计算编码前置节点的长度所需的大小// T = O(1)reqlen += zipPrevEncodeLength(NULL,prevlen);// 计算编码当前节点值所需的大小// T = O(1)reqlen += zipEncodeLength(NULL,encoding,slen);/* When the insert position is not equal to the tail, we need to* make sure that the next entry can hold this entry's length in* its prevlen field. */// 只要新节点不是被添加到列表末端,// 那么程序就需要检查看 p 所指向的节点(的 header)能否编码新节点的长度。// nextdiff 保存了新旧编码之间的字节大小差,如果这个值大于 0 // 那么说明需要对 p 所指向的节点(的 header )进行扩展// T = O(1)nextdiff = (p[0] != ZIP_END) ? zipPrevLenByteDiff(p,reqlen) : 0;/* Store offset because a realloc may change the address of zl. */// 因为重分配空间可能会改变 zl 的地址// 所以在分配之前,需要记录 zl 到 p 的偏移量,然后在分配之后依靠偏移量还原 p offset = p-zl;// curlen 是 ziplist 原来的长度// reqlen 是整个新节点的长度// nextdiff 是新节点的后继节点扩展 header 的长度(要么 0 字节,要么 4 个字节)// T = O(N)zl = ziplistResize(zl,curlen+reqlen+nextdiff);p = zl+offset;/* Apply memory move when necessary and update tail offset. */if (p[0] != ZIP_END) {// 新元素之后还有节点,因为新元素的加入,需要对这些原有节点进行调整/* Subtract one because of the ZIP_END bytes */// 移动现有元素,为新元素的插入空间腾出位置// T = O(N)memmove(p+reqlen,p-nextdiff,curlen-offset-1+nextdiff);/* Encode this entry's raw length in the next entry. */// 将新节点的长度编码至后置节点// p+reqlen 定位到后置节点// reqlen 是新节点的长度// T = O(1)zipPrevEncodeLength(p+reqlen,reqlen);/* Update offset for tail */// 更新到达表尾的偏移量,将新节点的长度也算上ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+reqlen);/* When the tail contains more than one entry, we need to take* "nextdiff" in account as well. Otherwise, a change in the* size of prevlen doesn't have an effect on the *tail* offset. */// 如果新节点的后面有多于一个节点// 那么程序需要将 nextdiff 记录的字节数也计算到表尾偏移量中// 这样才能让表尾偏移量正确对齐表尾节点// T = O(1)tail = zipEntry(p+reqlen);if (p[reqlen+tail.headersize+tail.len] != ZIP_END) {ZIPLIST_TAIL_OFFSET(zl) =intrev32ifbe(intrev32ifbe(ZIPLIST_TAIL_OFFSET(zl))+nextdiff);}} else {/* This element will be the new tail. */// 新元素是新的表尾节点ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(p-zl);}/* When nextdiff != 0, the raw length of the next entry has changed, so* we need to cascade the update throughout the ziplist */// 当 nextdiff != 0 时,新节点的后继节点的(header 部分)长度已经被改变,// 所以需要级联地更新后续的节点if (nextdiff != 0) {offset = p-zl;// T  = O(N^2)zl = __ziplistCascadeUpdate(zl,p+reqlen);p = zl+offset;}/* Write the entry */// 一切搞定,将前置节点的长度写入新节点的 headerp += zipPrevEncodeLength(p,prevlen);// 将节点值的长度写入新节点的 headerp += zipEncodeLength(p,encoding,slen);// 写入节点值if (ZIP_IS_STR(encoding)) {// T = O(N)memcpy(p,s,slen);} else {// T = O(1)zipSaveInteger(p,value,encoding);}// 更新列表的节点数量计数器// T = O(1)ZIPLIST_INCR_LENGTH(zl,1);return zl;
}

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

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

相关文章

机器学习笔记 - 什么是3D语义场景完成/补全?

一、什么是3D语义场景补全? 3D 语义场景完成(Semantic Scene Completion)是一种机器学习任务,涉及以体素化形式预测给定环境的完整3D场景(完成3D形状的同时推断场景的 3D 语义分割的任务)。这是通过使用深度图和为场景提供上下文的可选 RGB 图像来完成的。目标是以一种可轻…

LeedCode刷题---子数组问题

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、最大子数组和 题目链接&#xff1a;最大子数组和 题目描述 给你一个整数数组 nums &#xff0c;请你找出一个具有最大和的连…

【力扣】——可获得的最大点数(滑动窗口)

几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#xff0c;最终你必须正好拿 k 张卡牌。 你的点数就是你拿到手中的所有卡牌的点数之和。 给你一个整数数组 cardPoi…

[山东大学操作系统课程设计]实验三

0.写在前面(重点) 由于一些突发事件&#xff0c;导致目前大家手里或多或少都有了完整版的答案了。甚至很多学长学姐们写的代码远比我写的要好很多。 但是这个系列我觉得还是稍微坚持下去一点&#xff0c;或许某些地方可以帮到未来的同学们。 还是那句话&#xff0c;有需要可…

Spring MVC数据绑定的几种方法(一)

这篇文章包含spring mvc的默认数据类型绑定和简单数据类型绑定。内容来自实验。 准备&#xff1a; &#xff08;1&#xff09;在IDEA环境中从archetye创建webapp类型的maven项目exp6。 &#xff08;2&#xff09;在src\main目录下创建并标注java源代码文件夹和resources资源文…

NVIDIA GPU Operator install in kubernetes

文章目录 1. 简介2. Kubernetes 安装3. OS配置4. Docker Engine&#xff0c;cri-dockerd安装5. 安装 kubeadm6. GPU-Operator安装 1. 简介 Kubernetes通过设备插件框架提供对特殊硬件资源的访问&#xff0c;如NVIDIA GPU、⽹卡、Infiniband适配器和其他设备。但是&#xff0c;…

基于Java SSM框架实现实现四六级英语报名系统项目【项目源码+论文说明】

基于java的SSM框架实现四六级英语报名系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个高校四六级报名管理系统&#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作…

2024美赛数学建模资料---100%获奖资料

很好的教程了 一共二十四章 每一章都是一个模型 并且有matlab编程编码 第一章 线性规划 第二章 整数规划 第三章 非线性规划 第四章 动态规划 第五章 图与网络 第六章 排队论 第七章 对策论 第八章 层次分析法 第九章 插值与拟合 第十章 数据的统计描述和分析 第十一章…

详解Spring对Mybatis等持久化框架的整合

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

自带灯效的气传导耳机,声音当然好听,哈氪聆光体验

现在市场上的蓝牙耳机种类繁多&#xff0c;入耳式的算是主流&#xff0c;但不太适合户外使用 &#xff0c;我平时出门健身、散步的时候&#xff0c;更喜欢用气传导耳机。气传导耳机通常采用挂耳式的设计&#xff0c;耳机不入耳&#xff0c;佩戴舒适度更好&#xff0c;而且稳定性…

Linux安装mongodb数据库(详细)

一、下载安装包 本文使用 tgz 方式,根据服务器类型在官网下载 MongoDB 安装包。官方地址&#xff1a;https://www.mongodb.com/try/download/community 下载方式如图所示&#xff1a; 选择版本 关于 MongoDB 的版本选择&#xff0c;参见如下版本差异&#xff1a; 1、将从官…

Java基本数据类型详解

✨个人主页&#xff1a;全栈程序猿的CSDN博客 &#x1f4a8;系列专栏&#xff1a;Java从入门到精通 ✌座右铭&#xff1a;编码如诗&#xff0c;Bug似流星&#xff0c;持续追求优雅的代码&#xff0c;解决问题如同星辰般自如 Java是一种强类型语言&#xff0c;数据类型在程序中起…

JS不同运算符下的隐式类型转换

目录 运算符 逻辑运算符&#xff08;&&、||、!&#xff09;和 条件表达式&#xff08;if、三元表达式&#xff09; 逻辑运算符 条件表达式 算数运算符&#xff08;*、/、- %、&#xff09;和 关系运算符&#xff08;>、<、、!&#xff09; 算数运算符 关系…

Python实战:批量加密Excel文件指南

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;我是彭涛&#xff0c;今天为大家分享 Python实战&#xff1a;批量加密Excel文件指南&#xff0c;全文3800字&#xff0c;阅读大约10分钟。 在日常工作中&#xff0c;保护敏感数据是至关重要的。本文将引导你通过…

栈和队列的OJ题--13.用队列实现栈

13. 用队列实现栈 225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09; /*解题思路&#xff1a; 此题可以用两个队列去实现一个栈&#xff0c;每次始终保持一个队列为空&#xff0c; 入栈操作相当于给非空队列进行入队操作 出栈操作相当于非空队列的队尾元素出队&…

细说CountDownLatch

CountDownLatch 概念 CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。 CountDownLatch 定义了一个计数器&#xff0c;和一个阻塞队列&#xff0c; 当计数器的值递减为0之前&#xff0c;阻塞队列里面的线程处于挂起状态&#xff0c;当计数器递减到0时…

【学习笔记】机器学习——GAN

提出于2014年。 GAN由两个神经网络组成&#xff1a;一个试图生成看起来与训练数据相似数据的生成器&#xff0c;以及一个试图从虚假数据中分辨出真实数据的判别器。生成器和判别器在训练期间相互竞争。 对抗训练&#xff08;训练竞争性网络&#xff09;是一种重要的机器学习思想…

工作几年了,你真的懂 Redis 嘛?

大家好&#xff0c;我是伍六七。一个专注于输出 AI 编程内容的在职大厂资深程序员&#xff0c;全国最大 AI 付费社群破局初创合伙人&#xff0c;关注我一起破除 35 诅咒。 Redis 基本上是大部分技术公司都会使用的缓存框架&#xff0c;但是我发现很多程序员其实并不懂 Redis。 …

SSM新闻发布管理系统

SSM毕设分享 序号1&#xff1a;SSM新闻发布管理系统 1 项目简介 Hi&#xff0c;各位同学好&#xff0c;这里是郑师兄&#xff01; 今天向大家分享一个毕业设计项目作品【SSM新闻发布管理系统】 师兄根据实现的难度和等级对项目进行评分(最低0分&#xff0c;满分5分) 难度系数…

三极管在数字电路中的应用

一、认识三极管 三极管拥有3个引脚&#xff0c;分别对应3个级&#xff1a;基极(Base)、发射极&#xff08;Emitter&#xff09;、集电极(Collector)&#xff0c;如下图所示&#xff1b;下图横向左侧的是基极&#xff0c;带箭头的那个引脚就是发射极&#xff0c;另一个就是集电…