运维排查篇 | Redis占用内存过高怎么办

news2025/3/9 10:47:08

我们知道,Redis是一个key-value数据库,它的数据是运行在内存中

其读写效率比将数据存储到磁盘上的数据库要快很多

虽然性能强大,但是如果我们不了解Redis的内存回收策略,就有可能导致Redis消耗内存过高甚至导致内存溢出,严重影响系统性能

案例现象

发现生产环境上的一台服务器出现内存使用率达到阈值的告警

登上服务器使用 top 命令先看下系统资源整体使用的情况

top

通过 top 的输出发现:

  • 系统平均负载没有异常

  • 系统cpu使用率没有异常

  • 系统已使用的物理内存(used)数值特别高,达到了总物理内存的80%以上

  • 而且buffer/cache的数值也不小,这说明有应用产生了大量的读写缓存

光看系统资源整体使用情况不能精确的定位到问题

我们继续观察 top 输出,这次我们将重点放到了各个进程的资源使用情况

发现:

  • redis进程占用了最多的内存,达到了20G

  • redis进程的使用率也达到了90%以上

由 top 的输出我们不难发现,这台服务器上的 redis实例消耗了大量的内存,而且cpu使用率很高,应该是有应用往 redis 上进行大量的读写操作

定位问题

这台服务器上的 Redis 是运行在 docker 上的,我们进入到 redis 容器里面

docker exec -it redis /bin/bash 

我们还可以使用如下命令来查看 redis 容器的一些详细信息

docker inspect redis

然后找到 Redis 的配置文件路径,检查一下是不是配置出了问题

与对部署相同服务的服务器redis配置文件对比了一下,发现配置并没有什么问题

不是配置问题,那到底是什么原因导致Redis占用了这么多内存?这些使用的内存Redis不会回收的吗?

在回答这些问题时,我们先来了解一下Redis的内存回收策略以及键过期机制

内存回收策略

Redis是基于内存的数据库,常被用作缓存,以此来提高系统的响应速率与性能

Redis内存消耗

Redis进程的内存消耗主要包括:自身内存 + 对象内存 + 缓冲内存 + 内存碎片

自身内存

一般来讲,Redis空进程自身内存消耗非常少,通常 usedmemoryrss 在 3MB 左右时,used_memory 一般在 800KB 左右

一个空的 Redis 进程消耗内存可以忽略不计

对象内存

对象内存是Redis内存占用最大的一部分,因为它存储着用户所有的数据

对象内存消耗可以简单理解为这两个对象(Key和Value)的内存消耗之和(还有类似过期之类的信息)

使用 Redis 时很容易忽略键对内存消耗的影响,应当避免使用过长的键以及给键设置一个过期时间

缓冲内存

缓冲内存主要包括客户端缓冲、复制积压缓冲和AOF缓冲

客户端缓冲指的是所有接入到 Redis 服务器 TCP 连接的输入输出缓冲

复制积压缓冲区是Redis 在 2.8 版本后提供的一个可重用的固定大小缓冲区,用于实现部分复制功能,根据 repl-backlog-size 参数控制,默认 1MB。对于复制积压缓冲区整个主节点只有一个,所有的从节点共享此缓冲区。因此可以设置较大的缓冲区空间,比如说 100MB,可以有效避免全量复制

AOF 重写缓冲区这部分空间用在AOF 重写期间保存最近的写入命令。AOF 重写缓冲区的大小用户是无法控制的,取决于 AOF 重写时间和写入命令量,不过一般都很小

内存碎片

Redis内部有自己的内存管理器,为了提高内存使用的效率,实现对内存的申请和释放进行管理。

Redis中的值删除的时候,并没有把内存直接释放交还给操作系统,而是交给了Redis内部有内存管理器。这就使得如果大量的key在短时间内过期被删除,这些内存不会释放给操作系统,而是交给内部内存管理器, 从而导致redis实际占用的内存与申请的内存相差过大,就会导致大量的内存碎片

Redis 正常碎片率一般在 1.03 左右

PS:子进程内存消耗

除此之外,Redis实例的内存消耗还有一部分是子进程的内存消耗,子进程内存消耗主要指执行 AOF 重写 或者进行 RDB 保存时 Redis 创建的子进程内存消耗

Redis 内存相关的指标

我们可以通过info memory 命令可以获得 Redis 内存相关的指标

127.0.0.1:6379 > info memory

这里我们挑三个比较重要的字段来讲一下

  • mem_fragmentation_ratio

当该值 > 1时,说明有部分内存并没有用于数据存储,而是被内存碎片所消耗,如果该值很大,说明碎 片率严重

当该值 < 1时,一般是因为操作系统把 Redis 内存中的数据 swap 到硬盘里面,出现这种情况要格外关注,因为硬盘速度远远慢于内存,所以 Redis 性能会变得很差,甚至僵死

