【Redis5源码学习】浅析redis命令之persist篇

NoSay 2019-10-14 20:30:15
原文地址:https://segmentfault.com/a/1190000020589667

Grape


命令语法

命令含义:移除给定key的生存时间,将这个 key 从『易失的』(带生存时间 key )转换成『持久的』(一个不带生存时间、永不过期的 key )。
命令格式:

PERSIST key

命令实战:

redis> SET mykey "Hello"
OK
redis> EXPIRE mykey 10
(integer) 1
redis> TTL mykey
(integer) 10
redis> PERSIST mykey
(integer) 1
redis> TTL mykey
(integer) -1
redis> 

返回值:
当生存时间移除成功时,返回 1 .
如果 key 不存在或 key 没有设置生存时间,返回 0 .

源码分析

命令入口:

/* PERSIST key */
void persistCommand(client *c) {
    //查找这个key,调用lookupKeyWrite函数
    if (lookupKeyWrite(c->db,c->argv[1])) {
            //如果这个key存在,调用删除函数,调用dictGenericDelete函数
        if (removeExpire(c->db,c->argv[1])) {
            addReply(c,shared.cone);
            server.dirty++;
        } else {
            addReply(c,shared.czero);
        }
    } else {
        addReply(c,shared.czero);
    }
}

此处是persist命令的入口,我们可以看到这个命令在执行的过程中有一下几个阶段:1.查找你要执行persist命令的key,看是否存在,如果不存在则直接返回客户端信息。如果存在,这调用删除函数移除过期时间,删除成功返回给客户端成功信息,aof标志加1。

判断key的可用性

/* Lookup a key for write operations, and as a side effect, if needed, expires
 * the key if its TTL is reached.
 *
 * Returns the linked value object if the key exists or NULL if the key
 * does not exist in the specified DB. */
/* 查找这个key
*/
robj *lookupKeyWrite(redisDb *db, robj *key) {
    //首先查找这个key是否过期,过期则删除,此函数在之前的几篇文章中已经介绍过,此处不在赘述。
    expireIfNeeded(db,key);
    在上一个步骤的基础上,如果此键被删除则返回null,否则返回这个键的值
    return lookupKey(db,key,LOOKUP_NONE);
}
int removeExpire(redisDb *db, robj *key) {
    /* An expire may only be removed if there is a corresponding entry in the
     * main dict. Otherwise, the key will never be freed. */
    serverAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL);
    return dictDelete(db->expires,key->ptr) == DICT_OK;
}

这个过程是在判断这个key是否存在,它分为两个部分,首先他会查找这个key是否过期,如果过期则删除,然后去查询这个key,反之直接查找这个key,此处又一个疑问,为什么不是先查找这个key是否存在再去判断是否过期?我的想法:redis中存储过期时间和key-value是两个字典,在expire字典中存在的值一定在key-value字典中存在,那么在expire过期的时候就涉及reids的惰性删除机制,为了满足这个机制,reids在设计的时候会先对key的过期时间做判断,然后再去判断这个key是否存在,此时如果对redis的删除机制感兴趣的话,请查阅关于Redis数据过期策略

真正移除的过程

/* Search and remove an element. This is an helper function for
 * dictDelete() and dictUnlink(), please check the top comment
 * of those functions. */
static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
    uint64_t h, idx;
    dictEntry *he, *prevHe;
    int table;

    //我们知道在redis中的两个ht,ht[0]和ht[1],如果这两个ht中没有已使用空间,直接return null
    if (d->ht[0].used == 0 && d->ht[1].used == 0) return NULL;
    if (dictIsRehashing(d)) _dictRehashStep(d);
    //根据key来获取他的hash值
    h = dictHashKey(d, key);
    //遍历ht[0]和ht[1],如果找到则删除他的key-value,此处因为是删除过期时间,我们可以看到外层调用函数传入的是db->expires和key,则此块是从expire的字典中删除,            for语句中是删除的过程,很简单,遍历,如果找到了删除,同时释放空间。
    for (table = 0; table <= 1; table++) {
        idx = h & d->ht[table].sizemask;
        he = d->ht[table].table[idx];
        prevHe = NULL;
        while(he) {
            if (key==he->key || dictCompareKeys(d, key, he->key)) {
                /* Unlink the element from the list */
                if (prevHe)
                    prevHe->next = he->next;
                else
                    d->ht[table].table[idx] = he->next;
                if (!nofree) {
                    dictFreeKey(d, he);
                    dictFreeVal(d, he);
                    zfree(he);
                }
                d->ht[table].used--;
                return he;
            }
            prevHe = he;
            he = he->next;
        }
        if (!dictIsRehashing(d)) break;
    }
    return NULL; /* not found */
}

此处是redis真正此处过期过期时间的步骤,此处只要设计dict的一些操作,例如遍历dict的ht[0],ht[1]和删除一个dict的值。大致的思路是先找到这个key然后进行释放。因为此处在前面文章已经有所描述,故直接放上传送门:dict是如何find一个元素的

声明:该文章系转载,转载该文章的目的在于更广泛的传递信息,并不代表本网站赞同其观点,文章内容仅供参考。

本站是一个个人学习和交流平台,网站上部分文章为网站管理员和网友从相关媒体转载而来,并不用于任何商业目的,内容为作者个人观点, 并不代表本网站赞同其观点和对其真实性负责。

我们已经尽可能的对作者和来源进行了通告,但是可能由于能力有限或疏忽,导致作者和来源有误,亦可能您并不期望您的作品在我们的网站上发布。我们为这些问题向您致歉,如果您在我站上发现此类问题,请及时联系我们,我们将根据您的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。