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 一个元素的。