redis系列,键过期的知识你要知道的全在这里(一)



前言

本文主要全面分析redis如何来设置过期key,跟过期相关的操作,哪些我们认为可能会有影响但实际不会产生影响的操作,以及过期键的处理策略。本文会从源码层面聊到一些其他文章不曾有的部分


废话不多说,下面正式进入主题

一、键过期的相关命令

1.对过期有影响的操作

有键覆盖操作的命令如:DEL,SET,GETSE,RENAME, 这里重点说一下set和rename操作,
set操作可以指定过期时间,无论以前键是否带有过期时间,都以新的时间为准,如果不设置那会从expire 字典里面移除掉,即永不过期。
rename 效果上差不多无论,被覆盖的旧key 是何种属性,都已新的为准
如图:

在这里插入图片描述
在这里插入图片描述

2.对过期无影响的操作

对于那些仅仅对value 进行修改的值本质上不对key的属性做修改的,一些命令是不会对ttl 有任何影响的。如incr ,LPUSH,RPUSH,HSET … 等等
在这里插入图片描述

3.过期命令,和拯救正在过期键的命令

过期命令我们常用到的就是expirepexpire, 两个命令的区别一个是秒为单位,另位一个是已毫秒为单位
expire 的参数是一个整数代表键会在几秒后会过期。可通过ttl去查看他的过期时间
如果传递的是一个负数会立刻将键值删除
在这里插入图片描述
在这里插入图片描述

persist 是除了set rename 这些覆盖操作另外一个可以拯救正在过期key的命令,其余两个会跟具体的值挂钩。
但是要注意persist 已经过期的键的时候会立即触发删除,也就是说persist 并不能拯救已经删除的key
另外persist 虽然可以在子实例执行成功,即使是一个过期的键,但子实例仍会被主实例相关的同步删除给删除掉。

4.过期命令相关源码分析

redis.h

/* Redis database representation. There are multiple databases identified
 * by integers from 0 (the default database) up to the max configured
 * database. The database number is the 'id' field in the structure. */
typedef struct redisDb {
    dict *dict;                 /* The keyspace for this DB */
    // 键的过期时间,字典的键为键,字典的值为过期事件 UNIX 时间戳
    dict *expires;              /* Timeout of keys with a timeout set */
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS */
    int id;                     /* Database ID */
    long long avg_ttl;          /* Average TTL, just for stats */
    unsigned long expires_cursor; /* Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

首先注意到的所有过期键是放在一个map里面,这里面跟lru不同的是,lru会通过字段存在键对象里面,为什么了个人觉得有以下两个观点。

  1. 从目的分析下这两者的不同,过期集合的设置是为了再内存未满之前腾出更多的空间,而lru是内存满了之后所要执行的策略。
  2. 接着上面的目的,设置集合我们采取删除策略的时候才会更有效率,当然这里需要结合后面讲的过期删除策略有关系

expire.c

/* EXPIRE key seconds */
/**
 * 参数为秒的时候要获取系统的现在时间
 * mstime()这个方法
 * @param c
 */
void expireCommand(client *c) {
    expireGenericCommand(c,mstime(),UNIT_SECONDS);
}
void expireGenericCommand(client *c, long long basetime, int unit) {
    robj *key = c->argv[1], *param = c->argv[2];
    //需要设置的过期时间,时间戳
    long long when; /* unix time in milliseconds when the key will expire. */
    // 取出 when 参数 主要是验证传入的参数是否合法,比如是否传入是字符
    if (getLongLongFromObjectOrReply(c, param, &when, NULL) != C_OK)
        return;
    //如果是秒为单位,则when 需要*1000
    if (unit == UNIT_SECONDS) when *= 1000;
    when += basetime;

    /* No key, return zero. */
    // 寻找当前的键是否存在key 空间里面
    if (lookupKeyWrite(c->db,key) == NULL) {
        addReply(c,shared.czero);
        return;
    }
    //检查设置的时间是否已经过期
    if (checkAlreadyExpired(when)) {
        robj *aux;
        //过期的话就删除key,这里有判断是否开启异步删除
        int deleted = server.lazyfree_lazy_expire ? dbAsyncDelete(c->db,key) :
                                                    dbSyncDelete(c->db,key);
        serverAssertWithInfo(c,key,deleted);
        //键的改动都会加1
        server.dirty++;

        /* Replicate/AOF this as an explicit DEL or UNLINK. */
        aux = server.lazyfree_lazy_expire ? shared.unlink : shared.del;
        //修改客户端参数,但是这里的客户端指的是其内部处理,会和同步产生关系。
        //跟写入aof的日志挂钩
        rewriteClientCommandVector(c,2,aux,key);
        //每次键改动都会调用该方法,旧版本主要为了通知被watched 的键,而新版本还会去追踪错误的信息
        signalModifiedKey(c,c->db,key);
        //这一部分的代码主要跟内存回收有关的事件通知。
        notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
        addReply(c, shared.cone);
        return;
    } else {
        //如果时间合法未过期,则设置到expire 字典里面去, key 为传入的key, when 为计算好的时间戳
        setExpire(c,c->db,key,when);
        //设置返回
        addReply(c,shared.cone);
        //更上面注释一样,通知到钩子
        signalModifiedKey(c,c->db,key);
        //这一部分的代码主要跟内存回收有关的事件通知。
        notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
        server.dirty++;
        return;
    }
}

大体的流程为 判断参数是否合法->判断键是否存在键值空间->判断传入时间是否超时->超时改为删除操作,并对操作记录进行更改,然后一些通知事件->不超时,将键值设置到过期字典里面去。
由于以上注释已经非常详细,可以多看上面的注释

下一篇文章将展开过期策略是如何运行的,敬请留意

相关参考:
redis 6.0.7 的源码 https://github.com/redis/redis
redis 3.0 带注释的的源码 https://github.com/huangz1990/redis-3.0-annotated

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

偷懒的程序员-小彭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值