Redis数据存储的细节

news2025/1/11 14:21:45

原文章地址 : 深入学习Redis(1):Redis内存模型 - 编程迷思 - 博客园 (cnblogs.com)

1.概述

关于Redis数据存储的细节,涉及到内存分配器(如jemalloc)、简单动态字符串(SDS)、5种对象类型及内部编码、redisObject。在讲述具体内容之前,先说明一下这几个概念之间的关系。

下图是执行set hello world时,所涉及到的数据模型。

 

图片来源:https://searchdatabase.techtarget.com.cn/7-20218/

(1)dictEntry:Redis是Key-Value数据库,因此对每个键值对都会有一个dictEntry,里面存储了指向Key和Value的指针;next指向下一个dictEntry,与本Key-Value无关。

(2)Key:图中右上角可见,Key(”hello”)并不是直接以字符串存储,而是存储在SDS结构中。

(3)redisObject:Value(“world”)既不是直接以字符串存储,也不是像Key一样直接存储在SDS中,而是存储在redisObject中。实际上,不论Value是5种类型的哪一种,都是通过redisObject来存储的;而redisObject中的type字段指明了Value对象的类型,ptr字段则指向对象所在的地址。不过可以看出,字符串对象虽然经过了redisObject的包装,但仍然需要通过SDS存储。

实际上,redisObject除了type和ptr字段以外,还有其他字段图中没有给出,如用于指定对象内部编码的字段;后面会详细介绍。

(4)jemalloc:无论是DictEntry对象,还是redisObject、SDS对象,都需要内存分配器(如jemalloc)分配内存进行存储。以DictEntry对象为例,有3个指针组成,在64位机器下占24个字节,jemalloc会为它分配32字节大小的内存单元。

下面来分别介绍jemalloc、redisObject、SDS、对象类型及内部编码。

2、jemalloc

Redis在编译时便会指定内存分配器;内存分配器可以是 libc 、jemalloc或者tcmalloc,默认是jemalloc。

jemalloc作为Redis的默认内存分配器,在减小内存碎片方面做的相对比较好。jemalloc在64位系统中,将内存空间划分为小、大、巨大三个范围;每个范围内又划分了许多小的内存块单位;当Redis存储数据时,会选择大小最合适的内存块进行存储。

jemalloc划分的内存单元如下图所示:

 

图片来源:http://blog.csdn.net/zhengpeitao/article/details/76573053

例如,如果需要存储大小为130字节的对象,jemalloc会将其放入160字节的内存单元中。

3、redisObject

前面说到,Redis对象有5种类型;无论是哪种类型,Redis都不会直接存储,而是通过redisObject对象进行存储。

redisObject对象非常重要,Redis对象的类型、内部编码、内存回收、共享对象等功能,都需要redisObject支持,下面将通过redisObject的结构来说明它是如何起作用的。

redisObject的定义如下(不同版本的Redis可能稍稍有所不同):

1

2

3

4

5

6

7

typedef struct redisObject {

  unsigned type:4;

  unsigned encoding:4;

  unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

  int refcount;

  void *ptr;

} robj;

redisObject的每个字段的含义和作用如下:

(1)type

type字段表示对象的类型,占4个比特;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。

当我们执行type命令时,便是通过读取RedisObject的type字段获得对象的类型;如下图所示:

(2)encoding

encoding表示对象的内部编码,占4个比特。

对于Redis支持的每种类型,都有至少两种内部编码,例如对于字符串,有int、embstr、raw三种编码。通过encoding属性,Redis可以根据不同的使用场景来为对象设置不同的编码,大大提高了Redis的灵活性和效率。以列表对象为例,有压缩列表和双端链表两种编码方式;如果列表中的元素较少,Redis倾向于使用压缩列表进行存储,因为压缩列表占用内存更少,而且比双端链表可以更快载入;当列表对象元素较多时,压缩列表就会转化为更适合存储大量元素的双端链表。

通过object encoding命令,可以查看对象采用的编码方式,如下图所示:

5种对象类型对应的编码方式以及使用条件,将在后面介绍。

(3)lru

lru记录的是对象最后一次被命令程序访问的时间,占据的比特数不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。

通过对比lru时间与当前时间,可以计算某个对象的空转时间;object idletime命令可以显示该空转时间(单位是秒)。object idletime命令的一个特殊之处在于它不改变对象的lru值。

