专栏名称: Java知音
专注于Java,推送技术文章,热门开源项目等。致力打造一个有实用,有情怀的Java技术公众号!
目录
相关文章推荐
数据中心运维管理  ·  巡检必须关注这些指标 ·  22 小时前  
数据中心运维管理  ·  全面解读数据中心液冷散热技术的政策、类型及应用 ·  2 天前  
数据中心运维管理  ·  运营商智算中心建设思路及方案 ·  3 天前  
AustinDatabases  ·  OceanBase ... ·  2 天前  
AustinDatabases  ·  OceanBase ... ·  2 天前  
macrozheng  ·  300 秒到 4 秒,如何将 MySQL ... ·  3 天前  
51好读  ›  专栏  ›  Java知音

面试官:为什么删除缓存后,Redis内存占用依然很高?

Java知音  · 公众号  · 数据库  · 2024-12-26 10:05

主要观点总结

本文主要介绍了Redis内存碎片的问题,包括什么是内存碎片、内存碎片的原因、如何判断内存碎片以及清理内存碎片的方法。

关键观点总结

关键观点1: 什么是内存碎片

以火车卖票为例解释了内存碎片的概念,即应用程序需要的连续内存空间无法在满足需求的空闲内存中找到,造成部分内存空间闲置。

关键观点2: 内存碎片的原因

内存碎片大致分为两个原因:1. 内存分配策略局限性,导致实际分配的内存空间大于实际申请的;2. 键值对的修改、删除导致内存的扩容或释放,产生不连续的空闲内存块。

关键观点3: 如何判断内存碎片

通过Redis的INFO命令查询内存的使用情况,重点关注mem_fragmentation_ratio的值来判断内存碎片情况。

关键观点4: 清理内存碎片的方法

介绍Redis清理内存碎片的方式,包括自动清理的触发条件和控制CPU时间的参数,以确保在清理过程中不影响正常处理请求。


正文

前言

上周刚来了个应届小师弟,组长说让我带着,周二问了我这样一个问题:师兄啊,我用 top 命令看了下服务器的内存占用情况,发现 Redis 内存占用严重,于是我就删除了大部分不用的 keys ,为什么内存占用还是很严重,并没有释放呢?

嗯?为什么呢?今天就带着这个问题来介绍一下如何正确释放 Redis 的内存。

什么是内存碎片?

内存碎片这个概念应该不是第一听说了,熟悉JVM或者操作系统的应该都熟悉,以火车卖票为例,一个车厢 128 个车位,由于高峰期,只剩余 两个 位置了,但是此时 三个 人想要坐在一起,能够吹吹牛批,喝喝酒的,那么这三个人肯定不会买这节车厢的两个位置了,此时这两个位置可以称之为座位碎片 。

操作系统中对于内存分配也是一样的,比如应用需要申请一块连续N个字节的空间,虽然剩余内存总量大于 N 个字节,但是没有一块连续的内存空间是 N 个字节,那么剩余的空间就是内存碎片。如下图:

上图中的空闲3个字节和空闲2个字节都是内存碎片。

那么什么原因会造成内存碎片呢?这个其实大致分为两个原因,一个是操作系统的内存分配策略,一个是Redis自身原因,下面就这两个原因详细分析。

内存分配器的分配策略

内存分配器的分配策略一般是按照固定大小来分配内存,而不是按照应用程序申请的内存空间按需分配。比如8字节、16字节、32字节......

Redis 提供了多种的内存分配策略,比如 libc jemalloc tcmalloc ,默认使用 jemalloc

jemalloc 这种分配策略,是按照固定的空间分配,比如8字节、32字节....2KB、4KB等。当应用程序申请的内存接近某个固定值的时候, jemalloc 则会分配固定的大小。比如申请了6字节,则会分配8字节的空间。

这种分配的方式的好处很明显,则会减少内存分配的次数,比如申请了 20字节 的内存,实际分配的是 32字节 的内存空间,当应用再写入 10字节 的数据时,则不会再次分配,剩余的 12字节 足够用了。这样就避免了一次的内存分配。如下图:

