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
    clipboard.png
    clipboard.png
  • 我们可以看到进入到getLongLongFromObjectOrReply后做的动作就是 取出param中的整数值或者尝试将param中的数据尽可能转换成整数值存在when,打印when的值为我们设置的50000.
    clipboard.png
    clipboard.png
  • 判断是否为秒,是则转化为毫秒,接着加上basetime,结果为,unix时间转化为2019-09-23 05:30:15,(大约13小时之后)符合预期,下一步
    clipboard.png
  • 接下来是查找是否有key,有则进行下一步
  • 因为我们是正常进行过期设置,所以应该走的是else语句进行设置,设置就是从redisDb中找到我们的key,然后更新expire时间,具体实现看上方代码分析:
    clipboard.png
  • 接下来就是发信号通知等给客户端。
    clipboard.png
  • 我们继续c执行命令,从客户端收到执行成功
    clipboard.png

比较

redis此类命令有三种:EXPIREAT,PEXPIRE,PEXPIREAT
EXPIREAT 的作用和 EXPIRE 类似,都用于为 key 设置生存时间。
不同在于 EXPIREAT 命令接受的时间参数是 UNIX 时间戳(unix timestamp)。
而PEXPIRE,PEXPIREAT和EXPIRE和EXPIREAT的区别在于PEXPIRE,PEXPIREAT是以毫秒为单位,而后者用的是以秒为单位。

业务用处

在这里我简单的列举几点大家仅供参考:

  1. 限时的优惠活动信息
  2. 网站数据缓存(对于一些需要定时更新的数据,例如:积分排行榜)
  3. 手机验证码
  4. 限制网站访客访问频率(例如:1分钟最多访问10次)

NoSay
449 声望544 粉丝