欢迎来到科站长!

Redis

当前位置: 主页 > 数据库 > Redis

Redis过期时间的设计与实现代码

时间:2024-09-22 15:05:00|栏目:Redis|点击:

1. 设置过期时间

Redis 提供了多个命令来设置键的过期时间,如 EXPIREPEXPIREEXPIREAT 和 PEXPIREAT。这些命令可以以秒或毫秒为单位设置键的过期时间,也可以设置具体的过期时间点。

  • EXPIRE key seconds
  • PEXPIRE key milliseconds
  • EXPIREAT key timestamp
  • PEXPIREAT key milliseconds-timestamp

示例:

1
2
3
4
5
6
7
void expireCommand(client *c) {
    long long seconds;
    if (getLongLongFromObjectOrReply(c, c->argv[2], &seconds, NULL) != C_OK)
        return;
    setExpire(c, c->db, c->argv[1], mstime() + seconds*1000);
    addReply(c, shared.cone);
}

2. 过期键的存储结构

每个 Redis 数据库实例(redisDb)中都有一个名为 expires 的字典,用于存储键的过期时间。这个字典将键指针映射到以毫秒为单位的到期时间点。

1
2
3
4
5
typedef struct redisDb {
    dict *dict;                // 主字典,存储所有键值对
    dict *expires;             // 过期字典,存储键的过期时间
    ...
} redisDb;

3. 设置过期时间

通过 setExpire 函数设置键的过期时间。如果键已经存在于 expires 字典中,则更新其过期时间;否则,将其添加到 expires 字典中。

1
2
3
4
5
6
7
8
9
void setExpire(client *c, redisDb *db, robj *key, long long when) {
    dictEntry *de = dictFind(db->dict, key->ptr);
    if (de == NULL) return;
 
    /* Set the new expire time */
    if (dictAdd(db->expires, dictGetKey(de), (void*)when) == DICT_ERR) {
        dictReplace(db->expires, dictGetKey(de), (void*)when);
    }
}

4. 删除过期键的策略

Redis 采用了以下三种策略来删除过期键:

  • 惰性删除(Lazy Deletion) :每次访问键时检查其是否过期,如果已过期则删除。这样只在访问键时才进行过期检查,节省了资源。
1
2
3
4
5
6
robj *lookupKeyRead(redisDb *db, robj *key) {
    robj *val;
    expireIfNeeded(db,key);  // 检查并删除过期键
    val = lookupKey(db,key,LOOKUP_NONE);
    return val ? val : NULL;
}
  • 定期删除(Periodic Deletion) :Redis 会周期性地随机抽取一定数量的键进行过期检查,并删除其中已过期的键。这一过程由后台任务定期执行,确保尽可能多的过期键被及时删除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int activeExpireCycle(int type) {
    unsigned int current_db = server.dbnum;
    long long start = ustime();
    long long timelimit = 1000000; // 1秒
    int dbs_per_call = CRON_DBS_PER_CALL;
 
    current_db = server.current_db;
    while(dbs_per_call--) {
        redisDb *db = server.db + (current_db % server.dbnum);
        activeExpireCycleTryExpire(db, cycle_tickets);
        current_db++;
    }
 
    long long elapsed = ustime()-start;
    return elapsed > timelimit;
}
  • 主动删除(Active Expiration) :在内存使用接近最大限制时,会触发主动删除策略,通过扫描所有库的键删除过期数据,以确保内存使用量保持在设定范围内。
1
2
3
4
5
6
void evictExpiredKeys() {
    for (int j = 0; j < server.dbnum; j++) {
        redisDb *db = server.db+j;
        scanDatabaseForExpiredKeys(db);
    }
}
  • Redis 默认采用以下两种删除过期键策略:

    惰性删除(Lazy Deletion) :每次访问某个键时检查其是否过期,如果过期则删除。

    定期删除(Periodic Deletion) :后台任务定期扫描数据库中的键,随机抽取部分键进行过期检查并删除其中已过期的键。

5. 检查并删除过期键

expireIfNeeded 函数用于检查某个键是否过期,如果过期则删除该键。

