摸清 Redis 的数据清理策略,给内存使用高的被动缓存场景,在遇到内存不足时
本文整理 Redis 的数据清理策略所有代码来自 Redis version : 5.x, 不同版本的 Redis 策略可能有调整
Redis 的清理策略,总结概括为三点,被动清理、定时清理、驱逐清理
访问 Key 时,每次都会检查该 Key 是否已过期,如果过期则删除该 Key ,get 、scan 等指令都会触发 Key 的过期检查。
关键代码如下, expireIfNeeded (redisDb *db, robj *key) 函数会触发检查并删除
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) {
server.stat_keyspace_misses++; return NULL;
} if (server.current_client &&
server.current_client != server.master &&
server.current_client->cmd &&
server.current_client->cmd->flags & CMD_READONLY)
{
server.stat_keyspace_misses++; return NULL;
}
} val = lookupKey(db,key,flags); if (val == NULL)
server.stat_keyspace_misses++; else server.stat_keyspace_hits++; return val;
}
通过 serverCron 定期触发清理,可以通过 hz 参数,配置每秒执行多少次清理任务,流程如下
关键代码如下,activeExpireCycle (int type) 会执行上述逻辑
int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
...
databasesCron();
...
} void databasesCron(void) { /* Expire keys by random sampling. Not required for slaves
* as master will synthesize DELs for us. */ if (server.active_expire_enabled) { if (server.masterhost == NULL) {
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
} else {
expireSlaveKeys();
}
}
...
}
Redis 在命令处理函数 processCommand 会进行内存的检查和驱逐,任何命令都会出触发,包括 ping 命令。
maxmemory_policy 可选如下:
如上图,6.2 后的版本支持通过逐出因子 maxmemory-eviction-tenacity 来控制逐出阻塞的时间。具体的阻塞耗时间可以通过?latency-monitor?里的?eviction-cycle、eviction-del?来观测。
关键代码如下,freeMemoryIfNeeded () 函数会执行上述逻辑
int processCommand(client *c) {
... if (server.maxmemory && !server.lua_timedout) { int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
...
}
...
} int freeMemoryIfNeededAndSafe(void) { if (server.lua_timedout || server.loading) return C_OK; return freeMemoryIfNeeded();
}
回到开篇的背景问题,当遇到内存使用高的被动缓存场景,可用内存不足时: