一、故障问题
运维的同学反应:某个业务的redis使用总量内存增长了大约20g,但是业务访问量却没有显著增长。
仔细排查过,一直没有没有查到原因。这部分突增的内存开销是由什么造成的,消耗在哪里?
内存增长如下图:
日志中没有异常信息。但是观测到,cow耗费内存同时增长。
实例redis_cluster_feed_d2_0_39200异常日志:
二、问题排查过程
为了排查问题方便,选取redis_cluster_feed_d2_0_39200实例作为排查对象。
1. 排查这部分开销是内核系统行为,还是redis主动行为。这部分主要通过used_memory来确定。
监控显示同时段used_memory增长了2g,确定是redis的主动行为。
2. 排查这部分开销是用在kv上,还是其他开销上。
观测到前后时段落磁盘rdb大小几乎没变化,而且dba之前也做过rdb key分析,都显示key的数量、类型、大小都无显著变化。
说明没有bigkey,也没有大量写入。
建议dba将前后两个时间段的rdb重新加载,以确认是否是kv存储造成的其他开销。交给dba确认。(这个做了,就可以直接确认rehash问题,不需要继续后面的排查)。
3. 跟2同步排查这部分开销是否是业务访问造成。这部分主要通过instantaneous_input_kbps,qps, bigkey来查看。
大流量和bigkey都容易造成内存爆涨(但一般爆涨之后,这部分内存都会及时归还,其实不会出现只归还一部分内存的情况)。
监控显示同时段qps大约600左右,instantaneous_input_kbps大约30上下,非常小。且dba反应,这是一个写少读多的业务。排除业务访问造成的影响。
-
经过以上排查,基本可以确认这是由于redis为了维护自己的数据库主动发起的。
比如常见的清理过期键,清理超时客户端,给slave发送ping,rehash等等。其中rehash非常容易造成内存爆涨(网上文章很多)。
-
排查是否由rehash造成该问题, 网上测试显示:
(a) 当num(keys)=215=32768,从下面可以看出到key的个数为32769时,内存涨了一些,但是还不明显。
image.png
(b) 当num(keys)=220=1048576,从下面可以看出到key的个数为1048577时,内存涨了32M。
因为rehash会扩容,所以新的哈希表中的槽位变为了221 * 2(因为每个key都设置了过期时间,expires表),指针为8个字节,221 ️ 2 ️ 8 = 225 = 32MB
image.png
(c) 当num(keys)=226=67108864,从下面可以看出到key的个数为67108865时,内存涨了2GB。
因为rehash会扩容,所以新的哈希表中的槽位变为了227 * 2(因为每个key都设置了过期时间,expires表),指针为8个字节,227 ️ 2 ️ 8 = 231 = 2GB
image.png
Redis过期键存储:命令:expire key seconds
存储时未过期:往expire库中添加/或者替换一条expires[key]:timeout数据
此时数据库中存在两条关于这条key的信息:
dict------dict[key]:value
expires--expires[key]:timeout所以是有两个字典的,也就是说没有过期键时,当key到达 134217730=67108864*2时,由于rehash的原因会增长2g。
当时我们线上实例的num(keys)=134372150略微大于134217730,也是符合这个情况的。整个增长过程如下图:
image.png
-
三、结论
1. 内存增长原因:当key到达 134217730=67108864*2时,由于rehash的原因会增长2g。
2. 归还1g原因:rehash完成后,会释放ht[0]。
3. cow增长原因:rehash过程会更改数据数据指针。造成大部分数据虽未被访问,但是却发生改变,进而导致大范围的缺页异常。