师兄,为什么删除数据后,Redis内存占用依然很高?

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 师兄,为什么删除数据后,Redis内存占用依然很高?

前言

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

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

什么是内存碎片?

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

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

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

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

内存分配器的分配策略

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

Redis提供了多种的内存分配策略,比如libcjemalloctcmalloc,默认使用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自身提供了一种清除内存碎片的方法

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

如上图,清除之前AB不是连续的,中间隔着两个字节空闲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,导致响应延迟升高。

以上两个参数控制了清理过程中的CPU时间占比,保证了正常处理请求不受影响

总结

本文以师弟的一个疑问开头介绍了删除数据导致内存占用还是很高的原因是存在内存碎片,导致内存碎片大致分为两个原因,如下:

  1. 内存分配策略局限性,一般都会分配固定的空间大小,导致实际分配的内存空间大于实际申请的,从而多出了许多不连续的空闲内存块。
  2. 键值对的修改、删除导致了内存的扩容或者释放,导致多余的不连续的空闲内存块。

介绍了如何通过INFO memory命令查看内存的碎片率,通过mem_fragmentation_ratio的经验阀值来判断异常。

介绍了Redis清理内存碎片的方式以、自动清理的两个触发条件、保证正常处理请求的两个控制CPU时间的参数。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
18天前
|
NoSQL 算法 Redis
redis内存淘汰策略
Redis支持8种内存淘汰策略,包括noeviction、volatile-ttl、allkeys-random、volatile-random、allkeys-lru、volatile-lru、allkeys-lfu和volatile-lfu。这些策略分别针对所有键或仅设置TTL的键,采用随机、LRU(最近最久未使用)或LFU(最少频率使用)等算法进行淘汰。
34 5
|
2月前
|
存储 缓存 NoSQL
Redis Quicklist 竟让内存占用狂降50%?
【10月更文挑战第11天】
53 2
|
3月前
|
缓存 监控 NoSQL
阿里面试让聊一聊Redis 的内存淘汰(驱逐)策略
大家好,我是 V 哥。粉丝小 A 面试阿里时被问到 Redis 的内存淘汰策略问题,特此整理了一份详细笔记供参考。Redis 的内存淘汰策略决定了在内存达到上限时如何移除数据。希望这份笔记对你有所帮助!欢迎关注“威哥爱编程”,一起学习与成长。
|
3月前
|
存储 Prometheus NoSQL
Redis 内存突增时,如何定量分析其内存使用情况
【9月更文挑战第21天】当Redis内存突增时,可采用多种方法分析内存使用情况:1)使用`INFO memory`命令查看详细内存信息;2)借助`redis-cli --bigkeys`和RMA工具定位大键;3)利用Prometheus和Grafana监控内存变化;4)优化数据类型和存储结构;5)检查并调整内存碎片率。通过这些方法,可有效定位并解决内存问题,保障Redis稳定运行。
167 3
|
4月前
|
存储 NoSQL 算法
Redis内存回收
Redis 基于内存存储,性能卓越,但单节点内存不宜过大,以免影响持久化或主从同步。可通过配置 `maxmemory` 限制最大内存。内存达到上限时,Redis采用两种策略:内存过期策略和内存淘汰策略。过期策略包括惰性删除和周期删除,后者分为 SLOW 和 FAST 模式。内存淘汰策略有八种,如 LRU、LFU 和随机淘汰等,用于在内存不足时释放空间。官方推荐使用 LFU 算法。
Redis内存回收
|
3月前
|
缓存 NoSQL 算法
14)Redis 在内存用完时会怎么办?如何处理已过期的数据?
14)Redis 在内存用完时会怎么办?如何处理已过期的数据?
72 0
|
3月前
|
存储 缓存 NoSQL
Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
Redis 过期删除策略与内存淘汰策略的区别及常用命令解析
83 0
|
27天前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
211 1
|
17天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
26天前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
下一篇
DataWorks