Redis中的过期键删除策略

版权声明:转载请注明出处! https://blog.csdn.net/zjq_1314520/article/details/79123579

我们知道在Redis数据库中,我们可以为相应的键设置过期时间
那么在相应键的过期时间到了,我们要通过什么方法来回收相应的键呢?

对于上面的问题,我们有以下三种不同的删除策略

定时删除:在设置键过期时间的同时,创建一个定时器,让定时器在过期时间来临的时候,立即删除相应的键。
惰性删除:我们先不管当前键是否过期,在使用的时候检查一下当前键是否已经过期,如果过期就立即删除当前键。
定期删除:系统每隔一定的时间对数据库做一次检查,删除里面的过期键。

注:以上的三种删除策略里面,第一和第三种被称为主动删除策略,第二种被称为被动删除策略。

定时删除
定时删除对于内存是最友好的,通过使用定时器,可以保证过期的键会尽可能快的被删除,并释放其占用的内存。

但是在过期键比较多的情况下,删除过期键可能会占用占用相当一部分的CPU(如果这个时候内存并不是很紧张但是有大量的命令在请求CPU处理),所以说这种策略是对CPU不友好的。

除此之外,创建定时器需要用到Redis服务器中的时间事件,而当前时间事件的实现方式是无序链表,创建一个事件的时间复杂度为O(n) —— 并不能高效的处理大量时间时间。

因此,要让服务器创建大量的定时器从而时间定时删除策略,当前来说还并不现实。(引用自《Redis设计与实现》)

惰性删除
惰性删除对于CPU是最友好的,其只在当前键的时候才会做其过期检查,这可以保证删除过期键的操作只会在非做不可得情况下才会进行,从而使CPU不会在删除其他无关紧要的键上面浪费时间。

非常明显,这种策略对于内存是不友好的。

同时还可能会造成内存泄漏——比如有的键可能永远也不会再使用了,这样子就永远存在内存中不会被释放。而Redis服务器又是非常依赖内存的,所以这是一个非常不好的结果。

定期删除
上面的两种策咯都有明显的缺点

定时删除:可能会占用大量的CPU时间,影响服务器的响应时间和吞吐量
惰性删除:可能会浪费大量的内存,导致内存泄漏

而定期操作是前面两种策略的一个折中

通过每隔一定的时间删除过期键,并通过限制删除操作的时长和频率来减少对CPU影响(相对定时删除)
定期删除不会导致有的键因为很长时间才会使用或者永不使用(但是已经过期)而带来的内存浪费甚至内存泄漏问题

定期删除策略需要考虑的问题有两个:删除操作执行的时长,删除操作执行的频率

所以说,如果采取定期删除策略的话就需要根据当前项目的情况,合理的设置删除操作的时长和频率,从而使系统的性能达到最优。


那么在Redis数据库中使用的是哪种删除策略呢?—— 惰性删除和定期删除

惰性删除策略的实现
Redis的源码中是使用db.c/expireIfNeed()函数实现的

/**
 * 检查 key 是否已经过期,如果是的话,将它从数据库中删除。
 *
 * 返回 0 表示键没有过期时间,或者键未过期。
 *
 * 返回 1 表示键已经因为过期而被删除了。
 */
int expireIfNeeded(redisDb *db, robj *key) {
    /* 取出键的过期时间 */
    mstime_t when = getExpire(db,key);
    mstime_t now;

    /* 如果没有过期时间 */
    if (when < 0) return 0; 

    /* 服务器loading的时候暂时先不工作 */
    if (server.loading) return 0;

    /* 获取当前时间*/
    now = server.lua_caller ? server.lua_time_start : mstime();

    /* 如果当前的是从(Slave)机
     * 0 if we think the key should be still valid,
     * 1 if we think the key is expired at this time. 
     * */
    if (server.masterhost != NULL) return now > when;

    /* 如果未过期,返回 0 */
    if (now <= when) return 0;

    /* 删除键 */
    server.stat_expiredkeys++;
    propagateExpire(db,key,server.lazyfree_lazy_expire);
    notifyKeyspaceEvent(NOTIFY_EXPIRED,
        "expired",key,db->id);

    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}

命令调用expireIfNeeded()的过程如图所示

Created with Raphaël 2.1.0开始 所有读写数据可的命令(GET,LRANGE,SADD等)所操作的键是否已经过期?删除键执行实际的命令流程yesno

定期删除策略的实现
过期键的定期删除策略由expire.c/activeExpireCycle()函数实现,每当服务器周期性操作server.c/serverCron函数执行时,activeExpireCycle()函数就会被调用,在短时间内,分多次便利服务器中的数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页