建议设置和内存一样大小的交换区,如果没有交换区,一旦 Redis 突然需要的内存大于当前操作系统可用内存时,Redis 会因为内存溢出而被内核的 OOM 机制直接杀死

  • maxmemory

Redis 使用 maxmemory 参数限制最大可用内存。限制内存的目的主要有:

1、用于缓存场景,当超出内存上限 maxmemory 时使用 LRU 等回收策略释放空间

2、防止所用的内存超过服务器物理内存,导致 OOM 后进程被系统杀死

maxmemory 限制的是 Redis 实际使用的内存量,也就是 used_memory 对应的内存。

实际消耗的内存可能会比 maxmemory 设置的大,要小心因为这部分内存导致 OOM。所以,如果你有 10GB 的物理内存,最好将 maxmemory 设置为 8 或者9G

  • maxmemory_policy

Redis默认采用noeviction策略

volatile-lru:# 在设置了过期时间的所有键中,选取最近最少使用的数据删除volatile-lfu:# 在设置了过期时间的所有键中,选取最近最不常用,也就是一定时期内被访问次数最少的数据删除volatile-random:# 筛选出设置了过期时间的键值对,随机删除。volatile-ttl:# 筛选出设置了过期时间的键值对,越早过期的越先被删除。allkeys-lru:# 在所有键中,选取最近最少使用的数据删除allkeys-lfu:# 在所有键中,选取最近最不常用,也就是一定时期内被访问次数最少的数据删除allkeys-random:# 采用随机淘汰策略删除所有的键值对,这个策略不常用。noeviction:# 不淘汰任何键值对,当内存满时,如果进行读操作,例如get命令,它将正常工作,# 如果做写操作,它将返回错误,# 也就是说,当Redis采用这个策略内存达到最大的时候,它就只能读不能写了

键过期机制

除了上面提到的内存回收机制可以有效解决 Redis 内存使用过高的问题之外,Redis还有一个键过期机制——给key设置一个过期时间,一旦超过过期时间,这个key就会被被删除,内存将被回收

PS:前面一章说的是是内存不足的「回收策略」,这一种是过期键的删除策略,两者是不同的,不要搞混了

我们来看一下相关的命令

  • 查看key的过期时间

如果key存在过期时间,返回剩余生存时间(秒);如果key是永久的,返回-1;如果key不存在或者已过期, 返回-2

TTL单位是秒,PTTL单位是毫秒

127.0.0.1:6379> TTL KEY127.0.0.1:6379> PTTL KEY

  • 设置key的过期时间

设置一个key在当前时间"seconds"(秒)之后过期。返回1代表设置成功,返回0代表key不存在或者无法设 置过期时间

EXPIRE单位是秒,PEXPIRE单位是毫秒

语法:EXPIRE key seconds127.0.0.1:6379> EXPIRE name 60(integer) 1

设置一个key在"timestamp"(时间戳(秒))之后过期。返回1代表设置成功,返回0代表key不存在或者无法 设置过期时间

EXPIREAT单位是秒,PEXPIREAT单位是毫秒

语法:EXPIREAT key "timestamp"127.0.0.1:6379> EXPIREAT name 1586941008(integer) 1

SETEX在逻辑上等价于SET和EXPIRE合并的操作,区别之处在于SETEX是一条命令,而命令的执行是原子性 的,所以不会出现并发问题

语法:SETEX key "seconds" "value"127.0.0.1:6379> SETEX name 100 jackOK

给key设置了过期时间,如果 key 过期了 Redis 该怎么处理呢?

Redis 过期 Key 处理

Redis key过期处理的方式有三种:

1、惰性删除

不管键有没有过期都不主动删除,等到每次去获取键时再判断是否过期,如果过期就删除该键,否 则返回键对应的值。这种策略对内存不够友好,可能会浪费很多内存

缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄 露(无用的数据占用了大量的内存)

2、定时删除

在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对 key进行删除

缺点:定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器 产生),性能影响严重,因为每个定时器都会占用一定的 CPU 资源

3、定期删除

系统每隔一段时间就定期扫描一次,发现过期的键就进行删除

可以配置redis.conf 的hz选项,默认为10 (即1秒执行10次,100ms一次,值越大说明刷新频率 越快,最Redis性能损耗也越大)

我们在 Redis 当中,其选择的是惰性删除和定期删除的综合使用。

不过 Redis 的定期删除只会扫描设置了过期时间的键,因为设置了过期时间的键 Redis 会单独存储,所以不会出现扫描所有键的情况

如果给键设置了较大的过期时间且没有使用内存回收策略,原本的数据没过期不会被回收,又不断写入新的数据,这样会导致 Redis 消耗的内存不断增大

解决问题

