Redis5源码学习浅析redis命令之expire篇

17次阅读

共计 2590 个字符,预计需要花费 7 分钟才能阅读完成。

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;
    }
}

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

正文完
 0