一次redis内存爆满问题

某天项目突然挂了,通过查看错误日志如下:

通过报错信息,我们可以定位到是redis的内存用完了。

一.查看redis内存使用情况

通过info命令我们可以查看redis内存使用情况,以下是info命令的一些参数说明:

我们重点关注内存的使用情况,可直接通过memory命令查看内存使用情况:

used_memory表示当前使用的内存大小,used_memory_peak_human表示给redis分配的内存的峰值,当这两个参数比较接近的时候,说明redis的内存使用完了。

这个时候,你可能会疑惑,如果redis那么容易把内存用完的话,那项目上线之后,用户数量一增加,项目岂不是又要挂了?redis是怎么解决内存回收的问题呢?这时候有必要了解一下redis的内存回收机制。

二.redis内存回收策略

Redis的内存回收主要围绕以下两个方面:

1.Redis过期策略:删除过期时间的key值

2.Redis淘汰策略:内存使用到达maxmemory上限时触发内存淘汰数据

Redis的过期策略和内存淘汰策略不是一件事,实际研发中不要弄混淆了,下面会完整的介绍两者。

redis过期策略

过期策略通常有以下三种:

1.定时过期

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

2.惰性过期

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

3.定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。

Redis中同时使用了惰性过期和定期过期两种过期策略。

redis淘汰策略

1.简介

Redis的内存淘汰策略,是指当内存使用达到maxmemory极限时,需要使用LAU淘汰算法来决定清理掉哪些数据,以保证新数据的存入。

2、LRU算法

Redis默认情况下就是使用LRU策略算法。

LRU算法(least RecentlyUsed),最近最少使用算法,也就是说默认删除最近最少使用的键。

但是一定要注意一点!redis中并不会准确的删除所有键中最近最少使用的键,而是随机抽取3个键,删除这三个键中最近最少使用的键。

那么3这个数字也是可以可以设置采样的大小,如果设置为10,那么效果会更好,不过也会耗费更多的CPU资源。对应位置是配置文件中的maxmeory-samples。

3.缓存清理配置

maxmemory用来设置redis存放数据的最大的内存大小,一旦超出这个内存大小之后,就会立即使用LRU算法清理掉部分数据。

对于64 bit的机器,如果maxmemory设置为0,那么就默认不限制内存的使用,直到耗尽机器中所有的内存为止;,但是对于32 bit的机器,有一个隐式的闲置就是3GB

4.Redis数据淘汰策略

maxmemory-policy,可以设置内存达到最大闲置后,采取什么策略来处理。

对应的淘汰策略规则如下:

1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。

3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。

4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。

5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。

6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

5.缓存清理的流程

1)客户端执行数据写入操作

2)redis server接收到写入操作之后,检查maxmemory的限制,如果超过了限制,那么就根据对应的policy清理掉部分数据

3)写入操作完成执行。

 

redis之所以会使用内存爆满,很有可能是以下原因:

1)启动redis的时候配置文件中分redis分配的内存过小。这时候需要根据业务的需要,合理的给redis分配内存。比如:规划好哪些业务用redis存储,哪些用memcached,从业务代码层面对redis内存进行回收,条件允许的情况下可以多加几台机器做redis集群等等。

2)redis配置文件中没有配置redis内存回收参数maxmemory-policy。

掌握了以上的基础知识之后,问题还没解决,因为我们只知道内存被用完了,具体还需要定位是哪些业务用的内存比较多,或者想知道哪些key占的空间比较大,后面才能做进一步的优化。

方法1:bigkeys

这是redis-cli自带的一个命令。对整个redis进行扫描,寻找较大的key。例:

输出:

说明:

  1. 该命令使用scan方式对key进行统计,所以使用时无需担心对redis造成阻塞。
  2. 输出大概分为两部分,summary之上的部分,只是显示了扫描的过程。summary部分给出了每种数据结构中最大的Key。
  3. 统计出的最大key只有string类型是以字节长度为衡量标准的。list,set,zset等都是以元素个数作为衡量标准,不能说明其占的内存就一定多。所以,如果你的Key主要以string类型存在,这种方法就比较适合。

方案2:debug object key

redis的命令,可以查看某个key序列化后的长度。
例:

关于输出的项的说明:

  • Value at:key的内存地址
  • refcount:引用次数
  • encoding:编码类型
  • serializedlength:序列化长度
  • lru_seconds_idle:空闲时间
    关于refcount, encoding, lru_seconds_idle的更详细解释可以参考这里

几个需要注意的问题

  • serializedlength是key序列化后的长度(redis在将key保存为rdb文件时使用了该算法),并不是key在内存中的真正长度。这就像一个数组在json_encode后的长度与其在内存中的真正长度并不相同。不过,它侧面反应了一个key的长度,可以用于比较两个key的大小。
  • serializedlength会对字串做一些可能的压缩。如果有些字串的压缩比特别高,那么在比较时会出现问题。比如下列:

两个字串的实际长度都是30, 但str1的serializedlength为12, str2的为31。

  • redis的官方文档不是特别建议在客户端使用该命令,可能因为计算serializedlength的代价相对高。所以如果要统计的key比较多,就不适合这种方法。

方案3:redis rdb tools

这是一个redis rdb file的分析工具,可以根据rdb file生成内存报告。

3.1 安装

需要python2.4以上版本和pip。

3.2 生成内存报告

首先我们需要有一份rdb文件,如果你在配置中开启了rdb,那么redis会自动生成rdb文件。如果没有,可以手动执行bgsave。如果是线上机器,执行时要考虑机器负载等问题。拿到rdb文件后,我们就可以生成内存报告了。命令如下:

例:

输出了db,数据类型,key, 大小, 编码等多列信息。至于分析数据,你可以用shell,也可以保存成csv用excel排序,或者干脆存到db里,想怎么排怎么排。

如果只要知道最大的N个key, 可以使用-l选项。例:

3.3 查看单个key

如果我们只需要查询单个key所使用的内存可以不必依赖rdb file, 使用redis-memory-for-key命令即可。
例:

3.4 更多

  • 工具得出的内存值为近似值,这点可以参看作者的说明。”Why doesn’t reported memory match actual memory used?”
  • 工具通过分析rdb file中的key及value,反算出该kv在内存中的大小。计算时充分考虑了数据类型的影响,key本身长度的影响,内存分配等多种因素。虽然得出的大小不是真实值,但用于key大小的比较是完全可以的。
  • rdb的功能不仅于此,它还可以将kv导成json格式,也可以按正则表达式只导出部分key,
    更多使用方法可以查看:

也可以查看git上的帮助文档

总结:

  • 如果想粗略的看下最大key, 可以使用bigKeys。
  • 如果查询的key不多,key的压缩比又没有明显差异,可以使用debug object key。
  • 如果不介意安装个工具,那么redis rdb tools似乎是最佳选择。

一般情况下,项目上线之后运维都需要对redis,mysql服务器做内存监控,超出一定范围提前报警,如果公司有专门的运维团队,还可以对具体业务的使用情况做具体的分析上报,这样项目一出问题就可以快速定位解决。

 

发表评论

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