了解了 Redis 的内存回收策略以及键过期机制之后我们分别来看一下

我们首先看一下任意key的过期时间是多少

#从当前数据库中随机返回一个 key127.0.0.1:6379> RANDOMKEY127.0.0.1:6379> TTL key(integer) 12032145

我们发现,key的过期时间设置成了一千多万秒!这个过期时间也太长了吧

再查看一下 Redis 的内存回收策略配置

127.0.0.1:6379> info memorymaxmemory:0maxmemory_policy:"noeviction"

可以看到,我们并没有设置内存最大限制,而且内存回收策略是noeviction,即不淘汰任何键值对

查看了这两个选项之后,问题就清晰起来了:

key的过期时间设置的太长,没有设置最大可用内存限制而且内存回收策略是noeviction 就会使得原先的数据还没过期,又有新的数据写进来,导致消耗内存越来越多,而系统又无法进行回收

解决方法

1、重新给键设置过期时间

这个不太现实,因为生产环境中的 Redis 有大量的 Key(十万级甚至百万级),不可能说一个一个的重新设置过期时间

而且我们的 Redis是运行在docker上的,已经打包成一个容器,如果修改的话需要花费大量的时间和精力

2、修改配置文件(推荐使用)

我们可以更改 Redis 的配置,设置最大内存使用限制以及内存回收策略

修改 Redis 配置文件,添加如下两个字段:

maxmemory:10Gmaxmemory_policy:"volatile-lru"

我们设置了最大内存使用限制为10G,一旦redis占用内存超过10GB,就会触发内存回收机制volatilelru——在设置了过期时间的key里,删除最近最少使用的key

更改配置后重启一下,等待一段时间后发现 Redis 消耗的内存降下去了,也不再告警了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/374374.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[优化]上下游交互策略

书接上文&#xff1a;https://blog.csdn.net/weixin_43303530/article/details/127227147?spm1001.2014.3001.5502&#xff0c;为满足产品提的在24小时内能重试尽量去重试&#xff0c;不计较重试的次数的要求&#xff0c;在第三方电子卡系统无法提升接口并发数量的情况下&…

电脑C盘空间不足?这样做就行了!

一般来说&#xff0c;电脑C盘都是系统盘&#xff0c;它的稳定关系到系统能否正常运行&#xff0c;但是很多朋友的C盘却总是红色的&#xff0c;这也就意味着C盘储存空间不足了&#xff0c;那么我们就需要进行以下操作&#xff0c;来让C盘重新拥有储存空间。方案一&#xff1a;运…

BLE Mesh蓝牙协议学习记录

BLE Mesh蓝牙协议学习 文章目录BLE Mesh蓝牙协议学习前言概述一、蓝牙技术整体框架二、经典蓝牙和低功耗蓝牙mesh协议架构图承载层&#xff08;Bearer Layer&#xff09;网络层&#xff08;Network Layer&#xff09;底层传输层&#xff08;Lower Transport Layer&#xff09;上…

JAVA连接数据库——JDBC的简单使用

JDBC即Java数据库连接.用来实现Java程序对数据库增删查改。 为了对接Java程序和数据库&#xff0c;java.sql提供了很多api包含在java.sql和javax.sql里面 结构: DriverManager接口: 每一个数据库的驱动程序都必须去到DriverManager注册&#xff0c;生成一个Connection Conn…

电商平台的促销活动如何抵御大流量的ddos攻击

每一次活动大促带来的迅猛流量&#xff0c;对技术人而言都是一次严峻考验。如果在活动期间遭受黑产恶意 DDoS 攻击&#xff0c;无疑是雪上加霜。电商的特性是业务常态下通常不会遭受大流量 DDoS 攻击&#xff0c;且对延迟敏感&#xff0c;因此只需要在活动期间按需使用 DDoS 防…

【第五章 AOP概述,底层原理,AOP术语,切入点表达式,AOP操作(基于注解方式,基于xml配置文件)】

第五章 AOP概述&#xff0c;底层原理&#xff0c;AOP术语&#xff0c;切入点表达式&#xff0c;AOP操作&#xff08;基于注解方式&#xff0c;基于xml配置文件&#xff09; 1.AOP概述&#xff1a; &#xff08;1&#xff09;什么是AOP&#xff1a; ①面向切面编程&#xff08;…

11-KMP算法

KMP算法是一个字符串匹配算法&#xff0c;总的意义是在给定的字符串A中利用优化的方法快速地找出字符串B的位置&#xff0c;相比于传统匹配算法&#xff0c;它能有效减少匹配时间&#xff0c;提高效率。 前缀和后缀 在我们看KMP算法前我们先考虑一个问题&#xff1a;假如我们…

基于框架的平台总线式开发