lru值除了通过object idletime命令打印之外,还与Redis的内存回收有关系:如果Redis打开了maxmemory选项,且内存回收算法选择的是volatile-lru或allkeys—lru,那么当Redis内存占用超过maxmemory指定的值时,Redis会优先选择空转时间最长的对象进行释放。

(4)refcount

refcount与共享对象

refcount记录的是该对象被引用的次数,类型为整型,占4个字节。refcount的作用,主要在于对象的引用计数和内存回收。当创建新对象时,refcount初始化为1;当有新程序使用该对象时,refcount加1;当对象不再被一个新程序使用时,refcount减1;当refcount变为0时,对象占用的内存会被释放。

Redis中被多次使用的对象(refcount>1),称为共享对象。Redis为了节省内存,当有一些对象重复出现时,新的程序不会创建新的对象,而是仍然使用原来的对象。这个被重复使用的对象,就是共享对象。目前共享对象仅支持整数值的字符串对象。

共享对象的具体实现

Redis的共享对象目前只支持整数值的字符串对象。之所以如此,实际上是对内存和CPU(时间)的平衡:共享对象虽然会降低内存消耗,但是判断两个对象是否相等却需要消耗额外的时间。对于整数值,判断操作复杂度为O(1);对于普通字符串,判断复杂度为O(n);而对于哈希、列表、集合和有序集合,判断的复杂度为O(n^2)。

虽然共享对象只能是整数值的字符串对象,但是5种类型都可能使用共享对象(如哈希、列表等的元素可以使用)。

就目前的实现来说,Redis服务器在初始化时,会创建10000个字符串对象,值分别是0~9999的整数值;当Redis需要使用值为0~9999的字符串对象时,可以直接使用这些共享对象。10000这个数字可以通过调整参数REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值进行改变。

共享对象的引用次数可以通过object refcount命令查看,如下图所示。命令执行的结果页佐证了只有0~9999之间的整数会作为共享对象。

(5)ptr

ptr指针指向具体的数据,如前面的例子中,set hello world,ptr指向包含字符串world的SDS。ptr指针占据的字节数与系统有关,例如64位系统中占8个字节。

(6)总结

综上所述,redisObject的结构与对象类型、编码、内存回收、共享对象都有关系;在64位系统中,一个redisObject对象的大小为16字节:

4bit+4bit+24bit+4Byte+8Byte=16Byte。

4、SDS

Redis没有直接使用C字符串(即以空字符’\0’结尾的字符数组)作为默认的字符串表示,而是使用了SDS。SDS是简单动态字符串(Simple Dynamic String)的缩写。

(1)SDS结构

sds的结构如下:

1

2

3

4

5

struct sdshdr {

    int len;

    int free;

    char buf[];

};

其中,buf表示字节数组,用来存储字符串;len表示buf已使用的长度,free表示buf未使用的长度。下面是两个例子。

图片来源:《Redis设计与实现》

通过SDS的结构可以看出,buf数组的长度=free+len+1(其中1表示字符串结尾的空字符);所以,一个SDS结构占据的空间为:free所占长度+len所占长度+ buf数组的长度=4+4+free+len+1=free+len+9。

(2)SDS与C字符串的比较

SDS在C字符串的基础上加入了free和len字段,带来了很多好处:

  • 获取字符串长度:SDS是O(1),C字符串是O(n)
  • 缓冲区溢出:使用C字符串的API时,如果字符串长度增加(如strcat操作)而忘记重新分配内存,很容易造成缓冲区的溢出;而SDS由于记录了长度,相应的API在可能造成缓冲区溢出时会自动重新分配内存,杜绝了缓冲区溢出。
  • 修改字符串时内存的重分配:对于C字符串,如果要修改字符串,必须要重新分配内存(先释放再申请),因为如果没有重新分配,字符串长度增大时会造成内存缓冲区溢出,字符串长度减小时会造成内存泄露。而对于SDS,由于可以记录len和free,因此解除了字符串长度和空间数组长度之间的关联,可以在此基础上进行优化:空间预分配策略(即分配内存时比实际需要的多)使得字符串长度增大时重新分配内存的概率大大减小;惰性空间释放策略使得字符串长度减小时重新分配内存的概率大大减小。
  • 存取二进制数据:SDS可以,C字符串不可以。因为C字符串以空字符作为字符串结束的标识,而对于一些二进制文件(如图片等),内容可能包括空字符串,因此C字符串无法正确存取;而SDS以字符串长度len来作为字符串结束标识,因此没有这个问题。

