环境: CentOS 6.5 Redis 2.8 

    问题描述: 最近几天有一个使用了俩年的redis实例的内存使用情况的增长速率很是诡异,突然从1G增长到了4个多G,一开始认为是因为新项目上线有在使用,但是询问了开发发现新上线的项目并没有使用这个redis实例.并且发现一个比较诡异的情况就是在这个redis上面只有61个hash类型的key,并且使用redis-cli -p 6379 --bigkeys 进行大key分析,分析出来的情况如下:

# Scanning the entire keyspace to find biggest keys as well as
# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec
# per 100 SCAN commands (not usually needed).

[00.00%] Biggest hash   found so far 'rc:value:0116:16841639' with 6 fields

-------- summary -------

Sampled 61 keys in the keyspace!
Total key length in bytes is 1366 (avg len 22.39)

Biggest   hash found 'rc:value:0116:16841639' has 6 fields

0 strings with 0 bytes (00.00% of keys, avg size 0.00)
0 lists with 0 items (00.00% of keys, avg size 0.00)
0 sets with 0 members (00.00% of keys, avg size 0.00)
61 hashs with 342 fields (100.00% of keys, avg size 5.61)
0 zsets with 0 members (00.00% of keys, avg size 0.00)

    可以比较明显得看得到,所有的key加起来的值非常的小,几乎可以排除存在大key。

    然后查看有key和value的内存的占用情况:使用的工具是 redis-rdb-tools分析redis的内存分析:

    redis-rdb-tools的安装使用:https://help.aliyun.com/knowledge_detail/50037.html

[root@shell~]# rdb -c memory 6379dump.rdb
database,type,key,size_in_bytes,encoding,num_elements,len_largest_element
0,hash,rc:value:0116:16841639,142,ziplist,6,10
0,hash,rc:value:0118:30680413,142,ziplist,6,10
0,string,limit_34864207,88,string,8,8
0,hash,rc:hammer:color:0121:30680413,109,ziplist,3,8
0,string,limit_28962590,88,string,8,8
0,hash,rc:value:0116:30755821,142,ziplist,6,10
0,hash,rc:draw:14073376,115,ziplist,4,8
0,hash,rc:value:0119:30680413,142,ziplist,6,10
0,string,limit_34994213,88,string,8,8
0,string,limit_35067785,88,string,8,8
0,string,limit_33964751,88,string,8,8
0,hash,rc:value:0117:20638549,142,ziplist,6,10
0,hash,rc:value:0117:32835296,142,ziplist,6,10
0,hash,rc:value:0117:33047903,142,ziplist,6,10
0,hash,rc:value:0118:31405910,142,ziplist,6,10
0,string,limit_31912444,88,string,8,8
0,string,limit_34745689,88,string,8,8
0,hash,rc:value:0117:30755821,142,ziplist,6,10

    由于篇幅过长,所以部分分析结果没有打印出来,但是根据打印出来的size_in_bytes的计算统计发现总共才3w多bytes.所以基本上排除了key-value过大占用了这么多的内存。

    Google了一番之后,对于这种情况的解释大部分的解释都是基于redis的空间碎片,因为redis的内存分配都是基于page/block为最小单位分配,在删除掉del的时候由于这个page上面还有其他的key,所以这个page是不能释放的,只有这个page上面的所有key删除掉才能释放;但是针对于我这个情况来说,那怕一个key使用一个page,一个page我设置为16K或者更大,这些加起来也是61*16K 这么多,离4G还是有非常大的距离,所以这个情况可以排除掉了.

    在毫无头绪的情况下,无意中发现这个redis的链接有点不对劲,redis-cli -p 6379 info stats观察一端时间发现OPS基本是1000左右,但是在连接数这块却有3000的样子,并且链接数也是在这几天由1000增长到3000,然后我猜想有没有可能和链接数有关系呢?

redis-cli -p 6379 client list

    使用上诉命令查看redis内部的链接情况:

id=8546905 addr=x.x.x.x:14061 fd=1094 name= age=5014815 idle=3639147 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546981 addr=x.x.x.x:14858 fd=1596 name= age=5014439 idle=3640850 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546957 addr=x.x.x.x:14768 fd=1590 name= age=5014540 idle=3639017 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546960 addr=x.x.x.x:14798 fd=1593 name= age=5014535 idle=3641126 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8547541 addr=x.x.x.x:22018 fd=1599 name= age=5011777 idle=3641812 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546930 addr=x.x.x.x:14457 fd=1425 name= age=5014675 idle=3640389 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8547242 addr=x.x.x.x:18366 fd=1598 name= age=5013182 idle=3640371 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546959 addr=x.x.x.x:14789 fd=1591 name= age=5014536 idle=3639087 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546908 addr=x.x.x.x:14138 fd=1116 name= age=5014786 idle=3639026 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546980 addr=x.x.x.x:14842 fd=1595 name= age=5014443 idle=3640372 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546979 addr=x.x.x.x:14806 fd=1594 name= age=5014449 idle=3638217 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush
id=8546924 addr=x.x.x.x:14143 fd=1161 name= age=5014709 idle=3638929 flags=N db=1 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=lpush

    上诉是问题解决之后的部分输出.几个重要的参数解释:

-age: 链接的存活时长
-idl: 链接的空闲时长
-qbuf: 客户端的缓冲区的容量
-qbuf-free: 缓冲区的空闲容量

    一开始我并没有对这些参数很是重视,只是针对idl的时间很长的链接做了kill操作:

client-cli -p 6379 client kill ip:port

    在kill掉很多idl很长的链接的时候,在去看used_memory的值确实降了下来,但是还是有3G的样子,那么可以比较确认这个used_memory的值这么大是和链接有关系的.然后翻阅了资料获取得到:

(2)输入缓冲区:qbuf、qbuf-free

    Redis为每个客户端分配了输入缓冲区,它的作用是将客户端发送的命令临时保存,同时Redis从会输入缓冲区拉取命令并执行,输入缓冲区为客户端发送命令到Redis执行命令提供了缓冲功能,如图4-5所示。

   client list中qbuf和qbuf-free分别代表这个缓冲区的总容量和剩余容量,Redis没有提供相应的配置来规定每个缓冲区的大小,输入缓冲区会根据输入内容大小的不同动态调整,只是要求每个客户端缓冲区的大小不能超过1G,超过后客户端将被关闭

链接:http://www.jianshu.com/p/70f3b68a7fd7


    然后在查看链接,发现有些链接的qbuf的值将近50M,但是qbuf-free却是0,并且这样子的链接并不在少数,有几百个的样子.然后在查阅这些链接的idl已经很长了,将这些链接kill之后,used_memory的值厘米降下来了,只有几十M的样子了。


    used_memory统计的是redis实例分配的内存,其中还包含redis的内存管理和一些内存的stats信息,并不仅仅是data信息.

    这些链接这么多的原因是因为在几周之前client有做了迁移,但是旧的服务并没有立即关掉只是在这几天才把服务器关掉的,但是由于client没有主动关闭链接,并且在server端设置的timeout为0,所以导致这些链接一直存活,不能断开,才会出现这些情况。




    若有其中不正确的地方,请指正,自己也是菜鸟一枚.


【20180502】

补充:

    redis内存使用假如当前使用了2G的内存,但是由于某些原因,内存的使用情况激增到5G,然后将一些不必要的的key-value移除(比如说删除3G的数据),按照逻辑上面来说,redis使用的内存应该减少到2G,但是有可能实际情况确实redis还是使用5G内存并没有释放掉。那么这个时候若想要完全释放掉内存的话,那么需要重启redis才能够正常的释放掉内存。

    还有一个就是在重启redis的时候,需要注意一点就是最好将当前配置全部重写到配置文件中,避免已经做了更改配置在重启之后失效。

    命令是: CONFIG REWRITE

    命令是原子性的,在重写写入配置文件的时候的中途中若是发生意外情况,那么也会进行回滚的。

案例:

    今早在针对一个redis实例进行内存释放的时候,即重启redis。发生了一些事故,在本地进行redis-cli -p 6379的时候进行一些redis命令是可以正常的运行的,但是在远程服务器redis-cli -h redis_ip -p 6379的时候能够正常的链接进来,但是执行任意命令都会报错:

    DENIED Redis is running in protected mode because protected mode is enabled, no bind address was specified, no authentication password is requested to clients. In this mode connections are only accepted from the loopback interface. If you want to connect from external computers to Redis you may adopt one of the following solutions: 1) Just disable protected mode sending the command 'CONFIG SET protected-mode no' from the loopback interface by connecting to Redis from the same host the server is running, however MAKE SURE Redis is not publicly accessible from internet if you do so. Use CONFIG REWRITE to make this change permanent. 2) Alternatively you can just disable the protected mode by editing the Redis configuration file, and setting the protected mode option to 'no', and then restarting the server. 3) If you started the server manually just for testing, restart it with the '--protected-mode no' option. 4) Setup a bind address or an authentication password. NOTE: You only need to do one of the above things in order for the server to start accepting connections from the outside.

    看到这个错误信息的原因,我第一想法就是开启了密码验证登陆,然后我第一时间就在重新查看配置文件。但是让我惊讶的是发现还是配置文件# requirepass foobared 这个参数是注释掉的,说明是没有开启密码验证登陆的。然后我在重新查看错误信息,发现有在解决方案中有在命令行执行config set protected-mode no ,然后我在配置文件中搜索protected-mode这个参数,但是却没有任何的发现。但是我在本地登陆redis之后执行config get protected-mode发现这个参数的值却是yes,看到这个情况下我立马执行了config set protected-mode no,然后链接立马正常了。

    但是这里我却留下了一个疑问,为什么会出现这种情况呢?当我看到好多关于protected-mode解决方案中有一个方案就是将bind的注释先去掉,然后绑定回环地址和当前redis的ip地址,这个时候我想可能会不会是因为bind的原因导致这种现象呢。关于这个问题我自己做了一番实验,redis的版本是3.2.9。

    关于具体的实验步骤就是将bind是否注释掉并且绑定IP地址,其余的配置信息不做任何的更改。然后实验结果证明了我的这个猜想,所以我可以得出这个结论:

    在bind没有设置,直接启动redis的时候,这时候protected-mode这个参数默认值是yes,这个时候redis只能允许本地根据回环地址登陆之后才能进行操作,非本地回环地址是可以登陆的但是却无法进行任何的操作。