0%

Redis源代码阅读:从库读取到过期key的处理

Redis使用定时删除、惰性删除策略来删除过期的key。惰性删除是指在获取key的时候,判断key是否过期,如果过期则删除。不过当redis实例作为从库运行时,遇到过期的key却不会删除。

惰性删除策略

惰性删除是指在获取key的时候,先检查key是否过期:

1
2
3
4
5
6
7
8
9
10
11
12
13
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;

/* 检查key是否过期 */
expireIfNeeded(db,key);

/* 再查询key */
val = lookupKey(db,key);

// ...

return val;
}

查看检查key是否过期的expireIfNeeded()函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int expireIfNeeded(redisDb *db, robj *key) {
// 获取过期时间
mstime_t when = getExpire(db,key);
mstime_t now;

// key没有设置过期时间,返回0
if (when < 0) return 0; /* No expire for this key */

// ......

/**
* 以从库运行时,直接返回是否过期,不再往下执行
*/
if (server.masterhost != NULL) return now > when;

/*
* Return when this key has not expired
* key还没有过期,返回0
*/
if (now <= when) return 0;

// ......

// key已经过期,删除key
return dbDelete(db,key);
}

如果当前实例是主库,会将过期key删除;如果是从库,遇到过期key并不删除,而是等待主库的DEL命令来删除。

从库过期key处理

既然没有删除key,在收到主库的DEL命令之前,能否在从库查询到过期的key?

在Redis 3.2版本之前,从库确实可以查询到已经过期key。

在3.2版本对这个bug进行了修复,修复之后查询过期key返回NULL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
robj *lookupKeyRead(redisDb *db, robj *key) {
robj *val;

if (expireIfNeeded(db,key) == 1) { // key已经过期

// 主库直接返回NULL,key已经在expireIfNeeded()中删除了
if (server.masterhost == NULL) return NULL;

// 从库也返回NULL
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);

// ...
return val;
}

总结

  • 主库查到过期的key时,会将key删除(惰性删除策略)。
  • 从库查到过期的key时,不会主动删除已过期的key,而是等待主库的DEL命令来删除。
  • 在Redis 3.2之前,可能在从库查询到已过期的key。
  • 在Redis 3.2及之后,从库查询到过期key时,返回NULL。

思考

为什么从库遇到过期的key不删除呢?如果删除了会有什么问题?

在Redis源代码的lookupKeyRead()函数中的注释:

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.

注释中提到是为了保证数据的一致性。按照redis主从复制的设计,主库会将自己执行的写命令发送给从库,从库接收主库的写命令,这样就可以保证主从库的一致性。如果在从库执行了写操作,那么这个写操作不会同步到主库,就会造成数据不一致。

因此,从库是只读的,除非是来自主库的写命令。

那对于key已经过期这种场景来说,反正迟早都是要删除的,为啥从库不立即删除呢?

有一种比较极端的情况:由于从库的时钟快了,导致从库认为key已经过期了,而主库认为key还没过期。如果从库的时钟恢复正常了,再去从库查询这个key应该是可以查询到的。所以从库判断key已经过期时,不能主动删除key。