Redis-数据结构

参考资料 极客时间Redis(亚风)

Redis数据结构

SDS

sds(Simple Dynamic String) 字符串接结构体:

struct --attribute_- ((-_packed__)) sdshdr8{uint8_t len;/* buf已保祥的字符串字节数,不包含结束标示*/uint8_t alloc;/* buf申请的总的字节数,不包含结束标示*/unsigned char flags;/*不同SDS的头类型,⽤来控制SDS的头⼤⼩*/char buf [];
}

Redis SDS 扩容机制
假如我们要给SDS追加⼀段字符串,Amy,这⾥⾸先会申请新内存空间:
• 如果新字符串⼩于1M,则新空间为扩展后字符串⻓度的两倍+1;
• 如果新字符串⼤于1M,则新空间为扩展后字符串⻓度+1M+1。称为内存预分
配。
特别注意: :所以我们在生产中如果用Redis直接存储字符串最好是不要超过1M,不然可能回导致空间浪费。

IntSet

IntSet是Redis中set集合的⼀种实现⽅式,基于整数数组来实现,并且具备⻓度可变、有序等特征。
IntSet结构体

typedef struct intset {uint32_t encoding; /*编码⽅式,⽀持存放16位、32位、64位整数*/uint32_t Length;/* 元素个数 */int8_t contents []/*整数数组,保存集合数据*/
}

为了⽅便查找,Redis将intset中所有整数按升序依次保存在contents数组中。比如我们存储了1,2,3,4这个时候encoding 为16足以存储,如果我们插入50000这个时候有符号16位不足以存下,这个时候就会倒序扩容然后存储也就是从最后一个数据开始向后复制,最后的结果是每个数据都扩容到了32位。如果小于0插入最前面,如果大于0插入到最后面,如果是中间的数呢是通过二分法进行插入。

DICT

我们知道Redis是⼀个键值数据库,我们可以根据键实现快速的增删改查。⽽
键与值的映射关系是通过Dict来实现的。
Dict由三部分组成,分别是:DictHashTable(HashMap 类比java)、DictEntry(Map.entry)、Dict

typedef struct dictht {// entry数组 数组中保存的是指向entry的指针dictEntry **table;//哈希表⼤⼩unsigned Long size;//哈希表⼤⼩的掩码,总等于size - 1  h % size == h & size - 1 的前提是size是2的倍数 引出面试题hashmap 为什么扩容是2倍扩容unsigned Long sizemask;// entry个数unsigned Long used;
}
typedef struct dictEntry {void *key;//键union {// 值可能是4中类型之一void *val;uint64_t u64;int64_t s64;double d;} v;//值//下⼀个Entry的指针struct dictEntry *next;
}
// 此数据结构用于扩容
typedef struct dict {dictType *type;//dict类型,内置不同的hash函数void *privdata; // 私有数据,在做特殊hash运算时⽤dictht ht[2]//⼀个Dict包含两个哈希表,其中⼀个是当前数据,另⼀个⼀般是空,rehash时使⽤Long rehashidx;//rehash的进度,-1表示未进⾏int16_t pauserehash;// rehash是否暂停,1则暂停,0则继续
}

当我们向Dict添加键值对时,Redis⾸先根据key计算出hash值 (h),然后利⽤ h& sizemask来计算元素应该存储到数组中的哪个索引位置。
Dict中的HashTable就是数组结合单向链表的实现,当集合中元素较多时,必然导致哈希冲突增多,链表过⻓,则效率会⼤⼤降低。Dict在每次新增键值对时都会检查负载因⼦(LoadFactor =used/size),used表示entry的数量也就是数据节点的数量,size是hash表的大小,满⾜以下两种情况时会触发哈希表扩容:
• 哈希表的 LoadFactor >= 1,并且服务器没有执⾏ SAVE 或REWRITEAOF 等进程
• 哈希表的 LoadFactor>5;
扩容的逻辑

