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