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个字节闲置下来,形成内存碎片。
除了申请时会产生碎片,还有第二个原因:键值对在申请后如果被修改或删除,会导致内存空间的扩容或释放,也会产生碎片。 修改数据时若占用的空间有变化,此时就需要扩容或者缩容;而删除数据也会将内存释放出来,形成碎片。
结合下图例子来说更好理解。
- 第一阶段:各应用占用空间是A:2,B:1,C:3,D:3;
- 第二阶段:D释放了一个字节空间;
- 第三阶段:A修改了数据,增加了一个字节。为保证A的内存空间连续性,B的数据拷贝到了第二阶段D释放出来的那个字节位置。
- 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之后,提供了内存碎片自动清理的功能。其实内存碎片清理,原理很简单。我们以这个为例:
- 清理前,C和D之间、D和B之间就分别多了1字节和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性能的影响。
- 点赞
- 收藏
- 关注作者
评论(0)