Redis五大数据结构的底层实现

news2025/1/10 23:01:03

一)String类型:可以使用object encoding name就可以查看字符串的编码

SDS,flags的值不同,那么len和alloc所表示的值的数据范围也不同,所以flags的只是为了标识SDS头的总大小;

alloc和len刚开始进行申请内存空间的时候都是相同的

String是redis中最常见的数据结构类型:

1)最基本的编码方式是RAW,是基于简单动态字符串来实现的,存储上线是512mb,每一次存储一个String类型的数据的时候,需要先申请内存创建一个RedisObject的内存空间,再申请SDS的内存空间,申请的时候有两次内存的申请,释放的时候也是有两次内存的释放;

 2)如果存储的SDS的长度是小于44个字节的,那么会直接采用EMBSTR编码,此时的ObjectHead和SDS是一段连续的内存空间,申请内存的时候只是需要调用一次内存分配函数即可(因为每一次分配内存都要涉及到用户态和内核态的切换) 

SDS一共占用48个字节,RedisObject占用16个字节,一共是64个字节(不会超过64字节),Redis在底层采用的内存分配算法是以2^N来做内存分配的,64恰好是一个分片大小,就不会产生内存碎片,所以实际使用String类型的时候,尽量字符串长度小于44字节

3)如果进行存储的字符串是整数值,况且在Long_Max范围内,底层就会使用INT编码,直接将数据存储在RedisObject的指针位置(指针刚好8个字节),就不需要SDS了

Redis中的List数据结构

Redis的List类型可以从首和尾进行操作元素

哪一个数据结构可以满足以上特征呢 

1)使用普通双向链表,可以从双端进行访问内存占用比较高,内存碎片比较多

2)使用ZipList,可以从双端进行访问,内存的占用比较低,存储的上限比较低,双端访问不是基于指针实现的,而是记录每一个结点的大小,推算出内存地址的,因为他是申请连续内存空间的,如果数据量不多还可以,但是如果数据量过大,申请较大的连续内存空间效率可能非常低,所以是不建议使用ZipList来存储过多的数据的,只能存储少量数据

3)QuickList:底层是使用双端链表+压缩列表,可以从双端进行访问,内存占用是比较低的,包含多个Ziplist,存储上限较高,双端列表的每一个节点不是数据而是一个ZipList

3.1)在3.2版本之前,Redis使用ZipList(只能存储少量数据)和LinkedList来实现List,当元素数量小于512况且元素大小小于64字节的时候采用ZipList编码,超过使用LinkedList编码;

3.2)在3.2版本以后,Redis统一使用QuickList来实现List;

1)上面这个方法是通用的插入方法,where是队首或者是队尾,lpush和rpush底层都是调用的pushGenericCommand方法,最后的这个xx参数,如果你进行传递了,也就是true,只有当前的key存在,才会执行push命令,如果不存在,一般是不会传递的,也就是当你第一次进行插入的时候,key不存在,就会自动创建这个key,并分配内存空间;

2)第一个参数client代表客户端,当redis的客户端和服务端建立连接的时候,都会被封装成一个client对象,里面包含着客户端的各种信息,包括客户端要执行的命令,每一个命令都会被分成N段(lpush key value l1 l2 l3),命令本质上就是一个一个的字符串,每一个字符串会使用空格隔开,分成多个命令段放到一个数组中,也就是argv数组,其实最后执行命令的时候,就是从c里面的argv数组中取出一个一个的参数命令段,拼接成完整命令执行;

3)j=2开始遍历是在寻找未来可以存放在list中的元素,里面的if语句是针对用户传递过来的针对于元素大小的校验,argc就是整个argv数组元素的个数;

4)lookupkeywrite方法是在寻找你要向list集合中的哪一个key进行push,第一个参数是c->db,是客户端要进行访问哪一个数据库,取出当前客户端要访问的数据库,第二个参数在数据库中去选择对应key去寻找对应的value(list);

最终方法的返回值是一个RedisObject对象(list)

5)然后去进行检查当前类型是否是一个List类型,根据RedisObject中的type属性来检查当前对象的类型,如果不是list类型直接返回;

6)如果RedisObject不存在并且xx为true,说明没有list就直接返回了;

7)否则就会在createQuickList中创建一个全新的List:

8)createObject方法底层创建了一个全新的RedisObject对象,并将RedisObject中数据的指针指向创建的新的list,并在方法底层针对RedisObject设置全新的编码和类型;

 

