Redis 的数据被删除,内存占用还这么大?
mhr18 2024-11-11 11:56 22 浏览 0 评论
?
操作系统分配给 Redis 的内存有 6GB,通过指标 used_memory_human 发现存储数据只使用了 4GB,为何会这样?为何无法保存数据?
通过 CONFIG SET maxmemory 100mb或者在 redis.conf 配置文件设置 maxmemory 100mb Redis 内存占用限制。当达到内存最大值,会触发内存淘汰策略删除数据。
除此之外,当 key 达到过期时间,Redis 会有以下两种删除过期数据的策略:
- 后台定时任务选取部分数据删除;
- 惰性删除。
具体原理请移步《Redis 的过期数据删除那些事》。
?
假设 Redis 实例保存了 5GB 的数据,现在删除了 2GB 数据,Redis 进程占用的内存一定会降低么?(也叫做 RSS,进程消耗内存页数)。
答案是:可能依然占用了大约 5GB 的内存,即使 Redis 的数据只占用了 3GB 左右。
大家一定要设置maxmemory,否则 Redis 会继续为新写入的数据分配内存,无法分配就会导致应用程序报错,当然不会导致宕机。
释放的内存去哪了
?
明明删除了数据,使用 top 命令查看,为何还是占用了那么多内存?
内存都去哪了?使用 info memory 命令获取 Redis 内存相关指标,我列举了几个重要的数据:
127.0.0.1:6379> info memory
# Memory
used_memory:1132832 // Redis 存储数据占用的内存量
used_memory_human:1.08M // 人类可读形式返回内存总量
used_memory_rss:2977792 // 操作系统角度,进程占用的物理总内存
used_memory_rss_human:2.84M // used_memory_rss 可读性模式展示
used_memory_peak:1183808 // 内存使用的最大值,表示 used_memory 的峰值
used_memory_peak_human:1.13M // 以可读的格式返回 used_memory_peak的值
used_memory_lua:37888 // Lua 引擎所消耗的内存大小。
used_memory_lua_human:37.00K
maxmemory:2147483648 // 能使用的最大内存值,字节为单位。
maxmemory_human:2.00G // 可读形式
maxmemory_policy:noeviction // 内存淘汰策略
// used_memory_rss / used_memory 的比值,代表内存碎片率
mem_fragmentation_ratio:2.79
Redis 进程内存消耗主要由以下部分组成:
- Redis 自身启动所占用的内存;
- 存储对象数据内存;
- 缓冲区内存:主要由 client-output-buffer-limit 客户端输出缓冲区、复制积压缓冲区、AOF 缓冲区。
- 内存碎片。
内存占用
Redis 自身空进程占用的内存很小可以忽略不计,对象内存是占比最大的一块,里面存储着所有的数据。
缓冲区内存在大流量场景容易失控,造成 Redis 内存不稳定,需要重点关注。
内存碎片过大会导致明明有空间可用,但是却无法存储数据。
碎片 = used_memory_rss 实际使用的物理内存(RSS 值)除以 used_memory 实际存储数据内存。
什么是内存碎片
内存碎片会造成明明有内存空间空闲,可是却无法存储数据。举个例子,你跟漂亮小姐姐去电影院看电影,肯定想连在一块。
假设现在有 8 个座位,已经卖出了 4 张票,还有 4 张可以买。可是好巧不巧,买票的人很奇葩,分别间隔一个座位买票。
即使还有 4 个座位空闲,可是你却买不到两个座位连在一块的票,厚礼蟹!
内存碎片
内存碎片形成原因
?
内存碎片是什么原因导致呢?
主要有两个原因:
- 内存分配器的分配策略。
- 键值对的大小不一样和删改操作:Redis 频繁做更新操作、大量过期数据删除,释放的空间(不够连续)无法得到复用,导致碎片率上升。
接下来我分别探讨实际发生的原因……
内存分配器的分配策略
Redis 默认的内存分配器采用 jemalloc,可选的分配器还有:glibc、tcmalloc。
内存分配器并不能做到按需分配,而是采用固定范围的内存块进行分配。
例如 8 字节、16 字节…..,2 KB,4KB,当申请内存最近接某个固定值的时候,jemalloc 会给它分配最接近固定值大小的空间。
这样就会出现内存碎片,比如程序只需要 1.5 KB,内存分配器会分配 2KB 空间,那么这 0.5KB 就是碎片。
这么做的目的是减少内存分配次数,比如申请 22 字节的空间保存数据,jemalloc 就会分配 32 字节,如果后边还要写入 10 字节,就不需要再向操作系统申请空间了,可以使用之前申请的 32 字节。
删除 key 的时候,Redis 并不会立马把内存归还给操作系统,出现这个情况是因为底层内存分配器管理导致,比如大多数已经删除的 key 依然与其他有效的 key 分配在同一个内存页中。
另外,分配器为了复用空闲的内存块,原有 5GB 的数据中删除了 2 GB 后,当再次添加数据到实例中,Redis 的 RSS 会保持稳定,不会增长太多。
因为内存分配器基本上复用了之前删除释放出来的 2GB 内存。
键值对大小不一样和删改操作
由于内存分配器是按照固定大小分配内存,所以通常分配的内存空间比实际数据占用的大小多一些,会造成碎片,降低内存的存储效率。
另外,键值对的频繁修改和删除,导致内存空间的扩容和释放,比如原本占用 32 字节的字符串,现在修改为占用 20 字节的字符串,那么释放出的 12 字节就是空闲空间。
如果下一个数据存储请求需要申请 13 字节的字符串,那么刚刚释放的 12 字节空间无法使用,导致碎片。
碎片最大的问题:空间总量足够大,但是这些内存不是连续的,可能大致无法存储数据。
内存碎片解决之道
?
那该如何解决呢?
首先要确定是否发生了内存碎片,重点关注前面 INFO memory 命令提示的 mem_fragmentation_ratio 指标,表示内存碎片率:
mem_fragmentation_ratio = used_memory_rss/ used_memory
如果 1 < 碎片率 < 1.5,可以认为是合理的,而大于 1.5 说明碎片已经超过 50%,我们需要采取一些手段解决碎片率过大的问题。
重启大法
最简单粗暴的方式就是重启,如果没有开启持久化,数据会丢失。
开启持久化的话,需要使用 RDB 或者 AOF 恢复数据,如果只有一个实例,数据大的话会导致恢复阶段长时间无法提供服务,高可用大打折扣。
?
咋办呢?码哥靓仔
自动清理内存碎片
既然你都叫我靓仔了,就倾囊相助告诉你终极杀招:Redis 4.0 版本后,自身提供了一种内存碎片清理机制。
?
怎么清理呢?
很简单,还是上面的例子,想要买两张连在一块的电影票。与与别人沟通调换下位置,就实现了。
对于 Redis 来说,当一块连续的内存空间被划分为好几块不连续的空间的时候,操作系统先把数据以依次挪动拼接在一块,并释放原来数据占据的空间,形成一块连续空闲内存空间。。
如下图所示:
碎片清理
自动清理内存碎片的代价
自动清理虽好,可不要肆意妄为,操作系统把数据移动到新位置,再把原有空间释放是需要消耗资源的。
Redis 操作数据的指令是单线程,所以在数据复制移动的时候,只能等待清理碎片完成才能处理请求,造成性能损耗。
?
如何避免清理碎片对性能的影响又能实现自动清理呢?
好问题,通过以下两个参数来控制内存碎片清理和结束时机,避免占用 CPU 过多,减少清理碎片对 Redis 处理请求的性能影响。
开启自动内存碎片清理
CONFIG SET activedefrag yes
这只是开启自动清理,何时清理要同时满足以下两个条件才会触发清理操作。
清理的条件
active-defrag-ignore-bytes 200mb:内存碎片占用的内存达到 200MB,开始清理;
active-defrag-threshold-lower 20:内存碎片的空间占比超过系统分配给 Redis 空间的 20% ,开始清理。
避免对性能造成影响
清理时间有了,还需要控制清理对性能的影响。由一项两个设置先分配清理碎片占用的 CPU 资源,保证既能正常清理碎片,又能避免对 Redis 处理请求的性能影响。
active-defrag-cycle-min 20:自动清理过程中,占用 CPU 时间的比例不低于 20%,从而保证能正常展开清理任务。
active-defrag-cycle-max 50:自动清理过程占用的 CPU 时间比例不能高于 50%,超过的话就立刻停止清理,避免对 Redis 的阻塞,造成高延迟。
总结
如果你发现明明 Redis 存储数据的内存占用远小于操作系统分配给 Redis 的内存,而又无法保存数据,那可能出现大量内存碎片了。
通过 info memory 命令,看下内存碎片mem_fragmentation_ratio 指标是否正常。
那么我们就开启自动清理并合理设置清理时机和 CPU 资源占用,该机制涉及到内存拷贝,会对 Redis 性能造成潜在风险。
如果遇到 Redis 性能变慢,排查下是否由于清理碎片导致,如果是,那就调小 active-defrag-cycle-max 的值。
相关推荐
- Java培训机构,你选对了吗?(java培训机构官网)
-
如今IT行业发展迅速,不仅是大学生,甚至有些在职的员工都想学习java开发,需求量的扩大,薪资必定增长,这也是更多人选择java开发的主要原因。不过对于没有基础的学员来说,java技术不是一两天就能...
- 产品经理MacBook软件清单-20个实用软件
-
三年前开始使用MacBookPro,从此再也不想用Windows电脑了,作为生产工具,MacBook可以说是非常胜任。作为产品经理,值得拥有一台MacBook。MacBook是工作平台,要发挥更大作...
- RAD Studio(Delphi) 本月隆重推出新的版本12.3
-
#在头条记录我的2025#自2024年9月,推出Delphi12.2版本后,本月隆重推出新的版本12.3,RADStudio12.3,包含了Delphi12.3和C++builder12.3最...
- 图解Java垃圾回收机制,写得非常好
-
什么是自动垃圾回收?自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未使用中的对象(未引用...
- Centos7 初始化硬盘分区、挂载(针对2T以上)添加磁盘到卷
-
1、通过命令fdisk-l查看硬盘信息:#fdisk-l,发现硬盘为/dev/sdb大小4T。2、如果此硬盘以前有过分区,则先对磁盘格式化。命令:mkfs.文件系统格式-f/dev/sdb...
- 半虚拟化如何提高服务器性能(虚拟化 半虚拟化)
-
半虚拟化是一种重新编译客户机操作系统(OS)将其安装在虚拟机(VM)上的一种虚拟化类型,并在主机操作系统(OS)运行的管理程序上运行。与传统的完全虚拟化相比,半虚拟化可以减少开销,并提高系统性能。虚...
- HashMap底层实现原理以及线程安全实现
-
HashMap底层实现原理数据结构:HashMap的底层实现原理主要依赖于数组+链表+红黑树的结构。1、数组:HashMap最底层是一个数组,称为table,它存放着键值对。2、链...
- long和double类型操作的非原子性探究
-
前言“深入java虚拟机”中提到,int等不大于32位的基本类型的操作都是原子操作,但是某些jvm对long和double类型的操作并不是原子操作,这样就会造成错误数据的出现。其实这里的某些jvm是指...
- 数据库DELETE 语句,还保存原有的磁盘空间
-
MySQL和Oracle的DELETE语句与数据存储MySQL的DELETE操作当你在MySQL中执行DELETE语句时:逻辑删除:数据从表中标记为删除,不再可见于查询结果物理...
- 线程池—ThreadPoolExecutor详解(线程池实战)
-
一、ThreadPoolExecutor简介在juc-executors框架概述的章节中,我们已经简要介绍过ThreadPoolExecutor了,通过Executors工厂,用户可以创建自己需要的执...
- navicat如何使用orcale(详细步骤)
-
前言:看过我昨天文章的同鞋都知道最近接手另一个国企项目,数据库用的是orcale。实话实说,也有快三年没用过orcale数据库了。这期间问题不断,因为orcale日渐消沉,网上资料也是真真假假,难辨虚...
- 你的程序是不是慢吞吞?GraalVM来帮你飞起来性能提升秘籍大公开
-
各位IT圈内外的朋友们,大家好!我是你们的老朋友,头条上的IT技术博主。不知道你们有没有这样的经历:打开一个软件,半天没反应;点开一个网站,图片刷不出来;或者玩个游戏,卡顿得想砸电脑?是不是特别上火?...
- 大数据正当时,理解这几个术语很重要
-
目前,大数据的流行程度远超于我们的想象,无论是在云计算、物联网还是在人工智能领域都离不开大数据的支撑。那么大数据领域里有哪些基本概念或技术术语呢?今天我们就来聊聊那些避不开的大数据技术术语,梳理并...
- 秒懂列式数据库和行式数据库(列式数据库的特点)
-
行式数据库(Row-Based)数据按行存储,常见的行式数据库有Mysql,DB2,Oracle,Sql-server等;列数据库(Column-Based)数据存储方式按列存储,常见的列数据库有Hb...
- AMD发布ROCm 6.4更新:带来了多项底层改进,但仍不支持RDNA 4
-
AMD宣布,对ROCm软件栈进行了更新,推出了新的迭代版本ROCm6.4。这一新版本里,AMD带来了多项底层改进,包括更新改进了ROCm的用户空间库和AMDKFD内核驱动程序之间的兼容性,使其更容易...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (74)
- oracle批量插入数据 (65)
- oracle事务隔离级别 (59)
- oracle 空为0 (51)
- oracle主从同步 (56)
- oracle 乐观锁 (53)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)