redis内存空间效率问题的深入探究


Posted in Redis onMay 17, 2021

前言

在使用redis时,我们会遇到一个问题,数据删除后,数据量已经不大了,但是使用top命令查看,还会发现redis占用了很对内存。实际上,因为数据删除后,redis释放内存由内存分配器管理,不会立刻返回给操作系统。所以,操作系统仍然记录着给redis分配了大量的内存

这往往会伴随一个潜在的风险点:Redis 释放的内存空间可能并不是连续的,那么,这些不连续的内存空间很有可能处于一种闲置的状态。这就会导致一个问题:虽然有空闲空间,Redis 却无法用来保存数据,不仅会减少 Redis 能够实际保存的数据量,还会降低 Redis 运行机器的成本回报率。

什么是内存碎片

通常情况下,内存空间利用率低,往往是因为操作系统发生了比较严重的内存碎片,那么什么是内存碎片呢?可以将内存看成是高铁上的作为,连续的空间相当于连座,内存碎片可以看成一个个零散的作为,如果你是3个人出行,火车上没有三个座位连着的,那么你就没法买到合适的作为,可能需要换一辆车

redis内存空间效率问题的深入探究

内存类似,如果需要申请一个N字节的连续空间,但是没有这么大的连续空间,那么,这些剩余空间就是内存碎片,redis内存碎片是什么原因导致的呢,了解了原因才有可能比较好的解决

内存碎片形成的原因

一般来说内存碎片形成的原因有两个,内因是操作系统的内存分配机制,外因是redis的负载特征

内因:内存分配器策略

内存分配器的分配策略就决定了操作系统无法做到“按需分配”。这是因为,内存分配器一般是按固定大小来分配内存,而不是完全按照应用程序申请的内存空间大小给程序分配。

Redis 可以使用 libc、jemalloc、tcmalloc 多种内存分配器来分配内存,默认使用 jemalloc。接下来,我就以 jemalloc 为例,来具体解释一下。其他分配器也存在类似的问题。

jemalloc 的分配策略之一,是按照一系列固定的大小划分内存空间,例如 8 字节、16 字节、32 字节、48 字节,…, 2KB、4KB、8KB 等。当程序申请的内存最接近某个固定值时,jemalloc 会给它分配相应大小的空间。

外因:键值对大小不一样和删改操作

redis通常作为公共缓存和键值数据库对外提供服务,所以对于不同大小的数据,redis申请内存空间大小不一,这是一个外因。

因为内存分配是按照固定大小分配,所以内存空间一般都会比申请的空间大一些,所以本身就会有一些内存碎片,降低内存空间存储效率。

redis内存空间效率问题的深入探究

第二个外因是,这些数据会被删除和修改,会导致空间空间扩充和释放,具体来说,一方面,如果修改后的键值对变大或变小了,就需要占用额外的空间或者释放不用的空间。另一方面,删除的键值对就不再需要内存空间了,此时,就会把空间释放出来,形成空闲空间

redis内存空间效率问题的深入探究

一开始,应用 A、B、C、D 分别保存了 3、1、2、4 字节的数据,并占据了相应的内存空间。然后,应用 D 删除了 1 个字节,这个 1 字节的内存空间就空出来了。紧接着,应用 A 修改了数据,从 3 字节变成了 4 字节。为了保持 A 数据的空间连续性,操作系统就需要把 B 的数据拷贝到别的空间,比如拷贝到 D 刚刚释放的空间中。此时,应用 C 和 D 也分别删除了 2 字节和 1 字节的数据,整个内存空间上就分别出现了 2 字节和 1 字节的空闲碎片。如果应用 E 想要一个 3 字节的连续空间,显然是不能得到满足的。因为,虽然空间总量够,但却是碎片空间,并不是连续的。

好了,到这里,我们就知道了造成内存碎片的内外因素,其中,内存分配器策略是内因,而 Redis 的负载属于外因,包括了大小不一的键值对和键值对修改删除带来的内存空间变化。

如何判断是否有内存碎片

Redis 是内存数据库,内存利用率的高低直接关系到 Redis 运行效率的高低。为了让用户能监控到实时的内存使用情况,Redis 自身提供了 INFO 命令,可以用来查询内存使用的详细信息,命令如下:

INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86O memory

INFO memory
# Memory
used_memory:1073741736
used_memory_human:1024.00M
used_memory_rss:1997159792
used_memory_rss_human:1.86G
…
mem_fragmentation_ratio:1.86

