Redis String 类型底层揭秘

news2025/1/21 15:41:42

目录

前言

String 类型低层数据结构

节省内存的数据结构


前言

Redis 的 string 是个 “万金油” ,这么评价它不为过. 它可以保存Long 类型整数,字符串, 甚至二进制也可以保存。对于key,value 这样的单值,查询以及插入都是O(1)时间复杂度。满脑子都是它的优点,真的就那么好吗?如果不了解它的底层结构,会有很多坑的,下面让我们细细说来。

有这样一个需求,我们要开发一个图片存储系统,要求这个系统能快速地记录图片ID和图片在存储系统中保存的ID(图片存储对象ID)。同时还更够根据图片ID快速查询图片存储对象ID。我们现在假设图片ID和图片存储对象ID都是10位数据,而且图片数量非常多。一个图片ID 和存储对象ID正好一一对应,是典型的“键 - 单值”模式。所谓的“单值”,就是指键值对中的值就是一个值,而不是一个集合,这和 String 类型提供的“一个键对应一个值的数据”的保存形式刚好契合。

v1v2

v1:存储图片ID和图片存储对象前Reids 内存

v2:存储图片ID和图片存储对象后Reids 内存

v2.used_memory-v1.used_memory=96B ,也就是说明key,value 存储用了96个字节。一组图片的ID以及存储对象ID实际上只需要16字节就可以了。

我们来分析下。图片 ID 和图片存储对象 ID 都是 10 位数,我们可以用两个 8 字节的 Long 类型表示这两个 ID。因为8字节的Long类型表示两个ID。因为8字节的Long类型最大可以表示2的64次方的数值,肯定可以表示10位数,为啥用了96个字节呢?我们先聊聊string 底层数据结构。

String 类型底层数据结构

其实,除了记录实际的数据,String 类型还需要额外的内存空间记录数据长度、空间使用等信息,这些信息也叫元数据。当实际保存数据较小时,元数据空间很大,那就很不划算了。

大家在用Redis 的 String 类型时有没有考虑过一个问题,为啥String 那么神奇,既可以保存int,还可以保存字符串,还可以保存二进制数据。这些其实都与它低层的编码有关,不同的数可能会用不同的编码。String 编码有int、embstr 和 raw 这三种编码模式.为了详细说明,我用一张图表示:

通过图可以看出,embstr 和 Raw 编码有len,alloc, buf

  1. buf:字节数组,保存实际数据。为了表示字节数组的结束,Redis 会自动在数组最后加一个“\0”,这就会额外占用 1 个字节的开销

  2. len:占 4 个字节,表示 buf 的已用长度

  3. alloc:也占个 4 字节,表示 buf 的实际分配长度,一般大于 len

len+alloc+buf = SDS

int编码:RedisObject = 元数据+INT,其他的编码 RedisObject = 元数据+ptr

通过图我们也可以发现embstr编码的ptr 与 SDS 是一块连续的内存区域,这样就可以避免内存碎片。当保存Long 类型数据时,RedisObject 中的指针就直接赋值为整数数据了,这样就不用额外的指针再指向整数了,节省了指针的空间开销。

上面的描述我们发现RedisObject 就是我们保存的数据,那么我们是怎么找到数据呢。不要忘了Redis 会使用一个全局的哈希表保存所有的键值对。哈希表的每一项是一个Entry 也就是我们所说的哈希桶,哈希桶每一项是一个, dictEntry 的结构体,用来指向一个键值对。dictEntry 结构中有三个 8 字节的指针,分别指向 key、value 以及下一个 dictEntry。为了详细描述这个结构,我画了一个图。

通过这些知识点我们可以分析出为什么我们会存储96个字节,key,value 会根据hash 算法,在全局哈希表表找到一个哈希桶,哈希桶每个元素就是一个Entry,那么我们的key就是放在key指针所引用的RedisObject结构体,value 就是放在value指针所引用的RedisObject 的结构体。一个key,value 所占用的空间根据图所示应该包含entry 的大小以及它们自己的RedisObject。