此外,由于SDS中的buf仍然使用了C字符串(即以’\0’结尾),因此SDS可以使用C字符串库中的部分函数;但是需要注意的是,只有当SDS用来存储文本数据时才可以这样使用,在存储二进制数据时则不行(’\0’不一定是结尾)。

(3)SDS与C字符串的应用

Redis在存储对象时,一律使用SDS代替C字符串。例如set hello world命令,hello和world都是以SDS的形式存储的。而sadd myset member1 member2 member3命令,不论是键(”myset”),还是集合中的元素(”member1”、 ”member2”和”member3”),都是以SDS的形式存储。除了存储对象,SDS还用于存储各种缓冲区。

只有在字符串不会改变的情况下,如打印日志时,才会使用C字符串

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

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

相关文章

Python入门(三)

序列 序列是有顺序的数据集合。序列包含的一个数据被称为元素,序列可以由一个或多个元素组成,也是可以没有任何元素的空序列。 序列的类型 元组(定值表):一旦建立,各个元素不可再更变,所以一…

建设IAM/IDM统一身份管理,实现系统之间的单点登录(SSO)

企业实施身份管理的现状: 1.身份存储分散,不能统一供应诸多应用系统,企业用户信息常常存在于多个系统,如HR系统有一套用户信息,OA系统也有一套用户信息,身份存储不集中,不能统一地为诸多应用系…

ChatGPT :确定性AI源自于确定性数据

