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.mzph.cn/news/709173.shtml

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

相关文章

Nginx软件安装及配置说明

Nginx软件安装及配置说明 Nginx软件的安装包括以下三种方式: 源码编译安装linux软件源命令安装windows二进制安装包 Linux系统安装Nginx 一、源码编译安装Nginx 1.1 源码下载和编译 1.源码下载 源码下载地址:https://nginx.org/ 1.1直接下载 在…

【C++】结构体内存对齐详解

规则 1.第一个成员在结构体变量偏移量为0 的地址处,也就是第一个成员必须从头开始。 2.其他成员的偏移量为对齐数**(该成员的大小 与 编译器默认的一个对齐数 中的较小值)**的整数倍。 3.结构体总大小对最大对齐数(通过最大成员来确定)的整数…

Oracle 直接路径插入(Direct-Path Insert)

直接路径插入(Direct Path Insert)是Oracle一种数据加载提速技术,可以在使用insert语句或SQL*Loader工具大批量加载数据时使用。直接路径插入处理策略与普通insert语句完全不同,Oracle会通过牺牲空间,安全性&#xff0…

opengles 绘制图元 ——glDrawArrays() 相关API介绍 (十)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、opengles3.0 绘制图元介绍二、绘图图元 API 介绍1. glDrawArrays()1.1 glDrawArrays()函数原型1.2 GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN 三者的区别1.3 使用GL_TRIANGLES, G…

springboot+maven项目导入本地jar包,以有打包错误问题

1 本地jar包放置路径为: 2添加Modules File->project settings–>Modules–>Dependencies–>–>, 3 添加 Libraies 至此 项目即可成功运行。 mvn 打包错误,需要 运行以下命令 mvn install:install-file -Dfile${project.basedir}/s…

52.2k star! 自己部署gpt4free, 免费使用各种GPT

GPT4Free是一个由开发者Xtekky在GitHub上发布的开源项目,它可以免费地使用GPT-3.5、GPT-4、llama、gemini-pro、bard、claude等多种大模型。截止到当前(2024.1.30)已经有52.2k star,可见其受欢迎程度。 github地址:https://github.com/xtekky…

拜登:“一切非 Rust 项目均为非法”,开发界要大变天?

文章目录 科技巨头应为安全漏洞负起责任使用其他语言的开发者​该何去何从? 白宫国家网络总监办公室(ONCD,以下简称网总办)在本周一发布的报告中说道:“程序员编写代码并非没有后果,他们的⼯作⽅式于国家利…

Leetcode 134. 加油站 java版 如何解决环路加油站算法

# 官网链接:. - 力扣(LeetCode) 1. 问题描述: 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i1 个加油站需要消耗汽油 cost[i] 升…

C语言之操作符详解

文章目录 一、算术操作符二、移位操作符1、 原码、反码、补码2、左移操作符3、右移操作符 三、位操作符1、按位与【&】2、按位或【|】3、按位异或【^】4、按位取反【~】5、两道面试题6、进制定位将变量a的第n位置为1将变量a的第n位置为0 四、赋值操作符1、复合赋值符 五、单…

wayland(xdg_wm_base) + egl + opengles 渲染旋转的 3D 立方体实例(十一)

文章目录 前言一、实现旋转的3D 立法体需要用到的技术1. 模型矩阵2. 视图矩阵3. 投影矩阵4. 背面剔除二、opengles3.0 渲染旋转的 3D 立方体实例1. egl_wayland_cube3_0.c2. Matrix.h 和 Matrix.c3. xdg-shell-client-protocol.h 和 xdg-shell-protocol.c4. 编译5. 运行总结参考…

Linux系统---nginx(4)负载均衡

目录 1、服务器配置指令 ​编辑 1.1 服务器指令表 1.2 服务器指令参数 2、负载均衡策略指令 2.1 轮询 (1) 加权轮询 (2) 平滑轮询 2.2 URL 哈希(一致性哈希) 2.3 IP哈希策略 2.4 最少连接 Nginx 负载均衡是由代理模块和上…

OpenGL调用窗口,方向键和鼠标

9.2 OpenGL调用窗口,方向键和鼠标 9.2.1 opengl调用窗口 OpenGL调用窗口步骤: 第一步:初始化 GLFW,初始化OpenGL,初始化窗口,初始化上下文 第二步:设置窗口大小和位置,设置输入输出 第三步…

uniapp画图qiun-data-charts

引用:https://blog.csdn.net/LW0512/article/details/124857592

【STM32】STM32学习笔记-WDG看门狗(46)

00. 目录 文章目录 00. 目录01. WDG简介02. IWDG概述03. IWDG框图04. IWDG键寄存器05. WWDG简介06. WWDG框图07. WWDG工作特性08. IWDG和WWDG对比09. 预留10. 附录 01. WDG简介 WDG(Watchdog)看门狗 看门狗可以监控程序的运行状态,当程序因为…

华为OD技术面试案例2-2024年

软开C,机考满分,技术面面评两个A,时间线如下: 01.04 笔试链接 01.10 笔试(2.5h) 01.11 综测 01.13 资格面(HR面) 01.17 技术一面 01.19 技术二面 01.20 主管面 01.22 提Offer及审批 01.26 Offer邮件 笔试复盘 1.英文输入…

嵌入式烧录报错:板端IP与PC的IP相同

报错: 配置 实际上我配置并没有错。 服务器IP(就是本机)、板端IP、网关。此处网关必须与板子IP配套(可以不存在)。 解决 我网卡配置了多个IP。一番删除添加还是报错。 于是点击服务器IP,换成别的&#x…

鸿蒙OS应用编程实战:构建未来应用的基石

💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】🤟 基于Web端打造的:👉轻量化工具创作平台💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】 引言 鸿蒙OS(HarmonyOS&#xff0…

前后端分离跨域问题总结

进行前后端分离开发联调时,网络常常会出现问题,可能后端确实做了跨域处理,但是前端还是跨域。一些其他的解决方法。 预检跨域 Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ head…

CentOS7如何使用Docker部署Wiki.Js知识库并实现公网远程访问?

文章目录 1. 安装Docker2. 获取Wiki.js镜像3. 本地服务器打开Wiki.js并添加知识库内容4. 实现公网访问Wiki.js5. 固定Wiki.js公网地址 不管是在企业中还是在自己的个人知识整理上,我们都需要通过某种方式来有条理的组织相应的知识架构,那么一个好的知识整…

网络工程师笔记4

协议 端口号 FTP 21、20 HTTP 80 Telnet 23 SMTP 25 TCP头部 TCP三次握手 TCP重传机制:超…