sum(entry) = key指针占用空间+value 指针占用空间+next指针占空空间 = 8B+8B+8B=32B,为啥是32呢。由于Redis 使用的内存分配库 jemalloc 。jemalloc 有一个特点就是分配内存一定是N 的 2 的幂次数,这样可以避免经常分配内存而影响系统的性能。所以就是32B

sum(redisobject) = sum(key的redisobject)+sum(value的redisobject) = 16B+16B = 32B

一共花费了多少内存空间?sum(key+value) = sum(entry) +sum(redisobject) = 64B,可能又有人问 96B-64B=32B去哪了,那是Entry(新申请的哈希桶)的大小是32B.这个就不能算是String消耗的内存空间。明明有效数据就是16B,有48B浪费了,如果有1亿张图片呢?1亿*48B就有那么多浪费,真的是真金白银呀,有没有更加节省的内存呢?当然有,那就是ziplist。

节省内存的数据结构

Redis 有一种底层数据结构,叫压缩列表(ziplist). 这个数据结构下面我们详细讲解.我找了一个图,可以展开分析下

zlbytes 表示列表长度,zltail表示列表尾,zllen 偏移量,zlend 表示列表结束

entry 是有以下结构组成 = prev_len+len+encoding+content

prev_len:表示前一个 entry 的长度。prev_len 有两种取值情况:1 字节或 5 字节。取值 1 字节时,表示上一个 entry 的长度小于 254 字节。虽然 1 字节的值能表示的数值范围是 0 到 255,但是压缩列表中 zlend 的取值默认是 255,因此,就默认用 255 表示整个压缩列表的结束,其他表示长度的地方就不能再用 255 这个值了。所以,当上一个 entry 长度小于 254 字节时,prev_len 取值为 1 字节,否则,就取值为 5 字节。

len:表示自身长度,4 字节;

encoding:表示编码方式,1 字节;

content:保存实际数据。

这些 entry 会挨个儿放置在内存中,不需要再用额外的指针进行连接,这样就可以节省指针所占用的空间。

如果我们用ziplist结构存储数据时消耗的内存是sum(entry)= sum(prev_len)+sum(len)+sum(encoding)+sum(content)=1+4+1+8=14B实际分配了16B。

这种数据结构在Redis 中有Hash、List、Sorted set 集合类型。这种方案节省内存的原因是一个key对应一个集合数据,保存数据很多也只用一个dictEntry结构这样就节省内存,key ,value 各用一个元数据。当数据多的时候,空间复杂度就被分摊了,这样就节省内存了。

怎么保存hash的结构,我们需要对原始的单值的key用二级编码方式拆分两部分,前一部分作为Hash集合的key,后一部分作为hash集合value 的key。

以图片 ID 1101000060 和图片存储对象 ID 3302000080 为例,我们可以把图片 ID 的前 7 位(1101000)作为 Hash 类型的键,把图片 ID 的最后 3 位(060)和图片存储对象 ID 分别作为 Hash 类型值中的 key 和 value。

hset 1101000 060 3302000080

把最后 3 位作为 Hash 类型值中的 key,也是根据配置文件中的设置,只有这样才能利用ziplist 的结构,如果超过了这个阈值,那么就只能用hash表存储数据呢,并不能有效的节省内存了。

这两个阈值分别对应以下两个配置项:

hash-max-ziplist-entries:表示用压缩列表保存时哈希集合中的最大元素个数。

hash-max-ziplist-value:表示用压缩列表保存时哈希集合中单个元素的最大长度。

hash-max-ziplist-value 这个我们一定是可以满足的,hash-max-ziplist-entries 这个值一般设置成1000,三位数最大值是999加上一个特殊值000刚好1000。这个值不建议设置很大,ziplist 除了最开始的原始和最后的原始查找的时间复杂度是O(1),其他位置的元素时间复杂度是O(n),值越大,获取元素越低效

