记一次Redis内存占用高的问题排查过程

问题背景

一天下午,测试同学在群里说测试环境无法开启录制,出现了500的错误码。接着我去找了负责音视频媒体服务的同事,经排查后发现是由于机器的CPU负载一直很高,录制服务会对CPU负载进行监控,一旦负载过高则不会进行录制。一般情况下分析到这个程度,可能就会想着增加机器配额。可是,这是测试环境,测试的同学仅仅只有2-3个人,而且都是做一些业务功能测试,并没有做压测,CPU负载怎么会很高呢?

遇到CPU负载高的问题,首先要找到是什么进程导致CPU的负载升高。在测试环境的服务器中,通过htop命令发现,CPU负载一直维持在一个高位,并没有下降下去。而且16G的内存也几乎已经占满。观察了一段时间后,发现有几个redis的进程一直在top列表的前几位,而且内存占用也达到了1.2G。测试环境使用的人并不多,redis的内存不应该会占用那么多才对,redis里到底存着什么内容,导致内存占用这么高呢?

问题分析与解决

redis一般会有内存回收策略去回收,内存占用升高到这种程度,可能的原因有:

  • 在一段时间内产生了很多的大Key,Redis来不及删除
  • 积累了很多无过期时间的Key,Redis无法删除

要排查Redis里到底被哪些key占用了较多的内存,可通过redis的bigkeys命令看下相关的统计:

shell 复制代码
redis-cli --bigkeys

接着就可以得到一些统计信息:

python 复制代码
0 lists with 0 items (00.00% of keys, avg size 0.00)
2163 hashs with 2287 fields (08.38% of keys, avg size 1.06)
23553 strings with 1211289119 bytes (91.25% of keys, avg size 51428.23)
0 streams with 0 entries (00.00% of keys, avg size 0.00)
62 sets with 125 members (00.24% of keys, avg size 2.02)
34 zsets with 68617 members (00.13% of keys, avg size 2018.15)

可见,strings数据结构的key占用了91%的内存,一共有1.2G左右。测试环境的并发很低,不大可能在短时间产生很多大key,而且有设置过期时间的话,redis也会自动清理。因此,很可能是由于积累了很多无过期时间的key导致的问题。因为是在测试环境,所以我把redis的key都dump出来并下载到本地进行分析:

shell 复制代码
# dump redis 内存
bgsave

# 查看dump是否完成
info persistence

最终,在/etc/redis目录下得到一个dump.rdb的文件。可rdb文件又该怎么解析出它的内容呢?这时候就需要用到rdbtools工具了。

结合redis-rdb-tools分析redis中的内存组成

通过安装 redis-rdb-tools 到本地之后,就可以用这个工具来解析出rdb文件中都有哪些key了(详细安装和使用教程可以参考github)。在这个问题中,我们用下面这个命令来解析rdb文件:

shell 复制代码
rdb -c memory dump.rdb --bytes 128 -f memory.csv

其中,-c memory参数是指对memory输出相关的分析报告,--bytes 128参数是指大于128字节的才会被解析出来,-f memory.csv 参数表示将这些过滤后的key输出到memory.csv文件中。

输出后的文件内容如下所示:

rdb工具会帮我们对redis中的key进行一些分析,,列出每个key存在于哪个database中,是什么类型的key,大小有多少bytes,过期时间是什么时候等等。大致看了一下导出来的记录,有很多expiry为空的记录,大致观察了一下,有很多相同key前缀的key,过期时间都为空。但到底有多少呢?这里就要结合shell命令做一些统计了:

shell 复制代码
grep -E "你的业务key前缀" memory.csv | awk -F',' '{sum += $4; count++} END {print sum;print count}'

得到的结果为:

1340376328
22687

第一个结果 1340376328 代表字节数,合计1.2G,第二个结果22687 代表元素个数,相同key前缀的记录共有22687个这么多。这占用的字节数正好和top命令看到的内存占用量一致。根据这个业务前缀到代码里一看,果然是由于代码写错了导致过期时间没有被正确地设置:

因为不合理的写法,本该加上前缀的key没有设置正确的过期时间,导致key一直被累积,最终让redis内存无法被回收。解决方案也很简单,只需要把这段代码改成下面这样即可:

修改完之后发布到测试环境,并清理相关的数据,就再也没有出现Redis导致的CPU负载飙升、内存飙升的问题了。

问题延伸

这个问题,只能说幸亏发现得早,要是在客户使用过程中出现,那造成的影响是灾难性的。这也反映出我们现在对redis的监控、日常编码时对于redis这部分的使用关注度是不够的。为此,我又找了运维的同学,从止损、预防问题发生、问题发生后如何快速分析三个方面,一起制定了以下几个措施:

  • 止损:对现存的redis使用上限、各个环境中的redis用量进行统计和监控,以及早发现类似的redis内存问题。
  • 预防问题发生:筛查现存环境中的没过期时间的key,逐一解决无过期时间的key的代码。并在测试环境中增加对redis key的监控,当出现一些非预期的无过期时间的key,则发送告警通知给后端。
  • 问题发生后如何快速分析:当出现问题时可以快速定位,在私有化的场景下,输出一些redis内存分析工具,定位指定key前缀占用的内存量,这样就可以快速发现特定key前缀的内存问题
相关推荐
hlsd#25 分钟前
go mod 依赖管理
开发语言·后端·golang
陈大爷(有低保)29 分钟前
三层架构和MVC以及它们的融合
后端·mvc
亦世凡华、29 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
河西石头30 分钟前
一步一步从asp.net core mvc中访问asp.net core WebApi
后端·asp.net·mvc·.net core访问api·httpclient的使用
编程、小哥哥39 分钟前
设计模式之抽象工厂模式(替换Redis双集群升级,代理类抽象场景)
redis·设计模式·抽象工厂模式
2401_8574396942 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66643 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
阿华的代码王国1 小时前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
小码编匠1 小时前
领域驱动设计(DDD)要点及C#示例
后端·c#·领域驱动设计
德育处主任Pro2 小时前
『Django』APIView基于类的用法
后端·python·django