Redis学习--Redis对过期键的处理

Redis做RDB备份时对已过期键的处理

【待验证】执行SAVE和BGSAVE所产生的RDB文件不会包含"已过期键"。

Redis做RDB加载时对已过期键的处理

在Redis实例加载RDB时,会先加载"已过期键",如果当前实例为主节点则会通过decrRefCount操作来删除"已过期键"。

* an RDB file from disk, either at startup, or when an RDB was
* received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the slave. */
if (server.masterhost == NULL && !loading_aof && expiretime != -1 && expiretime < now) {
	decrRefCount(key);
	decrRefCount(val);
	continue;
}

Redis做AOF重写时对已过期键的处理

执行AOF重写时生成AOF文件不会包含已过期键。

# src\aof.c
int rewriteAppendOnlyFileRio(rio *aof) {
  	for (j = 0; j < server.dbnum; j++){
  		/* Iterate this DB writing every entry */
  		while((de = dictNext(di)) != NULL){
        	keystr = dictGetKey(de);
            expiretime = getExpire(db,&key);
            /* If this key is already expired skip it */
            if (expiretime != -1 && expiretime < now) continue;
            ....
    	}
  	}
}

Redis做AOF加载时对已过期键的处理

【未查到】猜测加载AOF时不会判断KEY是否已过期

Redis做复制初始化对已过期键的处理

【待验证】复制同步请求触发的RDB备份不包含"已过期键"

Redis做复制同步时对已过期键的处理

当Redis主节点删除Key或清理过期Key时,会产生一条DEL命令并追加到AOF日志和传递给Redis从节点,Redis从节点接收到"DEL命令"并执行删除对于Key。

应用请求Redis主实例时对已过期键的处理

在Redis每次对Key执行操作时,都是调用expireIfNeeded来判断是否已过期:

/* This function is called when we are going to perform some operation
 * in a given key, but such key may be already logically expired even if
 * it still exists in the database. The main way this function is called
 * is via lookupKey*() family of functions.
 *
 * The behavior of the function depends on the replication role of the
 * instance, because slave instances do not expire keys, they wait
 * for DELs from the master for consistency matters. However even
 * slaves will try to have a coherent return value for the function,
 * so that read commands executed in the slave side will be able to
 * behave like if the key is expired even if still present (because the
 * master has yet to propagate the DEL).
 *
 * In masters as a side effect of finding a key which is expired, such
 * key will be evicted from the database. Also this may trigger the
 * propagation of a DEL/UNLINK command in AOF / replication stream.
 *
 * The return value of the function is 0 if the key is still valid,
 * otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db,key);
    mstime_t now;

    if (when < 0) return 0; /* No expire for this key */

    /* Don't expire anything while loading. It will be done later. */
    if (server.loading) return 0;

    /* If we are in the context of a Lua script, we pretend that time is
     * blocked to when the Lua script started. This way a key can expire
     * only the first time it is accessed and not in the middle of the
     * script execution, making propagation to slaves / AOF consistent.
     * See issue #1525 on Github for more information. */
    now = server.lua_caller ? server.lua_time_start : mstime();

    /* If we are running in the context of a slave, return ASAP:
     * the slave key expiration is controlled by the master that will
     * send us synthesized DEL operations for expired keys.
     *
     * Still we try to return the right information to the caller,
     * that is, 0 if we think the key should be still valid, 1 if
     * we think the key is expired at this time. */
    /* 如果是Redis从节点,直接返回是否过期,不执行后面删除操作 */
    if (server.masterhost != NULL) return now > when;

    /* Return when this key has not expired */
    if (now <= when) return 0;

    /* Delete the key */
    server.stat_expiredkeys++;
    propagateExpire(db,key,server.lazyfree_lazy_expire);
    notifyKeyspaceEvent(NOTIFY_EXPIRED,
        "expired",key,db->id);
    /* 如果是Redis主节点且Key已经过期,则触发删除操作 */
    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
                                         dbSyncDelete(db,key);
}

应用请求Redis从实例时对已过期键的处理

在Redis从节点上每次对Key执行操作时,即使Key已经过期也不会触发删除,但会返回NULL值。
PS: 在Redis 3.2之前版本,请求Redis从节点上已过期KEY时,会返回已过期KEY对于的VALUE。

/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
 * common case. */
robj *lookupKeyRead(redisDb *db, robj *key) {
    return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);
}


/* Lookup a key for read operations, or return NULL if the key is not found
 * in the specified DB.
 *
 * As a side effect of calling this function:
 * 1. A key gets expired if it reached it's TTL.
 * 2. The key last access time is updated.
 * 3. The global keys hits/misses stats are updated (reported in INFO).
 *
 * This API should not be used when we write to the key after obtaining
 * the object linked to the key, but only for read only operations.
 *
 * Flags change the behavior of this command:
 *
 *  LOOKUP_NONE (or zero): no special flags are passed.
 *  LOOKUP_NOTOUCH: don't alter the last access time of the key.
 *
 * Note: this function also returns NULL is the key is logically expired
 * but still existing, in case this is a slave, since this API is called only
 * for read operations. Even if the key expiry is master-driven, we can
 * correctly report a key is expired on slaves even if the master is lagging
 * expiring our key via DELs in the replication link. */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    robj *val;

    if (expireIfNeeded(db,key) == 1) {
        /* Key expired. If we are in the context of a master, expireIfNeeded()
         * returns 0 only when the key does not exist at all, so it's safe
         * to return NULL ASAP. */
        if (server.masterhost == NULL) return NULL;

        /* However if we are in the context of a slave, expireIfNeeded() will
         * not really try to expire the key, it only returns information
         * about the "logical" status of the key: key expiring is up to the
         * master in order to have a consistent view of master's data set.
         *
         * However, if the command caller is not the master, and as additional
         * safety measure, the command invoked is a read-only command, we can
         * safely return NULL here, and provide a more consistent behavior
         * to clients accessign expired values in a read-only fashion, that
         * will say the key as non exisitng.
         *
         * Notably this covers GETs when slaves are used to scale reads. */
        if (server.current_client &&
            server.current_client != server.master &&
            server.current_client->cmd &&
            server.current_client->cmd->flags & CMD_READONLY)
        {
            return NULL;
        }
    }
    val = lookupKey(db,key,flags);
    if (val == NULL)
        server.stat_keyspace_misses++;
    else
        server.stat_keyspace_hits++;
    return val;
}

Redis从节点不会主动删除Key,除非接收到Redis主节点同步过来的"DEL命令"。

其他

在主节点上设置过期时间,无论使用EXPIRE、EXPIREAT、PEXPIRE、PEXPIREAT还是使用SET EX,都会将过期操作转换为PEXPIREAT命令并记录到AOF日志和发送给从节点。
如在主节点上执行:

set key01 val01 ex 3

生成的AOF日志为:

*3 $3 set $5 key01 $5 val01
*3 $9 PEXPIREAT $5 key01 $13 1686135364304

当key01经过3秒过期后,生成AOF日志为:

*2 $6 UNLINK $5 key01

如果显式执行DEL命令,则会生成AOF日志为:

*2 $3 del $5 key02 

如果显式执行UNLINK命令,则会生成AOF日志为:

*2 $6 unlink $5 key03
posted @ 2023-06-06 16:17  TeyGao  阅读(84)  评论(0编辑  收藏  举报