9)最后的quicklistsetoptions中限制QuickList中每一个ZipList的大小,是否压缩,server.list_compress_depth是压缩深度,如果是0表示不进行压缩,如果是1表示QuickList的首尾各有一个不压缩,中间节点压缩,如果是2表示QuickList的首尾各有两个节点不压缩,中间节点压缩

10)dbAdd方法是真正的添加元素

Redis中的Set数据结构:对元素的查询效率非常高

Set是Redis中的单列集合,包括以下特点:

1)不保证有序性

2)保证元素唯一

3)求并集,差集,补集

1)首先就是跳表, 查询性能比较不错,但是不太合适,首先跳表要去根据score值进行排序,但是Set集合不需要有序,但是Set集合中还没有score值;

2)Set是Redis中的集合,不一定保证元素有序性,但是需要保证元素唯一,对查询性能要求极高,为了保证查询效率和唯一性,set使用dict编码,dict中的key用来存储元素,value统一使用null;

3)当存储的Set集合里面全部都是整数的时候,并且元素数量不超过set-max-intset-entries的时候,底层默认使用的是IntSet编码,是连续存储空间,元素数量也不能太多,从而来节省内存;

1)subject就是一个RedisObject对象,里面的编码可能是Inset或者是dict,第二个参数是要插入的值

2)进行新增元素的时候如果RedisObject中的编码是HT编码,那么直接插入元素value即可

3)如果RedisObject中的编码类型是IntSet那么直接判断当前value是否是整数,如果是整数,直接添加元素到IntSet里面,如果此时添加元素成功了,直接判断当前元素个数是否大于了set-max-intset-entries的值,如果大于,那么直接将编码转换成HT

4)如果value值不是整数直接转换成HT编码;

 

Zset集合:

Zset也是SortedSet,其中每一个元素都需要指定score值和member值

1)可以根据score值进行排序

2)member值必须唯一

3)可以根据member值查询分数

在Zset里面底层的数据结构必须满足键值存储,键必须唯一,可以进行排序

1)跳表这种数据结构,SkipList可以进行排序,并且可以同时存储score值和ele字符串,但是要根据键查询对应的分数值就比较困难了,只能通过遍历的方式进行查询,效率比较低,况且member只能是唯一的,实现起来就比较困难;

2)HT:可以进行键值存储,并且可以根据Key(member)来查询对应的value值但是不能做排序

基于跳表进行排序(根据value也就是score得分来做排序,来做范围查找,基于哈希表来根据key(member)查询指定的val(score),但是很占用内存;

当元素数量不多的时候,那么HT和SkipList的优势是不明显的,而且还会更耗费内存,因此Zet底层还会采用ZipList结构来节省内存,但是要满足以下两个条件:

1)元素数量小于zset_max_ziplist_entries,默认值是128

2)每一个元素都小于zset_max_ziplist_value,默认值是64字节,这两个条件都是可以通过配置文件进行修改

1)这个代码首先会根据key去寻找对应的zset结构,如果不存在就创建新的Zset,最终返回的是RedisObject对象,里面的内容执行zset;

2)进行检查编码是否是Zset,如果不是则进行退出

3)如果zset不存在那么直接进行初始化操作,首先会在if条件中进行判断zset_max_ziplist_entries是否等于0,如果等于0说明这是人为手动进行设置的,说明如果这个条件成立,那么永远也不会再使用ZipList编码了,永远使用Dict+SkipList编码

或者是value中的元素大小小于zset_max_ziplist_value

 

1)上面的代码首先进行判断当前的编码格式,如果已经是跳表了,那么无需进行转换

2)如果是ZipList编码,首先进行判断当前元素中是否已经存在了这个新增的值(通过RedisObject中的ptr指针来进行查询),如果当前元素已经存在了,那么直接更新score值即可

3)如果元素不存在,就直接判断当前元素总个数(假设已经把这个元素给添加进来了,所以要+1)和当前元素的大小是否已经超过了指定的值,或者是整个ZipList的大小超过了上限,也是更换编码

但数据量比较小的时候,Zset底层使用ZipList,ZipList本身并没有排序功能,而且没有键值对的概念,因此只能通过Zset底层编码来实现:

1)ZipList是连续内存空间,因此score值和element是连续紧挨在一起的两个entry,element在前,score值在后面

2)score值越小越靠近队首,score值越大越靠近队尾,最终是按照score值升序排列的

 Hash结构的底层实现:

Hash结构和Zset结构十分相似,都是键值存储,都是要求根据键来获取对应的值,况且键都是唯一的,但是它们的区别也是很明显的:

1)Zset的值要求是member,值是score,但是哈希类型的键和值都是任意值

2)zset要根据score值进行排序,hash则无需进行排序

