Redis String 类型底层揭秘

目录

前言

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.hqwc.cn/news/495533.html

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

相关文章

【JSON2WEB】06 JSON2WEB前端框架搭建

【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 前端技术路线太多了,知识点更多&…

面试题 16.15. 珠玑妙算

1:题目 2.思路分析 1.首先malloc开辟的数组,将其中的元素的值为零,方便之后的统计次数 2.先统计猜中的,当猜中时,solution[i]guess[i]时,为让防止之后统计之后伪猜中时重复的,要将值改变&…

Linux下的IO多路复用

文章目录 一. IO的概念和分类1. IO操作的原理:二. I/O多路复用使用场景和作用1. 问题: 一台网络服务器需要接收100台客户端的连接和数据通信,应该如何设计和实现?2. I/O多路复用机制: 三. Select poll epollselectpoll…

导览系统厂家|景区电子导览|手绘地图|AR导览|语音导览系统

随着元宇宙、VR、AR等新技术的快速发展,旅游服务也更加多元化、智能化。景区导览系统作为旅游服务的重要组成部分,其形式更加多元化智能化。智能导览系统作为一种新的服务方式,能够为游客提供更加便捷的旅游服务和游览体验,也逐渐…

精品基于SpringBoot的体育馆场地预约赛事管理系统的设计与实现-选座

《[含文档PPT源码等]精品基于SpringBoot的体育馆管理系统的设计与实现[包运行成功]》该项目含有源码、文档、PPT、配套开发软件、软件安装教程、项目发布教程、包运行成功! 软件开发环境及开发工具: Java——涉及技术: 前端使用技术&#…

3D工业相机及品牌集合

3D相机可以获取物理世界的空间信息,即立体三维的物理信息,不仅可以拍摄到场景的二维图像,而且能获取物体之间的位置关系,再经过进一步深化处理,还能完成三维建模等应用。 3D相机三种方案 1、结构光 通常采用特定波长…

Vue3学习——路由prop配置、replace

写法一 在路由中可直接写prop: true&#xff0c;即可在页面中defineProps使用 相当于&#xff08;<Detail id“1” name“2” />&#xff09;,但只能是params {path: /service,name: 服务,component: () > import(../views/Service/index)&#xff0c;props: true}…

Javaweb之SpringBootWeb案例之 Bean管理的获取Bean详细的解析

2. Bean管理 在前面的课程当中&#xff0c;我们已经讲过了我们可以通过Spring当中提供的注解Component以及它的三个衍生注解&#xff08;Controller、Service、Repository&#xff09;来声明IOC容器中的bean对象&#xff0c;同时我们也学习了如何为应用程序注入运行时所需要依…

【QT+QGIS跨平台编译】之五十三:【QGIS_CORE跨平台编译】—【qgssqlstatementparser.cpp生成】

文章目录 一、Bison二、生成来源三、构建过程一、Bison GNU Bison 是一个通用的解析器生成器,它可以将注释的无上下文语法转换为使用 LALR (1) 解析表的确定性 LR 或广义 LR (GLR) 解析器。Bison 还可以生成 IELR (1) 或规范 LR (1) 解析表。一旦您熟练使用 Bison,您可以使用…

将法律条文很美观的复制到word上

前言 目前很多法律条款都没有现成的PDF或者word格式的供大家下载&#xff0c;这个时候呢&#xff0c;领导又要求你帮他搞定&#xff0c;这就很。。。。 步骤 复制全部条款到word中使用wps的排版功能&#xff0c;将空格和空段落全部移除 3. 设置好你需要的格式 标题&#xff…

黑马程序员——接口测试——day03——Postman断言、关联、参数化

目录&#xff1a; Potman断言 Postman断言简介Postman常用断言 断言响应状态码断言包含某字符串断言JSON数据Postman断言工作原理Postman关联 简介实现步骤核心代码创建环境案例1案例2Postman参数化 简介数据文件简介编写数据文件 CSV文件JSON文件导入数据文件到postman读取数…

opencv图像腐蚀

腐蚀&#xff08;Erosion&#xff09;是一种形态学图像处理操作&#xff0c;用于移除图像中的小白点、细小物体或者边缘。它通过将结构元素应用于图像上的像素来实现。 以下是opencv实现图像腐蚀的代码 #include <opencv2/highgui/highgui.hpp> #include <opencv2/im…