从源码看redis key 过期处理策略

252 阅读2分钟

redis的key 有三种过期处理策略

1 惰性删除过期key 
优点:
删除操作消耗时间短,不影响性能
缺点:
如果大量过期的键从此不被访问会浪费大量内存
2 主动删除过期key(定期)
优点:
解决如果大量过期的键从此不被访问会浪费大量内存
缺点:
如果大量过期键会浪费很多时间,影响性能
3 lru策略删除设置过期时间的key
优点:
解决如果大量设置了过期时间的key而且内存开销达到maxmemory的问题
缺点:
如果大量设置了过期时间的key且没达到maxmemory会浪费大量内存

reids command相关代码

void initServerConfig(void) {
...
 populateCommandTable();
...
}

void populateCommandTable(void) {
  int numcommands = sizeof(redisCommandTable)/sizeof(struct redisCommand);

    for (j = 0; j < numcommands; j++) {
        struct redisCommand *c = redisCommandTable+j;
        char *f = c->sflags;
        int retval1, retval2;

       ...
        retval1 = dictAdd(server.commands, sdsnew(c->name), c);
        rename前的初始命令
        retval2 = dictAdd(server.orig_commands, sdsnew(c->name), c);
    }
}

struct redisCommand redisCommandTable[] = {
    {"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
    {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
    {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
    {"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
    {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
    ...
}

设置key 超时时间

void setCommand(client *c) {
    ...
  setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
  ...
}
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
    if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
}

void setExpire(client *c, redisDb *db, robj *key, long long when) {
    ...
    kde = dictFind(db->dict,key->ptr);
    de = dictAddOrFind(db->expires,dictGetKey(kde));
    dictSetSignedIntegerVal(de,when);
    ...
}

惰性删除

void getCommand(client *c) {
    getGenericCommand(c);
}

int getGenericCommand(client *c) {
  ...
  if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
  ...
}

robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
    robj *o = lookupKeyRead(c->db, key);
    if (!o) addReply(c,reply);
    return o;
}

robj *lookupKeyRead(redisDb *db, robj *key) {
    return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);
}

robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
  ...
  if (expireIfNeeded(db,key) == 1) {
    ...
  }
  ...
}

int expireIfNeeded(redisDb *db, robj *key) {
   if (!keyIsExpired(db,key)) return 0;
   ...
  删除
   return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}

int keyIsExpired(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);

    if (when < 0) return 0;

    mstime_t now = server.lua_caller ? server.lua_time_start : mstime();

    return now > when;
}

删除方法
int dbSyncDelete(redisDb *db, robj *key) {
    /* Deleting an entry from the expires dict will not free the sds of
     * the key, because it is shared with the main dictionary. */
    if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr);
    if (dictDelete(db->dict,key->ptr) == DICT_OK) {
        if (server.cluster_enabled) slotToKeyDel(key);
        return 1;
    } else {
        return 0;
    }
}

主动删除

void initServer(void) {

 if (aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {
        serverPanic("Can't create event loop timers.");
        exit(1);
    }
}

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
    databasesCron();

}

void databasesCron(void) {
   /* Expire keys by random sampling. Not required for slaves
     * as master will synthesize DELs for us. */
    if (server.active_expire_enabled && server.masterhost == NULL) {
        activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
    } else if (server.masterhost != NULL) {
        expireSlaveKeys();
    }

  
  ...
}

void activeExpireCycle(int type) {
            ...
            while (num--) {
                dictEntry *de;
                long long ttl;
                随机挑选一个key
                if ((de = dictGetRandomKey(db->expires)) == NULL) break;
                ttl = dictGetSignedIntegerVal(de)-now;
                主动删除
                if (activeExpireCycleTryExpire(db,de,now)) expired++;
                if (ttl > 0) {
                    /* We want the average TTL of keys yet not expired. */
                    ttl_sum += ttl;
                    ttl_samples++;
                }
                total_sampled++;
            }
          ...
}

int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
    long long t = dictGetSignedIntegerVal(de);
    if (now > t) {
        sds key = dictGetKey(de);
        robj *keyobj = createStringObject(key,sdslen(key));

        propagateExpire(db,keyobj,server.lazyfree_lazy_expire);
        if (server.lazyfree_lazy_expire)
            dbAsyncDelete(db,keyobj);
        else
            dbSyncDelete(db,keyobj);
        notifyKeyspaceEvent(NOTIFY_EXPIRED,
            "expired",keyobj,db->id);
        decrRefCount(keyobj);
        server.stat_expiredkeys++;
        return 1;
    } else {
        return 0;
    }
}

lru策略删除设置了过期时间的key

int processCommand(client *c) {
...
 if (server.maxmemory && !server.lua_timedout) {
        int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
}
...
}

int freeMemoryIfNeededAndSafe(void) {
    if (server.lua_timedout || server.loading) return C_OK;
    return freeMemoryIfNeeded();
}

int freeMemoryIfNeeded(void) {
...
 while (mem_freed < mem_tofree) {
  ...

这里截选其中一种lru策略
 else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
                 server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
        {

            for (i = 0; i < server.dbnum; i++) {
                j = (++next_db) % server.dbnum;
                db = server.db+j;
                dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
                        db->dict : db->expires;
                if (dictSize(dict) != 0) {
                    de = dictGetRandomKey(dict);
                    bestkey = dictGetKey(de);
                    bestdbid = j;
                    break;
                }
            }
        }
        ...
        if (bestkey) {
          ...
            if (server.lazyfree_lazy_eviction)
                dbAsyncDelete(db,keyobj);
            else
                dbSyncDelete(db,keyobj);
      }
}
...
}