1
2
3
4
5
6
7
8
9
10
11
12
13
int expireIfNeeded(redisDb *db, robj *key) {
    mstime_t when = getExpire(db, key);
    if (when < 0) return 0;
 
    if (mstime() > when) {
        server.stat_expiredkeys++;
        propagateExpire(db,key);
        dbDelete(db,key);
        return 1;
    } else {
        return 0;
    }
}
  • getExpire:从 expires 字典中获取键的过期时间。
  • mstime:返回当前的毫秒时间戳。
  • 如果键已过期,则调用 dbDelete 删除该键,并增加统计计数器 stat_expiredkeys

6. 获取过期时间

getExpire 函数用于获取键的过期时间,如果键没有设置过期时间则返回 -1。

1
2
3
4
5
6
7
mstime_t getExpire(redisDb *db, robj *key) {
    dictEntry *de;
    if (dictSize(db->expires) == 0 ||
        (de = dictFind(db->expires, key->ptr)) == NULL) return -1;
 
    return (mstime_t)dictGetSignedIntegerVal(de);
}

总结

Redis 的过期时间设计与实现包括以下几个关键点:

  • 设置过期时间:通过 EXPIRE、PEXPIRE 等命令设置键的过期时间,并将过期时间存储在 expires 字典中。

  • 过期字典:每个数据库实例都有一个 expires 字典,用于存储键的过期时间。

  • 删除策略

    • 惰性删除:每次访问键时检查其是否过期,如果已过期则删除。
    • 定期删除:通过后台任务周期性地检测并删除过期键。
    • 主动删除:在内存使用接近最大限制时触发,扫描所有键并删除过期键。

定期删除activeExpireCycle函数详细解析

void activeExpireCycle(int type) {
    static unsigned int current_db = 0;  // 记录上一次处理的数据库索引
    static int timelimit_exit = 0;       // 用于指示是否超出时间限制
    unsigned int j;
    // 每次要处理的数据库数量
    unsigned int dbs_per_call = CRON_DBS_PER_CALL;
    long long start = ustime();          // 开始时间
    long long timelimit;                 // 时间限制

    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
        /* Fast cycle: 1 ms */
        timelimit = 1000;
    } else {
        /* Slow cycle: 25% CPU time p. DB / Configurable percentage. */
        timelimit = server.hz < 100 ? 1000 : 10;
        if (server.active_expire_effort != 1)
            timelimit *= server.active_expire_effort-1;
        timelimit /= server.dbnum;
        timelimit_exit = 0;
    }

    for (j = 0; j < dbs_per_call; j++) {
        redisDb *db = server.db + (current_db % server.dbnum);
        current_db++;
        int expired, sampled;

        do {
            long now = mstime();
            expireEntry *de;
            dictEntry *d;

            /* Sample a few keys in the database */
            expired = 0;
            sampled = 0;
            while ((de = dictGetRandomKey(db->expires)) != NULL &&
                   mstime() - now < timelimit) {
                long long ttl = dictGetSignedIntegerVal(de) - mstime();
                if (ttl < 0) {
                    d = dictFind(db->dict, dictGetKey(de));
                    dbDelete(db, dictGetKey(d));
                    server.stat_expiredkeys++;
                    expired++;
                }
                sampled++;
            }
        } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP / 2);

        elapsed = ustime() - start;
        if (elapsed > timelimit) {
            timelimit_exit = 1;
            break;
        }
    }
}


上一篇:Redis压缩列表的设计与实现

栏    目:Redis

下一篇:详解如何在Windows上配置和使用Redis持久化功能

本文标题:Redis过期时间的设计与实现代码

本文地址:https://www.fushidao.cc/shujuku/781.html

广告投放 | 联系我们 | 版权申明

申明:本站所有的文章、图片、评论等,均由网友发表或上传并维护或收集自网络,属个人行为,与本站立场无关。

如果侵犯了您的权利,请与我们联系,我们将在24小时内进行处理、任何非本站因素导致的法律后果,本站均不负任何责任。

联系QQ:1205677645 | 邮箱:1205677645@qq.com

Copyright © 2018-2024 科站长 版权所有冀ICP备14023439号