除了用hash 结构,也可以用 Sorted Set,zadd 1101000 3302000080 060 可以把value 当作score 存储。显然 Sorted Set 在插入数据性能上没有hash 高效,这是因为Sorted Set插入数据时首先根据score找到对应位置,然后在插入进去,而Hash在插入元素时,只需要将新的元素插入到ziplist的尾部即可,不需要定位到指定位置

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

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

相关文章

react-组件进阶

1.目标 能够实用props接收数据 能够实现父子组件之间的通讯 能够实现兄弟组件之间的通讯 能够给组件添加props校验 能够说出生命周期常用的钩子函数 能够知道高阶组件的作用 2.目录 组件通讯介绍 组件的props 组件通讯的三种方式 Context props深入 组件的生命周期 Render-p…

死区过滤器Deadband和DeadZone区别(应用介绍)

死区过滤器的算法和详细介绍专栏也有介绍,这里我们主要对这两个模块的区别和应用场景进行详细介绍 1、死区过滤器 https://rxxw-control.blog.csdn.net/article/details/128521007https://rxxw-control.blog.csdn.net/article/details/128521007 1、Deadband和DeadZone区别…

计算机网络:深入探索HTTP

引言: HTTP,全称超文本传输协议(Hypertext Transfer Protocol),是互联网上数据通信的基础。它定义了客户端(如浏览器)和服务器之间如何交互和传输数据。HTTP最初是为了支持Web浏览而设计的&…

面试经典150题——汇总区间

"The only place where success comes before work is in the dictionary." - Vidal Sassoon 1. 题目描述 2. 题目分析与解析 2.1 思路一 因为题目提示了我们数组是有序的,而覆盖区间都是根据数组中连续数字来确定的,比如: 可以…

Linux/Spectra

Enumeration nmap 第一次扫描发现系统对外开放了22,80和3306端口,端口详细信息如下 22端口运行着ssh,80端口还是http,不过不同的是打开了mysql的3306端口 TCP/80 进入首页,点击链接时,提示域名不能解析&…

5分钟看懂安迪比尔定律

大家好,我是董董灿。 今天跟大家聊一个,在写程序的过程中节省内存的这个话题。 话题来源于一个小伙伴问:我在写程序的时候,更改一个判断语句,会提升几毫秒,换一种方法会节省几KB,这些有必要吗? 这里涉及到一个软件与…

搭建sql-labs-master

环境:win10 解压sql-labs-master靶场文件,会提示有重复,直接覆盖就好。 将sql-labs-master文件拖到phpstudy/www目录下 打开浏览器访问“本机ip/sql-labs-master或localhost/sql-labs-master或127.0.0.1/sql-labs-master” 页面底部就是此靶…

Spring Boot项目误将Integer类型写成int来进行传参

在处理项目中Idea中无报错: 问题: localhost:8080/param/m2在浏览器中输入:localhost:8080/param/m2 产生报错: This application has no explicit mapping for /error, so you are seeing this as a fallback. Tue Feb 27 20:55…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的生活垃圾检测与分类系统(Python+PySide6界面+训练代码)

摘要:本篇博客详细讲述了如何利用深度学习构建一个生活垃圾检测与分类系统,并且提供了完整的实现代码。该系统基于强大的YOLOv8算法,并进行了与前代算法YOLOv7、YOLOv6、YOLOv5的细致对比,展示了其在图像、视频、实时视频流和批量…

【virtual Box】功能速通:安装 Windows 和 Ubuntu

文章目录 一、虚拟机1.1 概述1.2 virtual box概述 二、新建虚拟机、删除、注册三、虚拟机内部设置3.1 安装增强功能驱动3.2 分辨率问题3.3 网络链接方式 一、虚拟机 1.1 概述 虚拟机(Virtual Machine,VM)是一种软件实现的计算机系统&#x…

Qt篇——QTableWidget保存表格数据到Excel文件中,读Excel内容到QTableWidget