static int _dictExpandI fNeeded (dict *d){//如果正在rehash,则返回okif (dictisRehashing(d)) return DIcT.oK;//如果哈希表为空,则初始化哈希表为4if (d->ht[0].size == 0) return dictExpand (d, 4)// 当负载因⼦(used/size)达到1以上,并且当前没有进⾏rewriteaof等⼦进程操作//或者负载因⼦超过5,则进⾏ dictExpand,也就是扩容if (d->ht[0].used >= d->ht[0].size &&dict_can_resize Il d->ht [0] .used/d->ht [0] .size > 5){//扩容⼤⼩为used +1,底层会对扩容⼤⼩做判断,实际上找的是第⼀个⼤于等于 used+12nreturn dictExpand(d, d->ht[0].used + 1)}
return DICT_OK;
}

Dict除了扩容以外,每次删除元素时,也会对负载因⼦做检查,当LoadFactor<0.1 时,会做哈希表收缩:

// 判断是否需要缩容 染出操作成功的时候检查
int htNeedsResize(dict *dict) {//哈希表⼤⼩size = dictSlots(dict)// entry数量used = dictSize(dict)// 负载因⼦低于0.1return (size > 4 &&(used*100/size < 10))}
// 收缩逻辑
int dictResize(dict *d){unsigned Long minimal;//如果正在做save或rewriteaof或rehash,则返回错误if (!dict_can_resize || dictIsRehashing (d))return DICT_ERR;// 获取used,也就是entry个数minimal = d->ht[0].used;//如果used⼩于4,则重置为4if (minimal < 4) minimal = 4;// 重置⼤⼩为minimal,其实是第⼀个⼤于等于minimal的2nreturn dictExpand(d, minimal)}

不管是扩容还是收缩,必定会创建新的哈希表,导致哈希表的size和sizemask变化,⽽key的查询与sizemask有关。因此必须对哈希表中的每⼀个key重新计算索引,插⼊新的哈希表,这个过程称为rehash。过程是这样的:
1 计算新hash表的realsize,即第⼀个⼤于等于dict.ht[0].used的2的n次方。
2 按照新的realsize申请内存空间,创建dictht,并赋值给dict.ht[1]。
3 设置rehashidx= 0,标示开始rehash。
4 将dict.ht[0]中的每⼀个dictEntry都rehash到dict.ht[1]每次执⾏新增、查询、修改、删除操作时,都检查⼀下dict.rehashidx是否⼤于-1,如果是则将ht[0].table[rehashidx]的entry链表rehash到dict.ht[1](这里的逻辑是每次将这个槽里面的数据全部搬过去,不是将整个数据(所有槽)搬过去),井且将rehashidx++。直⾄dict.ht[0]的所有数据都rehash到dict.ht[1]。搬运过程中,这条链表上的数据,有可能会搬到别的位置。满足一个原则:元素要不原位置不动,要不搬运到oldsize + index的位置去。
5 将dict.ht[1]赋值给dict.ht[0],给dict.ht[1]初始化为空哈希表。
6 将rehashidx赋值为-1,代表rehash结束。
7 在rehash过程中,新增操作,则直接写⼊ht[1],查询、修改和删除则会在dict.ht[0]和dict.ht[1]依次查找并执⾏。这样可以确保h[0]的数据只减不增,随着rehash最终为空。

ZipList

生成环境中最多存储几千的数据,数据多性能会下降
ZipList 是⼀种特殊的双端链表,由⼀系列特殊编码的连续内存块组成。可以在任意⼀端进⾏压⼊/弹出操作,并且该操作的时间复杂度为 0(1)。
在这里插入图片描述
在这里插入图片描述
ZipList 中的Entry并不像普通链表那样记录前后节点的指针,因为记录两个指针要占⽤16个字节,浪费内存。⽽是采⽤了下⾯的结构:
在这里插入图片描述
previous_entry_length:前⼀节点的字节⼤⼩,占1个或5个字节。如果前⼀节点的⻓度⼩于254字节,则采⽤1个字节来保存这个⻓度值。如果前⼀节点的⻓度⼤于254字节,则采⽤5个字节来保存这个⻓度值,第⼀个字节为0xfe,后4个字节才是真实⻓度数据。
encoding:编码属性,记录content的数据类型(字符串还是整数)以及content的字节数,占⽤1个、2个或5个字节。如果encoding是以“00”、“01〞或者“10”开头,则证明content是字符串,如果encoding是以“11〞开始,则证明content是整数,encoding占⽤1个字节。
Content:负责保存节点的数据,可以是字符串或整数

ZipList这种特殊情况下产⽣的连续多次空间扩展操作称之为连锁更新(Cascade Update)。新增、删除都可能导致连锁更新的发⽣。这种情况的发生就是因为254字节这个临界点,当前节点以后的数据都要被更新,因为大于254长度会用5个字节来存,所以不推荐存大量的数据存储

QuickList

问题1:ZipList虽然节省内存,但申请内存必须是连续空间,如果内存占⽤较多,申请内存效率很低。怎么办?
答:限制ZipList的⻓度和entry⼤⼩。
问题2:但是我们要存储⼤量数据,超出了ZipList最佳的上限该怎么办?
答:可以创建多个ZipList来分⽚存储数据。
问题3:数据拆分后⽐较分散,不⽅便管理和查找,这多个ZipList如何建⽴联系?
答:使⽤QuickList,它是⼀个双端链表,只不过链表中的每个节点都是⼀个ZipList。
QuickList结构

typedef struct quicklist{quicklistNode *head;quicklistNode *tail;// 所有ziplist的entry的数量unsigned Long count;// ziplists总数量unsigned Long Len;// ziplist的entry上限,默认值 -2int fill: -2// ⾸尾不压缩的节点数量unsigned int compress: 0 
}
typedef struct quicklistNode 
{struct quicklistNode *prev;struct quicklistNode *next;//当前节点的ZipList指针unsigned char *zl;// 当前节点的ZipList的字节⼤⼩unsigned int sz;//当前节点的ZipList的entry个数unsigned int count: 16//编码⽅式:1,ZipList;2,压缩模式unsigned int encoding: 2// 是否被解压缩。1:则说明被解压了,将来要重新压缩unsigned int recompress : 1 
}

为了避免QuickList中的每个ZipList中entry过多,Redis提供了⼀个配置项:listmax-ziplist-size来限制。

  • 如果值为正,则代表ZipList的允许的entry个数的最⼤值
  • 如果值为负,则代表ZipList的最⼤内存⼤⼩,分5种情况:
    ① -1:每个ZipList的内存占⽤不能超过4kb
    ② -2: 每个ZipList的内存占⽤不能超过8kb
    ③ -3:每个ZipList的内存占⽤不能超过16kb
    ④ -4:每个ZipList的内存占⽤不能超过32kb
    ⑤ -5:每个ZipList的内存占⽤不能超过64kb

除了控制zipList的⼤⼩,QuickList还可以对节点的ZipList做压缩。通过配置项list-compress-depth来控制。因为链表⼀般都是从⾸尾访问较多,所以⾸尾是不压缩的。这个参数是控制⾸尾不压缩的节点个数:
0:特殊值,代表不压缩
1:标示QuickList的⾸尾各有1个节点不压缩,中间节点压缩
2:标示QuickList的⾸尾各有2个节点不压缩,中间节点压缩
在这里插入图片描述
中间…表示被压缩的节点。

SkipList

SkipList(跳表)⾸先是链表,但与传统链表相⽐有⼏点差异:
• 元素按照升序排列存储
• 节点可能包含多个指针,指针跨度不同

typedef struct zskiplist {//头尾节点指针struct zskiplistNode *header, *tail;//节点数量unsigned Long Length;// 最⼤的索引层级,默认是1int Level;
} zskiplist;typedef struct zskiplistNode {sds ele; //节点存储的值double score;// 节点分数,排序、查找⽤struct zskiplistNode *backward;// 前⼀个节点指针struct zskiplistLevel {struct zskiplistNode * forward;//下⼀个节点指针unsigned Long span;//索引跨度} level[]//多级索引数组
} zskiplistNode;

在这里插入图片描述

Redis对象

Redis 对底层的数据结构进行了进一步封装,但是其底层仍然是上面的几种数据结构实现:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
参考资料 极客时间Redis(亚风)

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

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

相关文章

day02-报表技术POI

1、基于模板导出列表数据 1.1、需求 按照以下样式导出excel 1.2、思路 首先准备一个excel模板,这个模板把复杂的样式和固定的内容先准备好并且放入到项目中,然后读取到模板后向里面放入数据。 1.3、实现 第一步:准备一个excel作为导出的…

AI 编程助手 Copilot:从对话中分析程序性能

大家好,我是木川 一、介绍 GitHub Copilot 是 GitHub 和 OpenAI 合作开发的一个 AI 辅助编程工具 官网地址:https://github.com/features/copilot 官方文档:https://docs.github.com/copilot 分析程序性能在对话功能中有提到 二、安装 在 VSC…

Ubuntu 常用命令之 ll 命令用法介绍

ll是ls -l的别名,用于在Ubuntu系统中列出目录的详细信息。ls命令用于列出目录内容,-l选项则以长格式显示,包括文件类型、权限、链接数、所有者、组、大小、最后修改时间以及文件或目录名。 这是ll命令的基本格式 ll [选项]... [文件]...这是…

Halcon参考手册异常检测知识总结

1.1异常检测介绍 本章将介绍如何使用基于深度学习的异常检测和全局上下文异常检测。通过这两种方法,我们想要检测图像是否包含异常(异常是指偏离正常的事物,未知的事物)。 异常检测或全局上下文异常检测模型学习无异常图像的共同特征。经过训练的模型将…

JS中call()、apply()、bind()改变this指向的原理

大家如果想了解改变this指向的方法,大家可以阅读本人的这篇改变this指向的六种方法 大家有没有想过这三种方法是如何改变this指向的?我们可以自己写吗? 答案是:可以自己写的 让我为大家介绍一下吧! 1.call()方法的原理…

Linux---压缩和解压缩命令

1. 压缩格式的介绍 Linux默认支持的压缩格式: .gz.bz2.zip 说明: .gz和.bz2的压缩包需要使用tar命令来压缩和解压缩.zip的压缩包需要使用zip命令来压缩,使用unzip命令来解压缩 压缩目的: 节省磁盘空间 2. tar命令及选项的使用 命令说明tar压缩和解压缩命令 …

二分查找|双指针:LeetCode:2398.预算内的最多机器人数目

作者推荐 【动态规划】【广度优先】LeetCode2258:逃离火灾 本文涉及的基础知识点 二分查找算法合集 滑动窗口 单调队列:计算最大值时,如果前面的数小,则必定被淘汰,前面的数早出队。 题目 你有 n 个机器人,给你两…

锁--07_2---- index merge(索引合并)引起的死锁

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 案例分析生产背景死锁日志表结构执行计划 EXPLAN为什么会用 index_merge(索引合并)为什么用了 index_merge就死锁了解决方案注:M…

初识Pandas函数是Python的一个库(继续更新...)

学习网页: Welcome to Python.orghttps://www.python.org/https://www.python.org/https://www.python.org/ Pandas函数库 Pandas是一个Python库,提供了大量的数据结构和数据分析工具,包括DataFrame和Series等。Pandas的函数非常丰富&…

Spring Boot3.1.6配置对应的Swagger

1. pom.xml导入Swagger依赖 <!--swagger3--> <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-ui</artifactId><version>2.0.2</version> </dependency> 2.创建SwaggerCo…

自动化访客互动:提升网站效益与用户体验的关键优势

在激烈的市场竞争环境中&#xff0c;想抢占市场&#xff0c;获得收益并不容易。每一个订单的完成都要经过一定的销售周期&#xff0c;所以企业可以根据销售周期每个阶段的特点进行优化&#xff0c;留住客户。其中&#xff0c;企业可以在与客户在线互动的过程中&#xff0c;让互…

【第2期】Springboot如何快速集成SpringSecurity

简单介绍 本专栏主要结合实战讲解&#xff0c;不过多介绍细节的概念&#xff0c;概念可以通过搜索引擎查找&#xff0c;一搜一大把&#xff0c;切入正题。 本专栏的实战项目是基于SpringbootSpringSecurityRSAJWTVUE的全栈开发项目&#xff0c;每个环节都会专门讲&#xff0c;…

C语言 文件I/O(备查)

所有案列 跳转到其他。 文件打开 FILE* fopen(const char *filename, const char *mode); 参数&#xff1a;filename&#xff1a;指定要打开的文件名&#xff0c;需要加上路径&#xff08;相对、绝对路径&#xff09;mode&#xff1a;指定文件的打开模式 返回值&#xff1a;成…

遥感图像分割系统:融合空间金字塔池化(FocalModulation)改进YOLOv8

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 遥感图像分割是遥感技术领域中的一个重要研究方向&#xff0c;它的目标是将遥感图像中的不同地物或地物类别进行有效的分割和识别。随着遥感技术的不断发展和遥感…

2024年高效远程协同运维工具推荐

随着企业的不断发展以及变化&#xff0c;企业的内部IT环境也是日益复杂&#xff0c;一跨高效远程协同运维工具必不可少&#xff0c;不仅可以提高生产力&#xff0c;还能降低运营成本。这里就给大家推荐2024年高效远程协同运维工具。 高效远程协同运维工具应用场景 1、IT运维管…

(五)STM32 按键输入实验及 GPIO做普通 IO 的注意事项

目录 1. 按键硬件连接 2. 按键软件设计 3. 按键消抖 4. 使用 IO 口时的 注意事项&#xff08;踩坑&#xff09; 上一节我们介绍了 STM32F1 的 IO 口作为输出的使用&#xff0c;这一章&#xff0c;我们将介绍如何使用 STM32F1 的 IO 口作为输入用。在本章中&#xff0c;我们…

modbus 通信协议介绍与我的测试经验分享

1、简介 Modbus 协议是一种通信协议&#xff0c;用于工业自动化系统中的设备间通信。该协议最初由 Modicon 公司开发&#xff0c;并于 1979 年发布。 Modbus 协议通过串行通信格式进行通信&#xff0c;在物理层上支持 RS-232、RS-422 和 RS-485 等多种通信方式。在协议层面&am…

Guardrails for Amazon Bedrock 基于具体使用案例与负责任 AI 政策实现定制式安全保障(预览版)

作为负责任的人工智能&#xff08;AI&#xff09;战略的一部分&#xff0c;您现在可以使用 Guardrails for Amazon Bedrock&#xff08;预览版&#xff09;&#xff0c;实施专为您的用例和负责任的人工智能政策而定制的保障措施&#xff0c;以此促进用户与生成式人工智能应用程…

redis未授权漏洞复现

什么是redis redis就是个数据库&#xff0c;跟mysql不同的地方在于redis主要将数据存在内存中&#xff0c;读写速度非常快 redis未授权 其原因很简单&#xff0c;就是redis服务器在默认安装好不配置的情况下可以直接免密码登录&#xff0c;登录后在web目录写入一句话木马&am…

【Spark精讲】RDD特性之数据本地化

目录 首选运行位置 数据的本地化级别 谁来负责数据本地化 数据本地化执行流程 调优 代码中的设置方法 首选运行位置 上图红框为RDD的特性五&#xff1a;每个RDD的每个分区都有一组首选运行位置&#xff0c;用于标识RDD的这个分区数据最好能够在哪台主机上运行。通过RDD的…