近日发现运行多年的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,大概会发现两类方法:
- redis-cli keys <pattern> | xargs redis-cli del
- 利用exec lua脚本在redis内删除
如果你要删除的keys不多,可以参考这两种方法,参考链接如下:
https://huoding.com/2014/04/11/343
实际情况是,因为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内存占用变得很小了。
至此,大功告成。
总结本文要点:
- 暂时停掉bgsave,可以让redis暂时变得可操作,而不是完全不能响应
- 内存占用很大的时候,keys <pattern>操作或者count过大的scan都没法执行,只能通过count比较小的scan逐步删除。