一、总线、设备、驱动 硬编码式的驱动开发带来的问题&#xff1a; 1. 垃圾代码太多 2. 结构不清晰 3. 一些统一设备功能难以支持 4. 开发效率低下 1.1 初期解决思路&#xff1a;设备和驱动分离 struct device来表示一个具体设备&#xff0c;主要提供具体设备相关的资源&am…

Telnet 基础实验2: SSH 实验

Telnet 基础实验2&#xff1a; SSH 实验 本实验只能使用 eNSP中 AR 系统的路由器做 拓扑图 SSH &#xff1a; Secure Shell 是一个网络安全协议&#xff0c;基本于 TCP 协议 22 端口传输数据&#xff0c;通过对网络数据的加密&#xff0c;使其能够在一个不安全的网络环境中&a…

网易新财报:游戏养家,教育维稳、音乐快走

配图来自Canva可画 随着互联网流量红利逐渐消退&#xff0c;互联网大厂们也告别高增长时代&#xff0c;逐渐进入稳定增长阶段。近两年&#xff0c;流量焦虑、业务失速等问题更是成为了一团浓雾&#xff0c;笼罩在互联网大厂周围。不过&#xff0c;面对所遭遇的难题&#xff0c…

力扣-换座位

大家好&#xff0c;我是空空star&#xff0c;本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;626. 换座位二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总结前言 …

读书笔记——《富爸爸穷爸爸》

《富爸爸穷爸爸》&#xff0c;以前不屑读这种书。这种书就是那种走进书店放在门口展销位的成功学著作&#xff0c;一眼看上去没什么实在的内容&#xff0c;看上去很不靠谱&#xff0c;感觉就是骗一些社会底层又做着暴富梦的人来买的&#xff0c;但是由于自身原因或环境局限根本…

Spring Boot + Vue3 前后端分离 实战 wiki 知识库系统<二>---后端架构完善与接口开发

数据库准备&#xff1a; 在上一次Spring Boot Vue3 前后端分离 实战 wiki 知识库系统<一>---Spring Boot项目搭建已经将SpringBoot相关的配置环境给搭建好了&#xff0c;接下来则需要为咱们的项目创建一个数据库。 1、mysql的安装&#xff1a; 关于mysql的安装这里就…

【C语言每日一题】杨氏矩阵(源码以及改进源码)

【C语言每日一题】—— 杨氏矩阵&#x1f60e;&#x1f60e;&#x1f60e; 目录 &#x1f4a1;前言&#x1f31e;&#xff1a; &#x1f49b;杨氏矩阵题目&#x1f49b; &#x1f4aa; 解题思路的分享&#x1f4aa; &#x1f60a;题目源码的分享&#x1f60a; &#x1f4…

夜天之书 #73 Apache Pulsar 的社群指标

去年十一月&#xff0c;我成为了 Apache Pulsar[1] 社群 Committers 的一员。成为 Committer 之前和之后&#xff0c;我都积极参与了代码仓库上 Issue 和 Pull Request (PR) 的处理回应和评审。去年十二月期间&#xff0c;我把未解决的 Issue 和 PR 数量分别从接近 2000 个和 4…

STM32学习笔记-I2C通信协议

文章目录介绍&#xff1a;两种实现方式&#xff1a;I2C设备的常用连接方式&#xff1a;I2C协议时序&#xff1a;STM32硬件I2C框架图I2C外设通讯过程**I2C读写EEPROM**&#xff08;硬件I2C&#xff09;介绍&#xff1a; 两根通信线SCL&#xff08;时钟线&#xff09;、SDA&#…

C语言中的强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#xff0c;如下所示&am…

“ChatGPT之父”Sam Altman:如何成功?

背靠微软&#xff0c;OpenAI能拳打谷歌&#xff0c;脚踢Meta&#xff0c;它背后的男人&#xff0c;必然不简单。 让我们来看一看&#xff0c;Sam Altman是如何一步步成长为今天这个搅动全世界的男人。 山姆奥特曼&#xff08;Sam Altman&#xff09; 成长和创业经历 在YC创始…

代码随想录【Day27】| 39. 组合总和、40. 组合总和 II、131. 分割回文串

39. 组合总和 题目链接 题目描述&#xff1a; 给定一个无重复元素的数组 candidates 和一个目标数 target &#xff0c;找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明&#xff1a; 所有数字&#xff08;包括 tar…

JavaScript 高级2 :构造函数和原型 d331702016e84f54b3594ae05e0eeac

JavaScript 高级2 &#xff1a;构造函数和原型 Date: January 16, 2023 Text: 构造函数和原型、继承、ES5中的新增方法 目标 能够使用构造函数创建对象 能够说出原型的作用 能够说出访问对象成员的规则 能够使用 ES5新增的一些方法 构造函数和原型 概述 在典型的 OOP 的…