Redis的内存碎片是怎样产生的

举报
芥末拌个饭吧 发表于 2022/11/15 09:28:50 2022/11/15
【摘要】 如果我们的Redis存了很多数据,当删除很多旧数据后,按理说内存空间会释放出来。但是我们去查看内存信息时,会发现内存占用率还是很高,并没有因为数据的删除把内存空间腾出来。这是怎么回事呢?这篇文章给你答案。

前言

如果我们的Redis存了很多数据,当删除很多旧数据后,按理说内存空间会释放出来。但是我们去查看内存信息时,会发现内存占用率还是很高,并没有因为数据的删除把内存空间腾出来。这是怎么回事呢?这篇文章给你答案。

什么是内存碎片

redis数据删除后,释放的内存不是直接给回操作系统,而是交由内存分配器来管理,因此我们用top查询时会发现内存依然还记录在redis中。

而且,Redis释放的内存空间不一定是连续的,不连续的空间可能会导致一个问题,那就是:这些空间已经闲置下来了,但是Redis不能用到它来存储数据。这样就会降低服务器的内存利用率。

怎么理解这个问题呢?举个例子如下图:

有一片内存空间,在使用的是红色块,闲置的是灰色块。表面看起来,我们有1、5、6这三个内存块闲置可用。

但实际上呢?比如我现在有个数据结构需要申请3个内存块,因为内存分配器在分配内存时,是从闲置内存中取连续的空间出来。但我们闲置内存中没有连续的3个内存块。因此,1、5、6这三个块就用不了,也就是我们所说的内存碎片。

那么内存碎片是如何形成的呢?主要有两方面:操作系统的内存分配策略Redis的负载特性

操作系统的内存分配策略

为何说内存分配策略会产生内存碎片呢?因为内存分配器在分配内存时,不是按照程序申请的内存大小来分配,而是按照固定的大小来分配。

比如Redis要申请26个字节来保存一个数据,但是内存分配器可不会按照你的要求只给你分26个字节。它会怎么做呢?

我们需要知道,内存分配器有libc、jemalloc、tcmalloc等,Redis默认使用jemalloc分配器。jemalloc的分配策略是按照固定的大小来划分内存,比如有2KB、4KB、16字节、32字节等。当程序申请的内存最接近某个固定值时,jemallo就会分配这么多空间。(注意:申请的内存要<固定值)。

因此,上面我们说的申请26个字节时,jemalloc会给我们32个字节的空间。这样的预留机制,本身是为了减少修改数据时的分配次数。比如下次要多写3个字节,因为第一次申请就多了6个字节,这样就不必再向系统申请空间了。

话说回来,这种机制本质上是为了减少分配次数。但是这种分配方式容易形成内存碎片。

Redis如何形成内存碎片

第一个形成原因:由内存分配器的策略,我们很容易知道Redis在申请内存空间时,会形成部分的碎片。

举个例子:

我们用Redis要保存一个数据,数据大小为5字节,那么按照内存分配策略会给我们8字节的空间。如果不修改数据的话,就会有3个字节闲置下来,形成内存碎片。

除了申请时会产生碎片,还有第二个原因:键值对在申请后如果被修改或删除,会导致内存空间的扩容或释放,也会产生碎片。 修改数据时若占用的空间有变化,此时就需要扩容或者缩容;而删除数据也会将内存释放出来,形成碎片。

结合下图例子来说更好理解。

  1. 第一阶段:各应用占用空间是A:2,B:1,C:3,D:3;
  2. 第二阶段:D释放了一个字节空间;
  3. 第三阶段:A修改了数据,增加了一个字节。为保证A的内存空间连续性,B的数据拷贝到了第二阶段D释放出来的那个字节位置。
  4. D又释放出一个字节空间,C也释放2个字节空间。

这样一来,C和D之间、D和B之间就分别多了1字节和2字节的闲置空间。接下来如果有应用要申请3个字节的空间,这个闲置的空间不是连续,因此就满足不了要求了。

内存碎片的存在,会降低Redis对内存的实际使用率,造成内存资源的浪费。

如何判断有内存碎片

Redis本身基于内存,如果有大量的内存碎片存在,会直接影响内存使用率的指标。我们在登录redis客户端后,可用INFO memory来查看当前Redis的内存使用情况。

127.0.0.1:6379> info memory
# Memory
used_memory:1052400
used_memory_human:1.00M
used_memory_rss:1798144
used_memory_rss_human:1.71M
used_memory_peak:1070320
used_memory_peak_human:1.02M
used_memory_peak_perc:98.33%
used_memory_overhead:1038254
used_memory_startup:987856
...
mem_fragmentation_ratio:1.79

我们执行INFO memory后,往下翻可以看到mem_fragmentation_ratio的指标,它就是当前的内存碎片率。

计算口径是:

mem_fragmentation_ratio = used_memory_rss/ used_memory

used_memory_rss表示实际分配的内存空间大小,used_memory是Redis申请的空间大小。比如保存一个数据申请了28字节(used_memory),内存分配器给了32字节(used_memory_rss),则内存碎片率mem_fragmentation_ratio=32/28,也就是1.14。

业界上对mem_fragmentation_ratio指标是否良好有个经验:

  • 1< mem_fragmentation_ratio < 1.5:此时属于正常情况。内存碎片无法避免,特别是内存分配器机制造成的碎片。存在部分碎片属于正常现象。
  • mem_fragmentation_ratio > 1.5:这说明碎片率已超过50%,需要采取响应措施来降低碎片率。

如何清理内存碎片

先说个不谨慎的方案:直接重启Redis实例。

但这种方案会带来2个问题:

  • 若Redis的数据没有持久化,数据会丢失;
  • 重启需要通过AOF或RDB恢复数据,恢复时间取决于日志的大小。如果恢复时间长,这个实例就不能提供服务了。

更好的方案:Redis内存碎片自动清理

Redis4.0之后,提供了内存碎片自动清理的功能。其实内存碎片清理,原理很简单。我们以这个为例:

  1. 清理前,C和D之间、D和B之间就分别多了1字节和2字节的内存碎片;
  2. 清理过程:将B和D的数据分别拷贝到C和D之间的闲置空间,这样3个字节的闲置空间就形成了连续空间。当新应用要申请3个字节的空间时,这个闲置空间就能利用起来了。

当然,碎片清理需要付出相应的代价。系统需要将数据拷贝到新位置,并释放原有位置的空间,这些开销都会影响Redis的性能。因为拷贝数据的过程,Redis只能处于等待状态。

我们可通过配置来启用内存碎片自动清理功能,配置项为:

config set activedefrag yes

开启后,Redis会在以下2个条件都满足的情况下触发内存清理:

  • active-defrag-ignore-bytes 100mb:表示碎片大于100M时清理。
  • active-defrag-threshold-lower 10:表示碎片占系统分配给Redis空间的10%时清理。

当触发内存清理时,清理功能还会实时监控CPU的情况,保证Redis的正常使用。

  • active-defrag-cycle-min 25:指的是清理过程cpu使用时间的比例不低于25%;
  • active-defrag-cycle-max 75:指的是清理过程cpu使用时间的比例不高于75%。

redis这种内存碎片自动清理机制,考虑到了碎片清理的必要性以及对Redis使用的影响。

小结

本文总结了Redis产生内存碎片的2大原因,分别是由内存分配机制和自身数据修改删除导致的。了解了内存碎片的产生原因,我们才能对症下药。

Redis4.0之后,我们就可以通过配置开启内存碎片自动清理功能。当碎片率偏高的时候,我们还可以手动清理内存碎片,但是要考虑到清理过程对Redis性能的影响。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。