表格和excel例子如下图所示&#xff1a; 一、QTableWidget保存表格数据到Excel文件中 代码如下&#xff1a; &#xff08;pro文件中添加QT axcontainer&#xff09; #include <QAxObject>void MainWindow::saveTableToExcel() {QDateTime current_date_time QDateTi…

【计算机网络】Socket的OOBInline选项与UrgentData

Socket的OOBINLINE选项是用于处理TCP紧急数据的一个设置。TCP紧急数据通常指的是那些需要优先处理的数据&#xff0c;例如某些控制信息或关键数据。 OOB&#xff08;Out-of-Band&#xff09;表示带外数据&#xff0c;OOBInline是一个Socket选项&#xff0c;用于控制是否将接收…

基于GWO优化的LSTM多输入分类预测(Matlab)灰狼算法优化长短期神经网络分类预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、部分程序&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 本代码基于Matalb平台编译&…

自动驾驶框架:自动驾驶汽车定位-感知-规划-决策-控制概述,按照我的架构图理解:决策决定的是速度,规划决定的是路径(架构理解推荐)

1.按照我的架构图理解&#xff1a;决策决定的是速度&#xff0c;规划决定的是路径 参考链接&#xff1a;【自动驾驶】运动规划丨速度规划丨自动驾驶速度规划及状态协调方法 2.下面是参考别人的理解&#xff1a; 自动驾驶汽车定位-感知-规划-决策-控制概述 规划-决策-控制知…

女儿的国风穿搭~粉绿两色你喜欢哪个?

法国皱绣花马甲和飘雅面料衬衫的组合 赋予古典新的诠释&#xff0c;别具一番韵味 衣服上是古典的山树绣花设计 精致典雅&#xff0c;上身立体又轻盈 做了粉绿两色&#xff0c;很适合春天的氛围

【王道数据结构】【chapter7查找】【P285t5】

线性表中各节点的检索概率不等时&#xff0c;可用如下策略提高顺序检索的效率&#xff1b;若找到指定的结点&#xff0c;则将该结点和其前驱结点&#xff08;若存在&#xff09;交换&#xff0c;使得经常被访问的结点尽量位于表的前端。试设计在顺序结构和链式结构的线性表盘上…

在正式项目中使用Improv配网及小程序配网工具

前面我们演示了使用.Net nanoFramework为ESP32进行蓝牙配网的简单演示&#xff0c;这个功能在实际项目中是非常有用的。当然&#xff0c;使用中我们还需要考虑一些其他的问题&#xff0c;比如如何将这个功能集成到我们的项目中&#xff0c;本文将详细介绍如何在正式项目中使用I…

Java进阶-集合(1)

进入Java集合的学习&#xff0c;集合的数学概念是指具有某种特定性质的具体的或抽象的对象汇总而成的集体。在Java中的集合也是类似的&#xff0c;先学习集合的框架&#xff0c;这次主要介绍一下Conllection接口。 一、概述 1、数组存储 数组存储具有两大缺点&#xff1a; …

BevFusion (2): nuScenes 数据介绍及点云可视化

1. nuScenes 数据集 1.1 概述 nuScenes 数据集 (pronounced /nu:ːsiː:nz/) 是由 Motional (以前称为 nuTonomy) 团队开发的自动驾驶公共大型数据集。nuScenes 数据集的灵感来自于开创性的 KITTI 数据集。 nuScenes 是第一个提供自动驾驶车辆整个传感器套件 (6 个摄像头、1 …

FinalShell控制远程Linux服务器(首先得自己已购买好Linux服务器并安装了对应的系统,这里是安装的centos系统)

1、电脑上需要安装FinalShell软件 可以到分享的链接中下载软件&#xff0c;然后双击点击下一步安装即可 链接&#xff1a;https://share.weiyun.com/Y6TrdDHp 密码&#xff1a;gbvyg62、建立远程连接 3、输入连接信息 4、显示连接主机成功&#xff0c;表示远程进入 5、输入…