这里有一个 mem_fragmentation_ratio 的指标,它表示的就是 Redis 当前的内存碎片率。那么,这个碎片率是怎么计算的呢?其实,就是上面的命令中的两个指标 used_memory_rss 和 used_memory 相除的结果。

mem_fragmentation_ratio = used_memory_rss/ used_memory

used_memory_rss 是操作系统实际分配给 Redis 的物理内存空间,里面就包含了碎片;而 used_memory 是 Redis 为了保存数据实际申请使用的空间。

我简单举个例子。例如,Redis 申请使用了 100 字节(used_memory),操作系统实际分配了 128 字节(used_memory_rss),此时,mem_fragmentation_ratio 就是 1.28。

那么,知道了这个指标,我们该如何使用呢?在这儿,我提供一些经验阈值:

  • mem_fragmentation_ratio大于1小于1.5。这种情况是合理的。这是因为,刚才我介绍的那些因素是难以避免的。毕竟,内因的内存分配器是一定要使用的,分配策略都是通用的,不会轻易修改;而外因由 Redis 负载决定,也无法限制。所以,存在内存碎片也是正常的。
  • mem_fragmentation_ratio大于1.5。 这表明内存碎片率已经超过了 50%。一般情况下,这个时候,我们就需要采取一些措施来降低内存碎片率了。

如何清理内存碎片

当 Redis 发生内存碎片后,一个“简单粗暴”的方法就是重启redis实例,当然这并不是一个优雅的方法,重启会带来一些问题

  • 如果数据没有持久化,那么数据会丢失
  • 如果数据持久化了,我们需要通过AOF或RDB进行恢复,恢复时长取决于AOF或RDB的大小,如果只有一个实例,在恢复阶段无法提供服务。

幸运的是,从 4.0-RC3 版本以后,Redis 自身提供了一种内存碎片自动清理的方法,我们先来看这个方法的基本机制。还是通过一张图来看下

redis内存空间效率问题的深入探究

在进行碎片清理前,这段 10 字节的空间中分别有 1 个 2 字节和 1 个 1 字节的空闲空间,只是这两个空间并不连续。操作系统在清理碎片时,会先把应用 D 的数据拷贝到 2 字节的空闲空间中,并释放 D 原先所占的空间。然后,再把 B 的数据拷贝到 D 原来的空间中。这样一来,这段 10 字节空间的最后三个字节就是一块连续空间了。到这里,碎片清理结束。

需要注意:碎片清理事由代价的,操作系统需要把多份数据拷贝到新位置,把原有空间释放出来,这会带来时间开销。因为 Redis 是单线程,在数据拷贝时,Redis 只能等着,这就导致 Redis 无法及时处理请求,性能就会降低。而且,有的时候,数据拷贝还需要注意顺序,就像刚刚说的清理内存碎片的例子,操作系统需要先拷贝 D,并释放 D 的空间后,才能拷贝 B。这种对顺序性的要求,会进一步增加 Redis 的等待时间,导致性能降低。

那么,有什么办法可以尽量缓解这个问题吗?这就要提到,Redis 专门为自动内存碎片清理功机制设置的参数了。我们可以通过设置参数,来控制碎片清理的开始和结束时机,以及占用的 CPU 比例,从而减少碎片清理对 Redis 本身请求处理的性能影响。

首先,Redis 需要启用自动内存碎片清理,可以把 activedefrag 配置项设置为 yes,命令如下:

config set activedefrag yes

这个命令只是启用了自动清理功能,但是,具体什么时候清理,会受到下面这两个参数的控制。这两个参数分别设置了触发内存清理的一个条件,如果同时满足这两个条件,就开始清理。在清理的过程中,只要有一个条件不满足了,就停止自动清理。

  • active-defrag-ignore-bytes 100mb:表示内存碎片数量达到100MB时,开始清理
  • active-defrag-threshold-lower 10:表示内存碎片空间占操作系统给redis分配空间的10%时开始清理

为了尽可能减少碎片清理对 Redis 正常请求处理的影响,自动内存碎片清理功能在执行时,还会监控清理操作占用的 CPU 时间,而且还设置了两个参数,分别用于控制清理操作占用的 CPU 时间比例的上、下限,既保证清理工作能正常进行,又避免了降低 Redis 性能。这两个参数具体如下:

  • active-defrag-cycle-min 25:表示自动清理过程cpu时间笔记不低于25%,保证清理能正常开展
  • active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于 75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。