但是坏处也很明显,申请的和分配的空间不一样,则剩余的空间很可能形成内存碎片,一旦内存碎片多了,内存利用率也会随之降低,这是很可怕的。

Redis自身的原因

Redis作为键值对存储的数据库,本身键值对的大小就是不确定的,正如上面的例子中,Redis申请了20字节的空间,但实际分配却是32字节,那么剩余的12字节则会被闲置成为内存碎片。如下图:

上图中剩余12个字节空间则是闲置的,很有可能成为内存碎片,因此键值对大小不同则会造成一定的内存碎片,这是第一个原因。

第二个原因其实理解起来很简单,键值对的修改或者删除肯定会造成空间的扩容或者释放;

一方面,如果修改后的键值对变大或者变小了,势必会将占用的空间扩大或者释放不用的空间,如下图:

上图中键值对修改后变小了,从原来的10个字节变成了7个字节,从而释放了3个字节,此时剩余了5个字节的空闲空间。

另一方面,如果键值对删除了,则会释放掉占用的空间,形成空闲空间。

如何判断存在内存碎片?

这个对于运维人员来说很重要,一旦出现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.86  

上面的各种属性含义如下:

mem_fragmentation_ratio 这个指标很清楚的展示了当前内存的碎片率,比如Redis申请了1000字节,但是操作系统实际分配的内存1800个字节,则 mem_fragmentation_ratio=1800/1000=1.8

从上文也知道了,由于内存分配器的局限性,实际分配的内存绝大部分都是大于实际申请的内存,则如何通过 mem_fragmentation_ratio 这个值来衡量呢?这个值的范围在多少是正常的呢?

作者这里参照了许多开发人员的建议,列出了以下经验阀值:

  1. >1&&<1.5 :在这个范围内是合理的,毕竟大部分情况下操作系统分配的内存总是总是大于实际申请的空间。

  2. >1.5 :这表明内存碎片率已经超过 50% ,此时需要采取一些措施来降低碎片率了。

  3. <1 :what?表明实际分配的内存小于申请的内存了,很显然内存不足了,这样会导致部分数据写入到 Swap 中,之后Redis访问Swap中的数据时,延迟会变大,性能会降低。

如何清理内存碎片?

既然存在内存碎片,那么的一定有方法清除内存碎片,最简单的方法则是重启Redis

但是这也存在一些风险,如下;

  1. 如果Redis未持久化,则数据会丢失(忽略从后端恢复)

  2. 即使持久化了,但是恢复数据时长不定,这个要根据AOF和RDB文件大小决定,在恢复阶段则无法提供服务。

好在Redis 4.0-RC3版本之后,Redis自身提供了一种清除内存碎片的方法

清除的原理很简单,通过复制拷贝将不连续的存放的数据搬到一起形成一块连续的内存空间,如下图:

如上图,清除之前 A B 不是连续的,中间隔着两个字节 空闲1 ,但是在执行清除内存碎片操作之后,Redis拷贝了 B 空闲1 ,释放掉之前 B 的空间,此时 空闲1 空闲2 则变成了连续的空闲空间了。

那么问题来了,这种方式固然好,但是对于单线程的Redis来说,通过这种拷贝复制的方式显然是一种耗时的操作,性能大大降低,那么有什么好的方法呢?

Redis提供了参数配置,可以控制清除内存碎片的时机,命令如下:

config set activedefrag yes  

以上命令启动自动清理,但是具体什么时候清理,还要受以下两个参数的影响:

  1. active-defrag-ignore-bytes 400mb :如果内存碎片达到了 400mb ,开始清理(自定义)

  2. active-defrag-threshold-lower 20 :内存碎片空间占操作系统分配给 Redis 的总空间比例达到 20% 时,开始清理(自定义)

以上两个参数只有全部满足才会开始清理

除了以上触发清理内存碎片的参数,Redis还提供了两个参数来保证在清理过程中不影响处理正常的请求,如下:

  1. active-defrag-cycle-min 25 :表示自动清理过程所用 CPU 时间的比例不低于 25% ,保证清理能正常开展

  2. active-defrag-cycle-max 75 :表示自动清理过程所用 CPU 时间的比例不高于 75% ,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。







请到「今天看啥」查看全文