问题背景
一天下午,测试同学在群里说测试环境无法开启录制,出现了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前缀的内存问题