Redis内存爆炸排查与解决

1. 问题发现

今天公司的小程序产品出现异常了,无论请求都没有数据,然后我就去线上看日志,发现了如图日志:

Redis线上报错

我一看,大致意思就是Redis的RDB快照持久化失败,看到这,我立马反应过来,坏了,不会是线上Redis服务器的磁盘满了吧,于是立马执行命令:

sudo df -h

系统磁盘空间

结果一看,好家伙,磁盘是毫无压力哇。

那难道是权限问题?这个问题一想到立马就被我PASS了,完全不可能,因为小程序已经在线上用了几年了,期间一直都是ok的。

没办法,没有其他想法了,只有去看服务器上的Redis的执行日志了。

结果不看不知道,一看吓一跳:

redis.conf中日志文件存放路径配置

看到这,当时我的内心OS:

不错不错,事情变得有趣起来了。日志也没有,这下直接开摆。

最后憋得没办法,为了防止下次再出现这个问题,只好先配置上Redis日志,防止下次再出现相同问题而不知道怎么排除,最后将线上的Redis服务给重启了。重启之后程序就恢复了正常,并且将近几个小时都是正常的,我以为就是服务器偶尔抽风,所有本次排除与解决就告一段落了,没有去深究。

主要是当时是周末,也不想深究了。

2. 问题再现

距离上次问题发生已经过去接近2个月了。今天早上上班途中,我发现小程序又出现上次的那个问题了。而且刚好小程序最近在做拉新活动,客户的反馈越来越平凡,没办法,这次必须给他解决了,不然在拉新的过程中,来自客户的压力可受不了。来到公司,立马开干!

立马登上Redis服务器,由于上一次还好配置了人Redis执行日志,这次我定要一探究竟。

我直接打开Redis执行日志,这下可让我发现了端倪:

Redis日志详情

报错的大概意思就是在执行RDB服务的时候,内存分配失败,好了,这下可以确定是内存的问题了。

我立马执行命令查看内存:

top

或者执行命令查看内存:

free -m

资源监控情况

[!tip]

执行top命令之后,可以在使用Shift+E,也就是按下大写的E切换资源监控的单位KiB/MiB/GiB/TiB/PiB/EiB。也可以按下Shift+M,也就是按下大写的M,进程列表会按内存使用量从高到低排序。

上图就是先按下大写的M,之后再按下大写的E将单位调整为GiB的。

正如上图显示,内存还剩10G,占用内存的两个程序就是redis-server。两个各自占用了40G,加起来就是80G了。

这里为什么会有两个redis-server呢?其实很简单,有一个是RDB服务。我们执行如下命令查看一下:

ps -ef | grep redis

执行结果

可见redis-srever服务fork出来了一个redis-rdb-bgsave服务,为啥这样说呢?因为redis-rdb-bgsave服务的根PID是9076,而9076正好是redis-srever的PID。

同时,从另外一个角度你也能够看出来,对于top命令的那个图,第一个redis-server的CPU占用基本上是0%,占用极低,可以确定它就是redis-server的主程序。第二个redis-server的CPU占用大约是66%,从Redis的执行日志,我们可知,现在是RDB服务再不断的执行Save操作。所以可以判断出第二个redis-server服务是RDB的。

好了,上面两个redis-server服务的事情说清了,那么我们来看看明明系统还有10G,为什么Redis会报错RDB服务快照无法写入磁盘呢?

这点其实很好理解,你持续观察top命令一段时间你就会明白,当RDB服务再执行的过程中,这个内存的抖动还是挺大的,有时候就给你干到零点几个G了

所以程序就会出现RDB快照写入磁盘失败以及内存分配失败的错误。

好了,现在问题的根源找到了,就是内存的问题,那么我们就要来解决它。

3. 问题解决

知道是内存的问题,那么我来解决,因为服务器是公司的,所以让公司加内存是可行的,但是成本有点过于高了,所以暂不考虑此方案,作为备选。

现在我们从Redis整个服务本身出发,看看到底是因为确实存放的数据量有这么大,还是因为碎片的原因导致的。

执行命令查看Redis的内存情况:

redis-cli info memory

当时就发现这两项的值used_memory_humanused_memory_rss_human十分接近,而且都是接近40G左右的数据。

[!tip]

  • used_memory_human:数据的实际内存(人类可读)。
  • used_memory_rss_human:系统分配内存(人类可读),包含碎片,所以该值是大于等于used_memory_human的。

数据量竟然这么大,莫非是有什么大Key吗?

所以我执行如下命令查看Redis中的BigKey

redis-cli --bigkeys

执行结果如下:

BigKey查询

这个Key是很大的,8千多万条缓存,并且这是plumolog应用的日志相关的缓存,作用其实不是很大对我们公司的业务来说,所以完全可以删除掉。

但是由于Redis是单线程模型,删除大数据结构会导致删除操作阻塞 Redis直到完成。同时这又是list结构的数据,所以我们要使用删除List结构数据的非阻塞式命令。

执行命令:

unlink your_list_key

执行删除操作之后,我们再来查看一下Redis的内存使用情况:

Redis内存数据情况

可见实际数据只有800多M了,但是系统分配的却还是有40G,这时候,你选一个用户低峰的时间段,重启一下Redis,这样内存就释放掉了

至此本次事件完美解决。