清理即将爆炸的redis

近日发现运行多年的redis服务器越来越慢,已经接近爆炸了。

服务器配置为:2GB内存,2GB swap,只跑了redis服务,没有任何其它服务。

登录后发现redis的内存占用在80%左右,此外还有redis-rdb-bgsave进程,内存占用也在80%左右,同时还有接近80%浮动的CPU占用。这两项导致整个服务器的内存和swap几乎全部占满了,服务器响应非常缓慢。

redis-rdb-bgsave是redis的持久化保存进程,它是从redis主进程中fork出来的。因此,当fork刚刚发生时,由于内存的copy-on-write机制,内存占用并不多,随着bgsave的进行和redis服务进程仍然在修改内存数据,两者的差异逐渐增加,内存占用也越来越大。加之swap的存在,数据不停地在硬盘和内存之间传递,性能惨不忍睹。

登录到redis-cli上,查看info,发现一共有760万个key,总内存用量为1.5G。从业务上看,并不可能有这么多key。这都是什么呢?

> redis-cli scan 0 count 1000

执行上面指令,列出来1000个key,发现其中几乎全部是cache:*。这时Rails的session存储。进而发现是Rails session没有设置超时,导致这些key全部积累起来了。

如何增加Rails session的超时不表,下面我们要删除这700多万个cache:*。可惜redis没有提供根据pattern批量删除key的办法。

如果去网上找批量删除key,大概会发现两类方法:

  1. redis-cli keys <pattern> | xargs redis-cli del
  2. 利用exec lua脚本在redis内删除

如果你要删除的keys不多,可以参考这两种方法,参考链接如下:

https://huoding.com/2014/04/11/343

https://stackoverflow.com/questions/4006324/how-to-atomically-delete-keys-matching-a-pattern-using-redis

实际情况是,因为redis数据库已经处于爆炸边缘状态,任何keys <pattern>操作,都会返回700万个key,所占用的内存完全分配不出来,因此完全行不通。

解决办法如下:

首先需要暂时关掉bgsave,这样可以省出来几乎一半的内存,即几乎把swap都省出来了,内存还是满满的,不过已经可以顺畅操作了。

修改/etc/redis.conf,把其中的bgsave相关设置调成不保存:

#注释掉默认的备份数据
#save 900 1
#save 300 10
#save 60 10000
#增加一条“不可能”触发的,理论上也可以不增加,不过为了保险增加也无妨
save 3600 1000000

重启redis,可以发现,bgsave不会自动开始了,swap也几乎空着,只是RAM仍然占满,redis仍然占用80%以上内存。这里我直接kill -9杀掉了原来的redis和bgsave,然后重启。如果不强行杀,也不知道要等多久才能正常重启。如果真的在乎尽量不丢有用的数据,可以尝试正常重启。

即便这样,仍然不能用keys <pattern>的方式列出所有要删除的键——内存仍然不够。我的办法是用scan一点一点删

> redis-cli scan 0 match cache:* count 10000 | xargs redis-cli del

实测count 10000的时候,机器不会死掉,可以正常运行。

理论上scan会返回一个新的cursor,以便下一次scan的时候用(换掉scan后面第一个参数0)。不过咱们这种简单粗暴的方式并不会输出这个cursor给你看,所有只能多执行几次上面的命令,你会发现del成功的数量越来越少。

之后,可以换一个起始cursor,比如1000000,再跑几次。然后再换一个……这样删了几轮之后,逐步把count提高,例如20000、50000、100000。

注意,删除的过程中,通过info可以查看剩余的总keys数量。不过这个过程中内存并不会被释放出来,redis的内存占用只是从80%以上降低到了70%左右。事实上,这微小的内存释放,就是咱们增大scan count的资本。

当删除到剩下300万左右keys的时候,count可以增大到50万。这样再来几次,几乎所有键都删掉了。

接下来,怎么彻底释放内存呢?

先手动执行一次bgsave(因为之前已经”关掉”了bgsave,所以需要手动执行)。可以感受到bgsave并没有让系统变慢,查看一下保存的文件,尺寸小了一个数量级。

然后把/etc/redis.conf改回原有的保存策略,重启redis,可以看到重启后,redis内存占用变得很小了。

至此,大功告成。

总结本文要点:

  1. 暂时停掉bgsave,可以让redis暂时变得可操作,而不是完全不能响应
  2. 内存占用很大的时候,keys <pattern>操作或者count过大的scan都没法执行,只能通过count比较小的scan逐步删除。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

在这输入验证码 : *

Reload Image