因此Hash结构底层采用的编码和Zset也是基本一致的只需要把排序有关的ZipList去掉即可 

1)Hash结构底层默认使用的是ZipList编码,用来节省内存,ZipList中的两个相邻Entry分别保存field和value

2)但数据量比较大的时候,Hash结构默认会转化为HT编码也就是Dict,但是触发条件有两个

2.1)ZipList中的元素数量超过了hash-max-ziplist-entries默认是512字节

2.2)ZipList中的任意entry大小超过了hash-max-ziplist-value默认是64字节

argv数组中全是用户输入的分段命令,argc是argv数组的长度

 

1)首先redis会进行判断如果当前编码不是ZipList编码,那么啥也不用做了,直接返回

2)start=2,表示要想hash结构中添加的元素的第一个value值得索引,argc是最后一个数组的长度,argc-1就是最后一个要向key中添加得value得索引下标

3)开始进行for循环遍历,判断这个value值是否是字符串,如果是字符串,就继续向下进行判断,然后根据sdslen来求出字符串的字节数,如果字节数大于hash_max_ziplist_value那么直接更换编码为hash

4)如果ZipList的长度大小超过了1G,也转化成哈希表

5)但是在上面的这个代码里面没有做ZipList元素大小的判断,因为用户输入的field值可能原来已经在redis中存在了,如果发现redis中的field值和用户所进行指定的field值相同,那么会直接覆盖对应的value值,但是redis中哈希结构的值的个数也是没有发生变化的,所以在这里面是不能够判断ZipList中的元素数量超过了hash-max-ziplist-entries转化成Dict编码

最终向Zset集合中添加元素的代码:

1)先进行查看是否是ZipList编码,如果不是ZipList编码,直接将之插入到哈希表中或者覆盖

2)如果是ZipList编码,先进行查询head指针,如果head不为空,再根据ziplistFind查询field值是否已经在redis中存在了,如果已经存在了,直接调用zipListReplace更新value值update代表是否执行了更新操作,将其置为1;

3)如果不存在,也就是update不等一,直接添加到ZipList的尾部

4)最后再来判断list长度是否超出

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

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

相关文章

Vue.js 中的父子组件通信方式

Vue.js 中的父子组件通信方式 在 Vue.js 中,组件是构建应用程序的基本单元。当我们在应用程序中使用组件时,组件之间的通信是非常重要的。在 Vue.js 中,父子组件通信是最常见的组件通信方式之一。在本文中,我们将讨论 Vue.js 中的…

原神3.2服务端PC端架设教程

安装教程 安装 MongoDB,不会安装建议使用宝塔面板 sudo apt updatesudo apt install mongodb-orgsudo systemctl enable --now mongod 直接启动.如果失败看看数据库启动了没有.端口一致 ./start.sh 安装配置JDK17 双击 OpenJDK17U-jdk_x64_windows_hotspot_1…

4年经验,面试二十多家公司后的整理....

先说一下自己的个人情况,普通二本计算机专业毕业,懂python,会写脚本,会selenium,会性能,然而离职后到今天都没有收到一份offer!一直在待业中,从离职第一天就开始准备简历&#xff0c…

【Proteus仿真】【51单片机】智能婴儿车

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器,使用LCD1604显示模块、按键模块、LED和蜂鸣器、DHT11温湿度、DS18B20温度传感器、SR04超声波,声音传感器、L298N直流电机、语音蓝牙控制等。…

搭建属于你自己的New Bing

前言 在这篇博客中,您将学习如何使用 Render 部署 go-proxy-bingai 项目,以便在不需要登录的情况下体验微软 Bing AI 的所有功能。 作者GitHub项目地址:adams549659584/go-proxy-bingai: 用 Vue3 和 Go 搭建的微软 New Bing 演示站点&#x…

工业场景中的RFID技术应用有哪些?

您是否好奇于如何在工业场景中利用RFID技术实现更高效的操作和生产流程优化?本文将带您深入探索RFID技术在工业领域的应用,揭示其优势和挑战,并通过实际案例为您呈现RFID技术在工业自动化和生产流程优化方面的成功应用。 一、RFID技术在工业…

OpenWrt 软路由解析公网IPV6域名访问家庭NAS的问题答疑

1、非要使用 Padavan 或者 OpenWrt固件的软路由才能IPV6公网访问吗? 答:这个并不是这样的,一般家用路由器都无法放行防火墙规则,这种情况当然无法实现IPV6公网访问,但是少部分路由器是可以的,只要有防火墙设…

MySQL6-深入理解MVCC和BufferPool缓存机制