自动内存碎片清理机制在控制碎片清理启停的时机上,既考虑了碎片的空间占比、对 Redis 内存使用效率的影响,还考虑了清理机制本身的 CPU 时间占比、对 Redis 性能的影响。而且,清理机制还提供了 4 个参数,让我们可以根据实际应用中的数据量需求和性能要求灵活使用,建议你在实践中好好地把这个机制用起来。

总结

到此这篇关于redis内存空间效率问题的文章就介绍到这了,更多相关redis内存空间效率内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
Redis持久化与主从复制的实践
Apr 27 Redis
详解Redis主从复制实践
May 19 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 Redis
Redis高级数据类型Hyperloglog、Bitmap的使用
May 24 Redis
redis不能访问本机真实ip地址的解决方案
Jul 07 Redis
Redis分布式锁Redlock的实现
Aug 07 Redis
关于SpringBoot 使用 Redis 分布式锁解决并发问题
Nov 17 Redis
CentOS8.4安装Redis6.2.6的详细过程
Nov 20 Redis
Redis官方可视化工具RedisInsight安装使用教程
Apr 19 Redis
Redis特殊数据类型bitmap位图
Jun 01 Redis
利用Redis实现点赞功能的示例代码
Jun 28 Redis
redis protocol通信协议及使用详解
Jul 15 Redis
深入浅析Redis 集群伸缩原理
Redis延迟队列和分布式延迟队列的简答实现
基于Redis延迟队列的实现代码
基于Redis实现分布式锁的方法(lua脚本版)
redis三种高可用方式部署的实现
May 11 #Redis
Redis数据结构之链表与字典的使用
基于Redis位图实现用户签到功能
May 08 #Redis
You might like
解析PHP中常见的mongodb查询操作
2013/06/20 PHP
php基本函数汇总
2015/07/09 PHP
php rmdir使用递归函数删除非空目录实例详解
2016/10/20 PHP
PHP+Ajax 检测网络是否正常实例详解
2016/12/16 PHP
php常用日期时间函数实例小结
2019/07/04 PHP
nodejs win7下安装方法
2012/05/24 NodeJs
javascript学习笔记(十) js对象 继承
2012/06/19 Javascript
Node.js的基本知识简单汇总
2016/09/19 Javascript
JS实现滑动门效果的方法详解
2016/12/19 Javascript
在node.js中怎么屏蔽掉favicon.ico的请求
2017/03/01 Javascript
vue实现app页面切换动画效果实例
2017/05/23 Javascript
jquery根据name取得select选中的值实例(超简单)
2018/01/25 jQuery
JavaScript实现的简单Tab点击切换功能示例
2018/07/06 Javascript
select2 ajax 设置默认值,初始值的方法
2018/08/09 Javascript
详解vscode中vue代码颜色插件
2018/10/11 Javascript
Vue侦测相关api的实现方法
2019/05/22 Javascript
jQuery轮播图功能制作方法详解
2019/12/03 jQuery
JS中作用域以及变量范围分析
2020/07/18 Javascript
vue+iview框架实现左侧动态菜单功能的示例代码
2020/07/23 Javascript
vue-axios同时请求多个接口 等所有接口全部加载完成再处理操作
2020/11/09 Javascript
Django在Win7下的安装及创建项目hello word简明教程
2014/07/14 Python
Python实现定时备份mysql数据库并把备份数据库邮件发送
2018/03/08 Python
python 实现在txt指定行追加文本的方法
2018/04/29 Python
python smtplib模块自动收发邮件功能(一)
2018/05/22 Python
numpy linalg模块的具体使用方法
2019/05/26 Python
python调用其他文件函数或类的示例
2019/07/16 Python
python读取当前目录下的CSV文件数据
2020/03/11 Python
Python动态导入模块:__import__、importlib、动态导入的使用场景实例分析
2020/03/30 Python
css3动画效果抖动解决方法
2018/09/03 HTML / CSS
俄罗斯最大的消费电子连锁零售商:Mvideo
2017/06/25 全球购物
会计自我鉴定范文
2013/10/06 职场文书
商业街策划方案
2014/05/31 职场文书
车辆工程专业求职信
2014/06/14 职场文书
2015年普法依法治理工作总结
2015/05/26 职场文书
2016年学校党支部公开承诺书
2016/03/25 职场文书
Android Rxjava3 使用场景详解
2022/04/07 Java/Android