Redis “瘦身”指南
本文内容纲要:
-前言
-Redis内存回收
-问题原由
-持久键废弃
-过期键未回收
-遍历清除垃圾键
-如何遍历键
-如何判断键是否垃圾
-获取键大小
-管道加速
-脚本实现
-从根源避免问题
-小结
-Redis假死
-危险命令
前言
Redis应该是开发者最常用的缓存服务器了,它丰富的数据结构,快速高效的内存操作能帮助开发者迅速完成复杂功能的设计,可以说让人一旦使用过后很难再离开它了,甚至在一些业务中,完全可以用Redis替代传统的关系型数据库Mysql。
作为一个内存型数据库,Redis经常会遇到内存问题,今天我们来谈一下Redis常见的内存满的问题,介绍一下给Redis“瘦身”的通用方式。
文章经常被人爬,而且还不注明原地址,我在这里的更新和纠错没法同步,这里注明一下原文地址:http://www.cnblogs.com/zhenbianshu/p/7642682.html以防误人子弟。
Redis内存回收
Redis服务器的最大占用内存量由配置项maxmemory
决定,我们可以通过configsetmaxmemory2GB
的格式来配置。一旦Redis内存满,所有引起内存增加的操作都会被返回error。作为专业Redis服务器我们通常将此项设置为0
,以服务器系统内存来作为限制;
那么Redis使用内存达到了上限怎么办?Redis为我们提供了几种选项以自动回收内存,可以通过配置项maxmemory-policy
来配置;
noeviction
不回收;allkeys-lru
从所有键中删除最近最少使用的键;volatile-lru
从设置了过期时间的键中删除最近最少使用的键;allkeys-random
从所有键中随机删除;volatile-random
从设置了过期时间的键中随机删除;volatile-ttl
从设置了过期时间的键中选择存活时间最短的键删除;
最大内存回收策略需要根据业务来配置,如果纯粹做缓存,allkeys-lru
无疑是最合适的。如果存储了稍微重要的数据,为了防止Redis误删一些重要键,则需要选用noeviction
;
allkeys-lru
、allkeys-random
在内存满时都有键可删,可以腾出内存,但如果配置了其他的策略,数据库用久了(根据业务量),随着业务发展和数据积累,通常会累积到到服务器内存占用率高,利用率低的情况,则可能会遇到内存占用满的问题。
问题原由
产生问题的原因有:
持久键废弃
这是导致此问题的最常见情况。
有时候是开发人员的锅,开发不规范,未给有时效性的键设置过期时间,后续又不进行手动删除,键就成为无人管的孤儿键了。
还可能是整个业务慢慢被废弃,不知道哪一天起,业务整体已不再维护了,一批键自然也就没用了。比这更严重的是,如果使用List传递数据,消费进程已被停止,但生产进程未同步停止,还在往Redis里写数据。
过期键未回收
这个原因首先要谈到Redis的两种过期键删除策略:
- 惰性删除:在读取键时发现键已过期,则将其删除。
- 定期删除:Redis会从所有设置了过期时间的键中选取100个,删除已过期的键,如果已过期的键超过25个,则再次进行此操作。此删除操作由配置项
hz
决定,Redis默认每秒进行10次;
如果我们产生过期键的速度很快,最多可导致Redis25%的过期键没有被及时删除。
遍历清除垃圾键
由上,明白了问题产生的原因,解决Redis内存满的方法就明确了:清除这些垃圾键。于是就面临着两个问题:
如何遍历键
对于查找键,我们首先想到的是KEYS
,但KEYS
的时间复杂度是O(n)
,n是Redis内键的总数,如果Redis内键很多还是会有性能问题,导致其他命令被阻塞的。
这里介绍一个键遍历命令:SCAN
。
SCANcursor:
0=>cursor,//cursor=0遍历结束
1=>array(key1,key2...)
需要注意的是SCAN命令是在版本2.8.0
加入的,如果是之前的版本,可以考虑解析Redis的RDB文件来获取所有的键。有坑,参见我之前的文章:扩充你的工具箱-大行文件的处理
如何判断键是否垃圾
我们有三种异常键需要处理:
- 过期键:这些键会在被SCAN到时被自动删除,不再考虑。如果是解析RDB文件获取到的键,在查询时也会被自动删除;
- 长时间未读写的键,很可能是业务不再需要的键;
- 占用大量内存的键,有可能是在不停地写,但未消费。
这里介绍Redis的另一个命令OBJECT
,使用它可以从内部查看key对象的状态。使用OBJECTIDLETIMEkey
来获取key的闲置时间,我们可以判断key闲置时间大于一个时间段(根据业务自定)的为已废弃。
此外还能使用OBJECTREFCOUNTkey
获取key引用所储存的值的次数,OBJECTENCODINGkey
获取key储存的值所使用的内部表示。
获取键大小
而获取Redis某键占用内存大小,则通过另一个命令DEBUGOBJECT
来获取,此命令会返回比OBJECT
命令更详细的内部数据。
DEBUGOBJECTtest
Valueat:0x7fb0ee16ebd0refcount:1encoding:embstrserializedlength:6lru:12362780lru_seconds_idle:4
结果包括内存地址、引用数、内部编码表示、序列化后的长度、最近最少使用标识值,闲置时间,我们可以解析此结果串来获取对应的数据。
需要注意,key作为复合键拥有大量字段时使用DEBUG命令计算内存会使Redis阻塞较长时间,且Redis官方并不建议在客户端使用此命令
。
我们也可以先使用TYPEkey
获取键的类型,再根据类型获取其键的大小,如对字符串使用LEN
,对哈希表使用HLEN
。
要注意在删除特别大的复合键时,建议先逐步清空键内的字段,防止因字段过多,Redis阻塞较长时间。
管道加速
Redis支持pipeline
管道技术,一次请求/响应服务器能实现处理并响应多个请求。这样就可以将多个命令同时发送到服务器,不等待回复,直接在最后获取多个结果。
PHP中使用MULTI(Redis::PIPELINE)
和EXEC()
命令来实现管道;
脚本实现
下面是个简单的脚本:
$redis=newRedis();
$redis->connect('127.0.0.1');
do{
$keys=$redis->scan($cursor);
$pipeline=$redis->multi(Redis::PIPELINE);
foreach($keysas$key){
$idle_time=$redis->object('idletime',$key);
if($idle_time>180*24*3600){
$pipeline->del($key);
}
//todo判断类型进而判断占用内存大小,再删除
}
$pipeline->exec();
}while($cursor!=0);
从根源避免问题
以上的脚本肯定也会在删除键时影响Redis的效率,最好的情况还是从根源就避免此类情况,以下是一些建议:
-
规范化开发;
首先是键命名要规范,让人见名知义,这样在人工排错或删除时也有判断依据,然后最好有完善的Redis键文档,以保证业务在很长时间,经手多人后也能资料可查。
-
使用
HashSet
替代Key-Value
;将业务中某一族的键以HashSet的方式存储,以替代普通的key-value类型。不仅可以省去为每个键设置前缀以节约内存,也便于统一管理。
-
有时效性的键注意设置过期时间;
-
合理设置定时清除过期键频率
hz
,在Redis不做多余操作的情况下,使过期键尽量能被删除; -
做好Redis内存的监控,在达到某个阈值时查找问题并解决。
小结
最后多絮叨两句经验:
Redis假死
我在使用守护进程时Redis有假死情况,PHP和Redis都不报错,但命令都返回false,这种情况可以使用Redis的ping()命令,来探测Redis连接是否还在,如果不在则再建立新的连接。此问题很可能是由服务器配置引起的,如果您有知道此问题的原由或有好的解决办法,烦请指点一二。
危险命令
不要在没看文档的情况下在线上使用Redis命令,例如debugsegfault
,别问我怎么知道的。
嗯,希望大家都能处理好跟Redis这个好朋友的关系。
关于本文有什么问题可以在下面留言交流,如果您觉得本文对您有帮助,可以点击下面的推荐
支持一下我。博客一直在更新,欢迎关注
。
本文内容总结:前言,Redis内存回收,问题原由,持久键废弃,过期键未回收,遍历清除垃圾键,如何遍历键,如何判断键是否垃圾,获取键大小,管道加速,脚本实现,从根源避免问题,小结,Redis假死,危险命令,
原文链接:https://www.cnblogs.com/zhenbianshu/p/7642682.html