redis过期键删除机制的源码分析

        在redis数据库中,可以对键值对设置过期时间。当键值对过期时,redis会通过一定的机制将过期键删除。

redis的过期键删除策略有两种:定期删除和惰性删除。

惰性删除

       惰性删除是每次获取键值对时,都对获取的键进行过期检查,如果过期的话,就删除该键值对;如果没过期,就返回该键。
       惰性删除策略的好处是对cpu时间比较友好,每次只检查当前处理的键值对,不会对其他过期的键值对花费
cpu时间。
       惰性删除策略的缺点就是对内存不友好,一个键已经过期,但是这个键还会留在数据库中,只要它不被访问,就不会被删除,一直占用这内存空间。如果这些过期键长期不被访问,将永远不会被删除,我们可以视作内存泄漏——无用的数据占据了大量内存,而服务器不去释放它。当然,用户可以通过手动执行FLUSHDB来删除过键。
       过期键的惰性删除策略由db.c/expireifNeeded函数实现,所有对数据库的读写命令执行之前都会调用
expireifNeeded来检查命令执行的键是否过期:
       1)键过期,expireifNeeded将键从数据库中删除
       2)键未过期,expireifNeeded不作任何处理
int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;
    if (when < 0) return 0; /* No expire for this key */
    now = server.lua_caller ? server.lua_time_start : mstime();
    if (server.masterhost != NULL) return now > when;
    /* Return when this key has not expired */
    if (now <= when) return 0;
    // 删除过期键
    server.stat_expiredkeys++;
    propagateExpire(db,key,server.lazyfree_lazy_expire);
    //server.lazyfree_lazy_expire为1进行异步删除(懒空间释放),反之同步删除
    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}
//获取键的过期时间
long long getExpire(redisDb *db, robj *key) {
    dictEntry *de;
    if (dictSize(db->expires) == 0 ||
       (de = dictFind(db->expires,key->ptr)) == NULL) return -1;
    return dictGetSignedIntegerVal(de);
}

定期删除

       定期删除是每隔一段时间,程序对数据库检查一次,删除数据库里的过期键。至于每次检查多少数据库,删除多少过期键,由算法决定。
      定期删除策略的关键点就是删除操作执行的时长和频率:
      1)如果删除操作太过频繁或者执行时间太长,就对cpu时间不是很友好,cpu时间过多的消耗在删除过期键上。
      2)如果删除操作执行太少或者执行时间太短,就不能及时删除过期键,导致内存浪费。
      定期删除策略由expire.c/activeExpireCycle函数实现。在redis事件驱动的循环中的eventLoop->beforesleep和周期性操作databasesCron都会调用activeExpireCycle来处理过期键。activeExpireCycle在规定的时间,分多次遍历各个数据库,从expires字典中随机检查一部分过期键的过期时间,删除其中的过期键。
//周期性操作中进行慢速过期键删除, 执行频率同databasesCron的执行频率
//执行时长为1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100
void databasesCron(void) {
    if (server.active_expire_enabled && server.masterhost == NULL) {
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
    } else if (server.masterhost != NULL) {
        expireSlaveKeys();
    }
}//进行快速过期键删除,执行间隔和执行时长都为ACTIVE_EXPIRE_CYCLE_FAST_DURATION
void beforeSleep(struct aeEventLoop *eventLoop) {
    if (server.active_expire_enabled && server.masterhost == NULL)
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_FAST);
}
activeExpireCycle函数实现
void activeExpireCycle(int type) {
    static unsigned int current_db = 0; /* 上次定期删除遍历到的数据库id*/
    static int timelimit_exit = 0;      /* Time limit hit in previous call? */
    static long long last_fast_cycle = 0; /* 上一次执行快速定期删除的时间点 */
    int dbs_per_call = CRON_DBS_PER_CALL;//每次定期删除,遍历的数据库的数量
    long long start = ustime(), timelimit;
    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
        if (!timelimit_exit) return;
        //快速定期删除的时间间隔是ACTIVE_EXPIRE_CYCLE_FAST_DURATION
        //ACTIVE_EXPIRE_CYCLE_FAST_DURATION是快速定期删除的执行时长
        if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
        last_fast_cycle = start;
    }
    if (dbs_per_call > server.dbnum || timelimit_exit)
        dbs_per_call = server.dbnum;
    //慢速定期删除的执行时长
    timelimit = 1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;
    timelimit_exit = 0;
    if (timelimit <= 0) timelimit = 1;
    if (type == ACTIVE_EXPIRE_CYCLE_FAST)
        timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /*删除操作的执行时长 */
    for (j = 0; j < dbs_per_call; j++) {
        int expired;
        redisDb *db = server.db+(current_db % server.dbnum);
        current_db++;
        do {
            ……
            if ((num = dictSize(db->expires)) == 0) {
                db->avg_ttl = 0;
                break;
            }
            //在每个数据库中检查的键的数量
            if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)
                num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;
            //从db->expires中随机选取num个键进行检查
            while (num--) {
                if ((de = dictGetRandomKey(db->expires)) == NULL) break;
                ttl = dictGetSignedIntegerVal(de)-now;
                //过期检查,并对过期键进行删除
                if (activeExpireCycleTryExpire(db,de,now)) expired++;
                ……            
            }
            ……
           //每次检查只删除ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4个过期键
        } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);
    }
}

 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值