Grape
命令语法
命令含义:为给定 key 设置生存时间,当 key 过期时(生存时间为 0 ),它会被自动删除。
命令格式:
EXPIRE key seconds
命令实战:
redis> EXPIRE cache_page 30000 # 更新过期时间
(integer) 1
返回值:
设置成功返回 1 。
当 key 不存在或者不能为 key 设置生存时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的生存时间),返回 0 。
源码分析:
expire对应的函数是expireCommand:
/* EXPIRE key seconds */
void expireCommand(client *c) {
// 调用通用处理函数
expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
/*这是expire、pexpire、expireat和pexpireat的通用命令实现。因为commad第二个参数可以是相对的,
也可以是绝对的,所以“base time”参数用来表示基本时间是什么(对于命令的at变量,或者相对过期的当前时间)。
单位是单位秒或者单位毫秒,仅用于argv[2]参数。基本时间总是以毫秒为单位指定的。*/
void expireGenericCommand(client *c, long long basetime, int unit) {
robj *key = c->argv[1], *param = c->argv[2];
long long when; /*when被设置为毫秒. */
/* 取出param中的整数值或者尝试将param中的数据尽可能转换成整数值存在when中,
成功返回OK失败则返回ERR*/
if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
return;
/*如果传入的过期时间是以秒为单位的,那么将它转换为毫秒*/
if (unit == UNIT_SECONDS) when *= 1000;
when += basetime;
/* 查询一下该键是否存在,不存在给客户端返回信息 */
if (lookupKeyWrite(c->db,key) == NULL) {
addReply(c,shared.czero);
return;
}
/*
* 在载入AOF数据时,或者服务器为附属节点时,
* 即使 EXPIRE 的 TTL 为负数,或者 EXPIREAT 提供的时间戳已经过期,
* 服务器也不会主动删除这个键,而是等待主节点发来显式的 DEL 命令。
*/
if (when <= mstime() && !server.loading && !server.masterhost) {
//进入这个函数的条件:when 提供的时间已经过期,未载入数据且服务器为主节点(注意主服务器的masterhost==NULL)
robj *aux;
/*删除该键,此处可以看del命令的解析,在del命令解析中有分析redis同步和异步删除的策略决定,此处不再赘述*/
int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
dbSyncDelete(c->db,key);
serverAssertWithInfo(c,key,deleted);
server.dirty++;
/* Replicate/AOF this as an explicit DEL or UNLINK. */
/* 传播 DEL 或者unlink命令到AOF或者从服务器 */
aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
/*修改客户端的参数数组*/
rewriteClientCommandVector(c,2,aux,key);
/* 发送键更改通知 */
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
addReply(c, shared.cone);
return;
} else {
// 设置键的过期时间
// 如果服务器为附属节点,或者服务器正在载入,根据上个if中的条件来推断,至少when提供时间过期为附属节点就会设置
// 这点猜测在redis中从属节点不去主动做删除操作,除非主节点同步del命令
// 那么这个 when 有可能已经过期的
setExpire(c,c->db,key,when);
addReply(c,shared.cone);
signalModifiedKey(c->db,key);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
server.dirty++;
return;
}
}
//设置expire
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de;
/* Reuse the sds from the main dict in the expire dict */
/*首先从dict中查找,这个过程就是从db的dict根据key来查找的过程,首先会判断是否有安全迭代器,如果没有就会 进行rehash,防止哈
*希表混乱。然后进行hash,获取索引值。通过索引我们可以遍历dict的链表来拿到值。这里会有dict上有hash值一样的情况,在这里
*redis是用while循环来比较,知道key相同返回这一项。
*/
kde = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
/* dictAddRaw()的变通,只是多了一个如果有key则返回的一个过程,如果存在则返回为null并返回当前项,反之将key进行hash索引
*index增加到dict中并返回dict。
*/
de = dictAddOrFind(db->expires,dictGetKey(kde));
/* 设置键的过期时间
* 这里是直接使用整数值来保存过期时间,不是用 INT 编码的 String 对象
*/
dictSetSignedIntegerVal(de,when);
int writable_slave = server.masterhost && server.repl_slave_ro == 0;
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
rememberSlaveKeyWithExpire(db,key);
}
我们gdb一个合法的例子来看一看他的流程:
- 首先我们来进入gdb的server和cli
- 我们可以看到进入到getLongLongFromObjectOrReply后做的动作就是 取出param中的整数值或者尝试将param中的数据尽可能转换成整数值存在when,打印when的值为我们设置的50000.
- 判断是否为秒,是则转化为毫秒,接着加上basetime,结果为,unix时间转化为2019-09-23 05:30:15,(大约13小时之后)符合预期,下一步
- 接下来是查找是否有key,有则进行下一步
- 因为我们是正常进行过期设置,所以应该走的是else语句进行设置,设置就是从redisDb中找到我们的key,然后更新expire时间,具体实现看上方代码分析:
- 接下来就是发信号通知等给客户端。
- 我们继续c执行命令,从客户端收到执行成功
比较
redis此类命令有三种:EXPIREAT,PEXPIRE,PEXPIREAT
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。
不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
而PEXPIRE,PEXPIREAT和EXPIRE和EXPIREAT的区别在于PEXPIRE,PEXPIREAT是以毫秒为单位,而后者用的是以秒为单位。
业务用处
在这里我简单的列举几点大家仅供参考:
- 限时的优惠活动信息
- 网站数据缓存(对于一些需要定时更新的数据,例如:积分排行榜)
- 手机验证码
- 限制网站访客访问频率(例如:1分钟最多访问10次)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。