当前位置:首页 > 问答 > 正文

Redis磁盘占用太多了,怎么查原因和解决办法分享

有朋友在知乎上问过一个问题,说他的Redis服务器磁盘空间报警,查了一下发现Redis的持久化文件(就是那个叫dump.rdb的文件)或者AOF文件长得特别大,远远超过了当时数据库里数据的内存占用量,问这是怎么回事,该怎么解决,下面我结合自己的经验和网上一些常见的讨论,比如知乎上“Kaito”等人的分享,来聊聊这个事儿。

最直接的一个原因,可能就是你开启了Redis的持久化功能,Redis为了在重启后不丢数据,提供了两种方式把内存里的数据写到磁盘上:一种叫RDB,可以理解为在某个时间点给整个数据库拍一张快照;另一种叫AOF,像是写日记,会把每条写命令都记录下来,不管是哪种,只要数据量大了,这个磁盘文件自然就大了,但问题在于,有时候这个文件的大小会不合常理地大于内存使用量,这就得深究了。

怎么查原因呢?可以从以下几个方面入手:

  1. 先确认是不是AOF文件惹的祸。 这是最常见的原因之一,因为AOF文件是追加写入命令的,比如你对同一个key反复修改了100次,那么AOF文件里就会记录下这100次操作,虽然最后内存里只有这个key最终的值,但磁盘上却保存了它完整的“变更史”,你可以用Redis自带的命令 INFO persistence 查看AOF的相关状态,aof_current_size 就是当前AOF文件的大小,如果这个值比你用 INFO memory 查到的 used_memory 大好几倍,那很可能就是它了。

  2. 检查一下RDB快照的生成时机。 虽然RDB是快照,但如果你的数据量本身就非常大,比如内存用了20个G,那生成的RDB文件压缩后可能也得有几个G甚至十几G,这看起来也会很吓人,关键是看这个大小是否在正常范围内,通常RDB文件会比内存占用小,因为Redis会压缩它。

  3. 用Redis的工具深入文件内部看看。 Redis提供了一个很实用的命令行工具叫 redis-cli,结合 --bigkeys 参数可以扫描出当前数据库中哪种数据类型的哪个key最大,命令是 redis-cli --bigkeys,这样你就能知道是不是有某些特别的“大key”在作怪,有时候可能就是几个超级大的key(比如一个list里存了几百万个元素)撑大了整个数据集。

  4. 检查是否有大量已过期但未及时清理的Key。 Redis的过期key清理是惰性的+定期的,惰性删除是说当客户端访问一个key时才发现它过期了再删;定期删除是Redis每隔一段时间随机检查一些key并删除过期的,如果有很多key已经过期了,但一直没被访问,Redis的定期任务也没抽到它们,那么这些“僵尸key”还会暂时存在于内存和持久化文件中,直到被清理掉,你可以用 INFO stats 命令查看 expired_keys 这个指标,了解历史清理了多少过期的key,但无法直接看到当前还有多少未清理的。

    Redis磁盘占用太多了,怎么查原因和解决办法分享

  5. 考虑一下数据碎片化的问题。 内存中的数据在经过频繁的修改和删除后,可能会产生很多小的、不连续的内存空间,这就是内存碎片,虽然这主要影响内存效率,但持久化到磁盘时,如果碎片化严重,也可能导致持久化文件比预想的大一些,可以用 INFO memory 里的 mem_fragmentation_ratio(内存碎片率)指标来评估,但这个指标主要反映内存状态。

知道了原因,解决办法就比较有针对性了:

  • 针对AOF文件过大:

    • 手动触发AOF重写: 执行命令 BGREWRITEAOF,这个命令会启动一个子进程,基于当前数据库的数据生成一个新的、更精简的AOF文件(只包含恢复当前数据所需的最小命令集合),然后用它替换掉旧的、臃肿的AOF文件,这是最立竿见影的方法。
    • 优化AOF重写配置: 在配置文件redis.conf中,可以调整 auto-aof-rewrite-percentageauto-aof-rewrite-min-size,可以设置当AOF文件比上次重写后的大小增长了100%(百分比可调),并且AOF文件本身至少达到64MB(大小可调)时,自动触发重写,这样能避免文件无限制膨胀。
  • 处理大Key:

    Redis磁盘占用太多了,怎么查原因和解决办法分享

    • 如果通过 --bigkeys 找到了罪魁祸首,就要从业务层面考虑优化,比如能不能把一个大key拆分成多个小key?或者是不是可以用其他更节省空间的数据结构?比如存储大量整数值时,用set或sorted set可能不如用HyperLogLog或Bitmap节省空间(具体看场景)。
  • 强制清理过期Key:

    • 可以尝试执行 redis-cli --bigkeys 命令,虽然它的主要目的是找大key,但在这个过程中会触发对遍历到的key的过期检查,从而清理掉一部分“僵尸key”,更直接的方法是重启Redis节点(如果允许的话),因为重启后Redis从持久化文件恢复数据时,不会加载过期的key。
  • 考虑内存碎片整理:

    • 从Redis 4.0开始,支持了手动内存碎片整理命令 MEMORY PURGE(取决于所用allocator),在更高版本(5.0之后?记不清了),还可以通过配置 activedefrag yes 来开启自动碎片整理功能,但这个功能会消耗额外CPU,需要根据服务器情况谨慎开启。
  • 终极手段:重启并重载

    • 如果情况比较复杂,或者想快速得到一个“干净”的状态,可以规划一次维护窗口,先关闭Redis,然后删除巨大的AOF或RDB文件,再重新启动Redis(如果数据可以从其他地方恢复,或者可以接受暂时无数据),更安全的方式是,在有从节点的情况下,在主节点上执行 BGSAVE 生成一个新的RDB快照,然后用这个新的、更小的RDB文件去重启实例或搭建新节点。
  • archiving方案(治本):

    从根本上说,还是要审视业务,有些数据可能根本不需要永远存在Redis里,或者访问频率很低,可以设置合理的过期时间(TTL),对于冷数据,可以考虑定期归档到更廉价的存储系统(比如MySQL、对象存储等),然后从Redis中删除,减轻Redis的压力。

Redis磁盘占用异常变大,多半是AOF日志堆积或者存在大Key/过期Key造成的,排查时先用 INFO 命令和 --bigkeys 工具定位问题,然后通过重写AOF、优化数据结构、清理过期数据等手段来解决,平时做好监控和配置优化,就能防患于未然。