❤️ 个人主页:程序员句号 🚀 支持水滴:点赞👍 收藏⭐ 留言💬关注 🌸 订阅专栏:MySQL性能调优 原创博文、基础知识点讲解、有一定指导意义的中高级实践文章。 认真或有趣的技术分享。 MySQL性…

Vue.js 中的路由是什么?如何使用路由?

Vue.js 中的路由是什么?如何使用路由? 在 Vue.js 中,路由是指为不同的 URL 地址提供不同的页面内容或视图的机制。Vue.js 中的路由可以使用 Vue Router 库来实现,它是 Vue.js 官方提供的路由管理库。 Vue Router 简介 Vue Route…

vue (8)

vue8 文章目录 1. 浏览器本地存储1.1 localStorage1.2 sessionStorage1.3 总结 2. 修改 TodoList 案例3. 绑定自定义事件3.1 绑定3.2 解绑3.3 两个注意点3.4 总结3.5 修改 TodoList 案例 4. 全局事件总线4.1 总结4.2 修改 TodoList 案例 1. 浏览器本地存储 1.1 localStorage 图…

【MySQL数据库】项目中用到的一些SQL查询总结

文章目录 前言1. 有A,B两张表,需要统计A表中某个属性值的总数,更新到B表中实现代码 2. 将三张(或n张)表中的结果合并一起实现代码 3. 删除题库中的所有试题信息实现代码 4. 统计同一张表中,不同属性的数量于…

C语言-typedef关键字

一.typedef 关键字 typedef是在C语言允许为一个数据类型起一个新的别名。它本身是一种存储类的关键字,与auto extern,mutable、static、register 等关键字不能出现在同一个表达式中。 二、typedef用法 示例: 对于数据类型使用例如: 对于指针的使用例如…

【VictoriaMetrics】VictoriaMetrics单机版批量和单条数据写入(influx格式)

VictoriaMetrics单机版支持以influx格式的数据写入,写入支持单条数据写入以及多条数据写入,下面操作演示下如何使用 1、首先需要启动VictoriaMetrics单机版服务 启动VictoriaMetrics单机版服务执行的命令如下 nohup /opt/victoriaMetrics/victoria-metrics-prod -httpListe…

【剑指offer专项突破版】整数篇(经典面试题)——C

文章目录 前言一. 整数除法题目分析1.一般思路①代码 2.优化思路②优化后的代码 拓展:用位运算实现整数的加法③代码 二. 二进制加法题目分析思路分析①代码 三. 前n个数字中1的个数题目描述思路分析①方法1——遍历②方法2——i&(i-1)③方法3——i&(i-1)的优化④方法…

数字赋农:数字农业新时代,致富之路宽又阔!

不管在什么年代,粮食永远是国之根本、民之命脉,而来粮食安全更是“国之大者”,更是曾在大会中明确提出过,要全方位夯实粮食安全根基,牢牢守住十八亿耕地红线,确保中国人的饭碗牢牢端在自己手中。 我们需要深…

快速入门SpringMVC 学习

目录 SpringMVC 定义 MVC定义 创建SpringMVC项目 SpringMVC掌握功能 一、连接功能 RequestMapping(请求映射) GetMapping 和 PostMapping 二、获取参数功能 传递单个参数/多个参数 注意点: RequestParam(前后端参数映射) 前后端参数映射 RequestParam特…

骆驼祥子思维导图怎么画?高效工具分享

骆驼祥子是一部中国现代文学经典作品,由老舍所著。在阅读这本书时,我们可以使用思维导图来梳理故事情节和人物关系,从而更好地理解这本书的主题和内涵。 我们可以在线制作思维导图,这个网站还内置了流程图和Markdown功能。 站点内…

前瞻洞察|Prompt Learning(提示学习)——新的低资源场景克星

近年来,预训练语言模型已然成为自然语言处理(NLP)领域中备受瞩目的技术之一。预训练模型可以在大规模文本语料上进行自监督学习,从而获得丰富的语言学知识,并通过在下游任务上进行微调,实现出色的性能。Pro…

markdown导出成html,并将图片转换成base64

文章目录 方案一,使用typora第一步,编写python脚本第二步,设置导出后运行py脚本,设置如下 方式二,使用vscode插件:Markdown Preview Enhanced使用方法在markdown头部添加 预览界面右键,选择导出…

JAVA基础知识总结系列(2):JAVA语法基础

1,关键字:其实就是某种语言赋予了特殊含义的单词。 保留字:其实就是还没有赋予特殊含义,但是准备日后要使用过的单词。 2,标示符:Java中的包、类、方法、参数和变量的名字,可由任意顺序的大小写…