ChatGPT 幻觉 大模型实际应用落地过程中,会遇到幻觉(Hallucination)问题。对于语言模型而言,当生成的文本语法正确流畅,但不遵循原文(Faithfulness),或不符合事实(Factua…

clickhouse学习笔记01(小滴课堂)

老王经历-数据库架构演变历史 你是否能分清OLTP和OLAP系统 急速掌握-数据库里面行存储和列式存储 新一代列式存储ClickHouse介绍和应用场景说明 Linux服务器容器化部署ClickHouse实战 记得要在安全组里配置开放端口号。 到这我们就安装完了。 简单使用: 创建你的第…

C语言基础练习——Day10

目录 选择题 编程题 不用加减乘除做加法 找到所有数组中消失的数字 选择题 1、求函数返回值,传入-1,则在64位机器上函数返回 int func(int x) {int count 0;while (x){count;x x&(x - 1);//与运算}return count; } A 死循环B 64C 32D 16 答案&…

C# 打开文件对话框(OpenFileDialog)

OpenFileDialog&#xff1a;可以打开指定后缀名的文件&#xff0c;既能单个打开文件也能批量打开文件 /// <summary>/// 批量打开文档/// 引用&#xff1a;System.Window.Fomrs.OpenFileDialog/// </summary>public void OpenFile(){OpenFileDialog dialog new Op…

【解读】保障软件供应链安全:SBOM推荐实践指南(含指南获取链接)

2023年11底&#xff0c;美国NSA&#xff08;National Security Agency&#xff09;、CISA&#xff08;Cybersecurity and Infrastructure Security Agency&#xff09;等多个政府机构部门组成的ESF&#xff08;Enduring Security Framework&#xff0c;持久安全框架&#xff09…

力扣L15--- 67.二进制求和(JAVA版)-2024年3月17日

1.题目描述 2.知识点 注1&#xff1a; 二进制用 %2 /2 3.思路和例子 采用竖位相加的方法 4.代码实现 class Solution {public String addBinary(String a, String b) {StringBuilder sbnew StringBuilder();int ia.length()-1;int jb.length()-1;int jinwei0;int digit1,d…

龙芯新世界系统(安同AOCS OS)安装Cinnamon桌面最新版6.0.4

龙芯的新世界系统安同AOCS OS是十分优秀的操作系统&#xff0c;处于纯社区方式运行&#xff0c;她的各组件更新得很及时&#xff0c;很多组件都处于最新的状态&#xff0c;给我们安装使用最新的开源软件提供了很好的基础。由于本人一直使用Cinnamon桌面环境&#xff0c;各方面都…

C++初阶:类与对象(尾篇)

目录 1. 构造函数与初始化列表1.1 对象的创建与构造函数的初始化1.2 初始化列表及构造函数存在的意义1.3 explicit关键字与构造函数的类型转换 2. static成员变量与static成员函数2.1 static成员变量2.2 static成员函数 3. 日期类流插入操作符的重载与友元3.1 友元3.2 友元函数…

【OpenGL手册19】几何着色器

目录 一、说明 二、渲染管线的逻辑 三、几何着色器 四、使用几何着色器 五、造几个房子 六、几何着色器渲染爆破物体 一、说明 如果说用顶点和片段着色器干了什么&#xff0c;其实不多。加入几何着色器&#xff0c;能够加大渲染能力&#xff0c;简化数据结构&#xff0c;…

【Linux】对进程PCB的理解查看进程信息的方法

一、学习准备&#xff1a;对操作系统工作模式的理解 首先我们要清楚的是&#xff0c;操作系统是一个进行软硬件资源管理的软件。操作系统对下要管理好底层硬件。每一个硬件的生产产商都会给他们的产品提供对应的驱动程序&#xff0c;驱动程序是特定于某一硬件或系统设备的软件组…

参与者中心方案设计

参与者中心方案设计 ⼀、背景介绍 为适应客⼾多元化&#xff0c;随着业务发展&#xff0c;需要⽀持以平台为基础⽀持多租⼾企业⼊驻⽅式进⾏对外放。 1.1 需求来源 客⼾、产品 1.2 需求描述 ⽀持多租⼾ ⽀持客⼾⾃定义⻆⾊、菜单权限 ⽀持根据不同⻆⾊设置不同数据权限控…

备战蓝桥杯Day26 - 二叉搜索树查询和删除操作

一、查询 递归查询 寻找的值比根节点大&#xff0c;遍历右子树&#xff1b; 寻找的值比根节点小&#xff0c;遍历左子树。 def qurey(self, node, val):if not node: # 没有节点&#xff0c;返回空return Noneif node.data < val:return self.qurey(node.rchild, val)el…

Spring MVC 如何返回响应

上期我们讲请求的时候&#xff0c;每个方法返回的数据就是响应&#xff0c;我们也可以返回一个静态页面&#xff0c;设置响应的状态码&#xff0c;Header信息等。 1. 返回静态页面 我们先在项目的static文件夹下创建一个HTML文件作为我们返回的页面&#xff1a; <!DOCTYPE…

OLAP与数据仓库和数据湖

OLAP与数据仓库和数据湖 本文阐述了OLAP、数据仓库和数据湖方面的基础知识以及相关论文。同时记录了我如何通过ChatGPT以及类似产品&#xff08;通义千问、文心一言&#xff09;来学习知识的。通过这个过程让我对于用AI科技提升学习和工作效率有了实践经验和切身感受。 预热 …

粤嵌6818嵌入式开发入门教程

学习目标 1.了解嵌入式开发 2.开发环境的搭建 3.Linux操作系统的基本操作 一、了解嵌入式开发 以应用为中心&#xff0c;以计算机技术为基础&#xff0c;软硬件可裁剪&#xff0c;适应应用系统对功能、可靠性、成本、体积、功耗严格要求的专用计算机系统。 1.嵌入式可以干…

再见 Pandas,又一数据处理神器

cuDF介绍 cuDF是一个基于Apache Arrow列内存格式的Python GPU DataFrame库&#xff0c;用于加载、连接、聚合、过滤和其他数据操作。cuDF还提供了类似于pandas的API。 GitHub&#xff1a; https://github.com/rapidsai/cudf Documentation&#xff1a; https://docs.rapids.a…

基于springboot+vue的大学生就业需求分析系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

2.亿级积分数据分库分表:增量数据同步之代码双写,为什么没用Canal?

1.亿级积分数据分库分表&#xff1a;总体方案设计 上一篇博客中写了一下积分数据分库分表的总体方案设计&#xff0c;里面说了采用应用程序代码双写的方式实现的增量数据同步&#xff0c;本篇就对这一块进行一些细化的介绍&#xff0c;包括&#xff1a; 为什么不用Canal监听数…