一、删除策略
惰性删除
每次获取 key 的时候会在 expire
字典中查询是否有当前key,如果有的话会校验当前key的过期时间,过期则删除,缺点是如果存在键已过期,但长期不使用的情况,实际上数据还是存在内存中的
以下是 get
命令的部分源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// src/t_string.c line 78
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
long long milliseconds = 0; /* initialized to avoid any harmness warning */
int found = 0;
int setkey_flags = 0;
if (expire && getExpireMillisecondsOrReply(c, expire, flags, unit, &milliseconds) != C_OK) {
return;
}
if (flags & OBJ_SET_GET) {
if (getGenericCommand(c) == C_ERR) return;
}
found = (lookupKeyWrite(c->db,key) != NULL); // 重点,开始查找当前key
// ... 省略部分源码
}
|
顺着 lookupKeyWrite
找下去
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
// src/db.c line 158
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE); // 继续跳转
}
// src/db.c line 154
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
return lookupKey(db, key, flags | LOOKUP_WRITE); // 继续跳转
}
// src/db.c line 81
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr);
robj *val = NULL;
if (de) {
val = dictGetVal(de);
/* Forcing deletion of expired keys on a replica makes the replica
* inconsistent with the master. We forbid it on readonly replicas, but
* we have to allow it on writable replicas to make write commands
* behave consistently.
*
* It's possible that the WRITE flag is set even during a readonly
* command, since the command may trigger events that cause modules to
* perform additional writes. */
int is_ro_replica = server.masterhost && server.repl_slave_ro;
int force_delete_expired = flags & LOOKUP_WRITE && !is_ro_replica;
// 这里开始判断是否过期
if (expireIfNeeded(db, key, force_delete_expired)) {
/* The key is no longer valid. */
val = NULL;
}
}
}
// src/db.c line 1666
// 判断逻辑
int expireIfNeeded(redisDb *db, robj *key, int force_delete_expired) {
if (!keyIsExpired(db,key)) return 0;
if (server.masterhost != NULL) {
if (server.current_client == server.master) return 0;
if (!force_delete_expired) return 1;
}
if (checkClientPauseTimeoutAndReturnIfPaused()) return 1;
/* Delete the key */
deleteExpiredKeyAndPropagate(db,key);
return 1;
}
|
定期删除
每隔一段时间检查一次数据库,随机删除一些过期键,需要注意的是:Redis 每次扫描并不是遍历过期字典中的所有键,而是采用随机抽取判断并删除过期键的形式执行的
Redis 默认每秒进行10次过期扫描,此配置可以通过 redis.conf
的 hz
配置项进行配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// src/server.c line 971
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 (iAmMaster()) {
activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW); // Master 节点开始处理过期键
} else {
expireSlaveKeys();
}
}
// 省略部分源码...
}
|
Redis使用使用的是惰性删除
加定期删除
的策略
二、淘汰策略
只有在 Redis 的运行内存达到了某个阀值,才会触发内存淘汰机制,这个阀值就是我们设置的最大运行内存,此值在 Redis 的配置文件中可以找到,配置项为 maxmemory。
查看最大运行内存
1
2
3
|
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "0"
|
Redis在32位系统下默认阈值为3G(最大运行内存为4G,为保证系统正常运行,预留1G资源),64位系统下默认阈值为0,表示没有大小限制
查看内存淘汰策略
1
2
3
|
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
|
noeviction
表示当运行内存超过最大设置内存时,不淘汰任何数据,但新增数据操作会报错
内存淘汰策略分类
1. noeviction: 不淘汰任何数据,当运行内存不足时,新增数据会直接报错
2. allkeys-lru: 淘汰所有键中最长时间未使用的
3. allkeys-random: 随机淘汰任意键
4. allkeys-lfu: 淘汰所有键中最少使用的;
5. volatile-lru: 淘汰所有设置了过期时间的键中最长时间未使用的
6. volatile-random: 随机淘汰设置了过期时间的键
7. volatile-ttl: 优先淘汰更快过期的键
8. volatile-lfu: 优先淘汰设置了过期时间的键中最少使用的
LRU算法
LRU 算法全称是Least Recently Used
译为最近最少使用
LRU 算法基于链表结构,链表中的元素按照操作顺序从前往后排列,最新操作的会被移动到表头,当需要内存淘汰时,只需要删除链表尾部的元素即可
Redis使用的是一种近似 LRU 算法,目的是为了更好的节约内存,它的实现方式是给现有的数据结构添加一个额外的字段,用于记录此键的最后一次访问时间,Redis内存淘汰时,会使用随机采样的方式淘汰数据,他是随机取N
个值(可配置),然后淘汰醉酒没有使用的那个
LFU算法
LFU 全称是 Least Frequently Used
翻译为最不常用,最不常用的算法是根据总访问次数来淘汰数据的,它的核心思想是”如果数据过去被访问的次数很多,那么将来被访问的频率也会很高“。
LFU 解决了偶尔访问一次之后,数据就不会被淘汰的问题,相比于 LRU 算法也更合理一些