深入学习Redis(1):Redis内存模型

server/2024/10/18 20:20:06/

Redis的五个对象类型

字符串,哈希,列表,集合,有序集合

本节有关redis的内存模型

1.估算redis的内存使用情况

目前内存的价格比较的高,如果对于redis的内存使用情况能够进行计算,就可以选用合适的设备进行使用,可以有效的节省开支

2.优化内存占用

对于redis,选用合适的数据类型和编码,这样才能更好的利用redis现有的内存

3.分析解决问题

对于redis出现阻塞,内存占用等问题的时候,能够很快发现问题并解决

127.0.0.1:6379> info memory
# Memory
used_memory:1324768
used_memory_human:1.26M
used_memory_rss:11939840
used_memory_rss_human:11.39M
mem_fragmentation_ratio:9.30
mem_allocator:jemalloc-5.2.1

used_memory是redis分配器分配的内存总量,其中包括虚拟内存swap

used_memory_rss是redis内存占据操作系统的内存,和top/ps一致,包括内存和内存碎片

used_memory_human这个对于阅读比较的好

对于mem_fragmentation_ratio这个值来说的话,他是对于used_memory_rss/used_memory的比值,可以发现这个等式,如果redis启用了虚拟内存swap的话,这个比值就可能变成小于1,这个时候就需要对于redis进行问题排查,应为磁盘太慢了,对于内存增加的方法有,增加redis节点,对于redis的服务器内存进行增加,优化应用,也就是分为横向和纵向的区别

如果内存中并未存入什么数据,那么就会造成这个比值过大

mem_allocator这个参数是指当前使用的内存分配器的版本

Redis内存的划分

1.数据

数据是有内存分配器分配的内存,会被统计在used_memory中

五种类型,分别是字符串,哈希,列表,集合,有序集合,这五种类型是对外提供的,当然,在内部还有基数统计,位图,地理位置,对于对象需要进行redisObject,SDS的包装,才会被放到内存中去

2.进程本身需要的内存

redis本身的代码,例如代码,常量池,这些占据了几兆,这点空间可以忽略不计,然后应为不经过jemalloc的分配,所以不被记录到used_memory中

3.缓冲内存

缓冲内存包括客户端的缓冲区,复制积压缓冲区,AOF缓冲区等,其中客户端缓存客户端连接输入输出缓存;复制积压缓冲区用户部分复制功能,AOF缓冲区对于AOF进行重写,保存最近的写入命令。这部分是由jemalloc来分配的,所以记录到used_memory当中

4.内存碎片

对于数据进行频繁修改之后,数据之间的大小相差就很大,导致redis释放得空间在实际得物理内存中并没有释放,redis也无法合理利用,导致了内存碎片。内存碎片不会统计到used_memory当中。

内存碎片的产生是多方面的,和对于内存的操作,数据的特点等都有关,内存分配器也有关系,设计的好,内存碎片产生的也少,jemalloc在这方面做的很好

redis中的内存碎片很大的时候,可以进行安全重启,在重启之后,redis可以从备份的文件中读取数据,在内存中进行重排,为每个数据进行选择合适的内存单元,减小内存的碎片

redis数据的存储细节

前面提到,对于数据我们是需要对他进行封装,包装成redisObject,或者SDS

在这张图片中,可以看到dictEntry是基本的单位,每一个键值对都有一个dictEntry,然后就里面也有一个这样的指针,然后就指向过去,里面的key可以看到是存储在SDS结构中,然后就是值,这个值不是放在sds里面的,是存储在redisObject里面,然后type字段表面这是sds类型,然后ptr指针指向了sds对象所在的地址,里面存放着值,值也是需要sds结构进行存储的

之后就是jemalloc对于上面说的dictEntry对象、redisObject、SDS对象都进行了内存的分配,dictEntry这个对象有三个指针,也就是3*8字节为24字节,然后就向上取整,给他分配了一个32字节大小的内存单元

jemalloc

redis对于内存分配器进行选择的时候,内存分配器有libc,jemalloc,tcmalloc,三个,当然默认的是jemalloc,这一个在64位系统中,对于内存空间进行了小中大的三个范围的划分,当然,在每一个范围内又进行了更加细致的划分,

就像这里如果需要存储一个字节的对象,那么就需要字节的内存单元中

redisObject

redis中有五种对象,现在有八种,不管哪一种,redis都不会进行直接存储,都会对于redisObject对象进行存储

redisObject对象中包含了redis对象的类型,内部编码,内存回收,共享对象等功能,都需要redisObject的支持,

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;

type,对象的类型,也就是那八个

直接使用type的命令

encoding 在字符串中有三种编码 int emmbstr raw

对于列表对象来说,元素少的时候,就是int型,如果元素多的话,会抓换成embstr类型

int是压缩列表,embstr是双端链表

lru记录的是对象最后一次被程序访问的时间,如果你想看,可以使用object idletime命令进行查看

这条指令并不会修改lru

lru和redis的内存回收算法是有关系的,如果是redis打开了maxmemory选项,那么内存回收算法选择的是volatile-lru或allkeys-lru,这两种算法,每当redis的内存超过maxmemory的时候,redis就会优先选择lru的空转时间最长的对象进行释放

还有一个refcount

这个东西是和共享对象有关的,新对象创建的时候,初始话为1,之后如果有新程序使用了该对象,这个refcount+1没使用了就-1,如果refcount=0的时候,说明对象占用的内存会被释放

redis目前的共享对象支持的只有整数值的字符串对象,但是其他八种都可能使用共享对象

这里要注意,本篇文章是redis3.0那个时候还是五种,这里的八种我认为是可以延伸的,如果有不正确的,请指正

哈希,列表可以使用这种整数值的字符串对象

一般来说会初始化10000个字符串对象,分别是0~9999,如果你使用的话,就是使用共享对象了

最新版好像是一个好大好大的值

这一万个数字是可以进行调整的,可以自己百度下自己的版本怎么调整

之后就只剩下一个ptr了

ptr就是指向具体的数据了,这里可以认为指向的就是SDS结构,这个ptr的大小个系统有关

redis的所占大小就可以算了type是4个比特,encoding也是4个比特,然后就是lru,这个就看你的版本了,4.0是24比特,3.0是22比特,然后就是refcount一个int,4个字节,然后就剩下一个ptr指针了,现在都是64位的系统,所以都是8个字节,然后算一算就是16字节

接下来就是SDS结构了

SDS是简单动态字符串(Simple Dynamic String)

struct sdshdr {int len;int free;char buf[];
};

buf数组用来存放字符串的,len就是已经使用的长度,free就是还没有使用的长度

buf数组的长度就可以通过len+buf+1应为字符串都是末尾来个空字符'\0'来表示结束的

sds的优点为,可以在O(1)的情况下查找长度,获取字符串长度是O(1)的,这里我就想到了go语言中的切片slice了,里面也存在着cap最大容量上限

然后再缓冲区溢出方面,直接使用C字符串的api会导致溢出,如果字符串的长度增加的话,你没有分配内存就会导致缓冲区的溢出,这里的sds记录了长度,在对应的api造成缓冲区溢出的时候,可以自动分配内存,防止了缓冲区的溢出

在修改字符串内存方面的重分配,如果是C字符串的话,需要重新分配,也就是释放再重新申请,如果,字符串长度增大会导致内存缓冲区溢出,字符串长度减少时会造成内存泄漏。对于sds来说,应为有了len 和free,空间预分配之后,字符串长度增大在重新分配的概率就小很多,应为一般我们都会分配多一点内存给他们,在内存释放方面,对于值进行修改之后,直接修改len和free就可以了,所以也很方便

对于二进制数据来说,C字符串不可以存,但是sds是可以进行存储的,应为二进制数据中可能有'\0'这种,但是sds中有len这个长度,所以可以规避这个错误

如果sds里面存储的是文本数据时可以使用C字符串的函数,但是二进制数据不行,应为这个可能是以'\0'数据也可能在数据里面,会出错

在打印日志以及不更改字符串的情况下,才会使用C字符串,不然一般都是使用sds结构体

转换过程是不可逆的

只能是小内存编码向大内存编码进行转换


http://www.ppmy.cn/server/33456.html

相关文章

linux基本操作

vim的基本操作 正常模式:启动vim后默认处于正常模式。不论位于什么模式,按下Esc建都会进入正常模式。 插入模式:在正常模式中按下i,l,a,A等键,会进入插入模式。现在只用记住按i键会进行插入模…

时间复杂度_空间复杂度

时间复杂度_空间复杂度 1.算法效率 算法效率分析分为两种:第一种是时间效率,第二种是空间效率。 时间效率被称为时间复杂度,而空间效率被称作空间复杂度。时间复杂度主要衡量的是一个算法的运行速度,而空间复杂度主要衡量一个算法所需要的…

超强动画制作软件blender

blender中文手册:Blender 4.1 Manual Blender 是一款集3D建模、渲染、动画、视频编辑、音频处理、游戏设计等多功能于一体的软件。由于其开源性质,它拥有庞大的用户群体和活跃的开发者社区,这使得Blender的功能和性能得到了不断的提升和优化…

Adobe 更新 Firefly Image 3 图像生成模型

一个工具或者模型,对于初次使用的人来说,易用性和超出预期的效果很能吸引使用者,suno和mj在这方面我感觉确实不错,第一次使用感觉很惊艳。 Adobe 更新 Firefly Image 3 图像生成模型,我用了mj的提示词,最后…

C 408—《数据结构》图、查找、排序专题考点(含解析)

目录 Δ前言 六、图 6.1 图的基本概念 6.2 图的存储及基本操作 6.3 图的遍历 6.4 图的应用 七、查找 7.2 顺序查找和折半查找 7.3 树型查找 7.4 B树和B树 7.5 散列表 八、排序 8.2 插入排序 8.3 交换排序 8.4 选择排序 8.5 归并排序和基数排序 8.6 各种内部排序算法的比较及…

OpenHarmony实战开发-如何实现单一手势

点击手势(TapGesture) TapGesture(value?:{count?:number; fingers?:number})点击手势支持单次点击和多次点击,拥有两个可选参数: count:声明该点击手势识别的连续点击次数。默认值为1,若设置小于1的非…

Python中class类和方法的用法详解及常见坑

在Python中,类(class)是实现面向对象编程(OOP)的核心概念。类允许我们定义对象的模板,包括数据属性和方法。本文将深入探讨Python中的类和方法的用法,并结合实际开发中遇到的一些常见问题进行分…

C语言实验-循环结构和选择结构

一&#xff1a; 求和:1(14)(149)(14916)…(14916…n2)? 其中n的值由键盘输入&#xff1b; #define _CRT_SECURE_NO_WARNINGS #include<stdio.h>int main() {int sum 0;int n 0;printf("请输入一个整数");scanf("%d", &n);for (int i 0; i &l…