Redis 的过期删除策略以及淘汰策略

redis的删除策略

一、删除策略

惰性删除

每次获取 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.confhz配置项进行配置

 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 算法也更合理一些

Licensed under CC BY-NC-SA 4.0
皖ICP备20014602号
Built with Hugo
Theme Stack designed by Jimmy