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

用Redis咋自动删过期键值对,定时清理那些老数据的思路分享

说到用Redis自动删除过期键值对,清理那些老数据,这其实是Redis本身就很擅长的一个事儿,咱们今天就不聊那些特别深奥的原理,就说说它怎么干的,以及咱们自己还能怎么帮把手,让清理工作更稳妥。

最省心的方法,就是交给Redis自己来,Redis自己有一套聪明的机制来处理过期的键,这在你用EXPIRE或者SET命令直接设置过期时间的时候就已经启动了,这套机制主要有两种办法混着用,一种叫“惰性删除”,另一种叫“定期删除”。(这个思路在Redis官方文档里有明确说明)

这“惰性删除”啊,听起来有点懒,但其实很巧妙,它的意思是:别急着去扫荡所有数据,等什么时候某个键被访问了,Redis才顺便检查一下它过期了没,如果过期了,那就当场把它删掉,然后返回个空值给客户端,好像这个键从来不存在一样,这么做最大的好处就是省力气,想象一下,要是不管有没有人用,Redis都一刻不停地去检查成百上千万个键过没过期,那CPU肯定累得够呛,这种“用时检查”的法子,把计算资源用在了刀刃上,但它的缺点也挺明显:要是一个过期的键一直没人来访问,那它就会一直赖在内存里,成了“僵尸数据”,白占地方,光靠这个“懒”办法不行。

为了补上这个漏洞,Redis还有个“定期删除”的机制,这个就主动多了,Redis会每隔一段时间(默认是每秒10次)就悄悄地活动一下,从设置了过期时间的键里面,随机抽出一小部分样本(比如20个)来检查,它可不是把整个数据库翻个底朝天,那样太慢了,它就是随机抽检,把抽出来并且已经过期的键给删掉,如果在这一批样本里,发现过期的键比例很高,说明过期键不少啊,那Redis就会立马再抽一批接着删,直到过期的键比例降下来为止,通过这种抽检加动态调整力度的方式,Redis就在清理效率和CPU消耗之间找了个平衡,既不会因为清理工作把自己卡死,又能比较及时地清理掉大部分过期数据。(这个抽样的策略在Antirez的博客和很多技术分析文章里都有讨论)

用Redis咋自动删过期键值对,定时清理那些老数据的思路分享

那既然Redis自己已经这么能干了,是不是咱们就完全不用管了呢?大多数情况下是的,尤其是数据量不是特别恐怖的时候,这套组合拳很好用,但有时候你也得留个心眼,如果你的Redis里塞了海量的键,而且这些键几乎都设置了过期时间,但每个键存在的时间又特别短,像烟花一样瞬间就过期了,这时候,光靠Redis自身的定期抽样可能就有点忙不过来了,会导致很多键已经过期了但还没被抽检到,内存占用可能会比你预期的高,再比如,有些特别重要的数据,你希望它的过期和清理是百分百准时的,不能有一丝拖延,那Redis这种概率性的清理方式可能就达不到你的要求。

这时候,咱们就得自己想想办法,给Redis搭把手了,这里也有几个简单的思路。

用Redis咋自动删过期键值对,定时清理那些老数据的思路分享

一个办法是,在程序代码里下功夫,你在写业务逻辑的时候,除了用Redis的GET命令,可以试试用GETDEL命令(如果Redis版本支持的话),这个命令是啥意思呢?就是获取这个键的值,然后不管它过没过期,直接就把这个键删掉了,这特别适合那种只用一次就作废的数据,比如限时的验证码、一次性的令牌什么的,这就相当于把“惰性删除”的主动权掌握在自己手里了,保证了一次性数据绝对不会残留。

另一个办法,算是上个办法的增强版,就是弄个简单的脚本,这个脚本干嘛呢?它不用像Redis内部那样随机抽样,而是利用Redis提供的命令,比如SCAN命令,慢慢地、分批地扫描整个数据库,扫描的时候,可以用TTL命令检查每个键的剩余生存时间,如果你发现某个键的TTL是负数,那就说明它已经过期了(Redis里TTL返回-2表示键不存在,-1表示没设置过期时间,负数通常指已过期但未被删除),这时候你就可以用DEL命令手动删掉它,这个脚本可以设置成在凌晨三四点这种访问量最少的时候,由定时任务工具(比如Linux的cron)来跑一跑,专门清理那些“漏网之鱼”,这样做对线上服务的影响最小。(这种外部扫描补偿的思路在运维实践的文章中很常见)

还有一个更根本的预防思路,就是在设计系统的时候,别把所有希望都寄托在“过期删除”上,可以考虑给不同的数据类型用不同的Redis数据库(如果用了多个db的话),或者干脆用不同的Redis实例来隔离,把那种生存期极短、变化频繁的会话数据放在一个实例里,把长期保存的配置信息放在另一个实例里,你甚至可以给这个存放短期数据的Redis实例设置一个最大的内存上限(通过maxmemory参数),并且配上淘汰策略(maxmemory-policy),比如设置为volatile-ttl,让Redis在内存不够用时,优先淘汰那些过期时间更早的键,这样就算偶尔有清理不及时的,也不至于把整个Redis拖垮。

所以总结一下,用Redis自动清理过期数据,首选也是最佳选择,就是相信并利用好它自带的“惰性删除”加“定期删除”机制,这已经能应对八九成的场景了,但如果你的业务比较特殊,对数据清理的及时性要求极高,或者数据量巨大、过期非常集中,那就可以考虑在应用层用GETDEL这样的命令,或者写个低频的补偿脚本在业务低峰期扫一下,最保险的,还是在系统设计之初就做好数据和资源的隔离,设定好内存上限,多一层保障,这样里外结合,就能让Redis里的老数据自动、及时地清理掉,保证内存的高效利用。