Redis 过期与淘汰机制全解析

概述

Redis 作为一种高性能的内存数据库,提供了数据过期和淘汰策略以管理存储的数据。本文将详细探讨 Redis 中数据失效的基本原理、实现方式,并结合源码进行分析,帮助读者更深入地理解和优化 Redis 的使用。

数据过期机制

过期键的存储方式

Redis 中每个数据库都有一个 expires 字典,用于保存设置了过期时间的键及其对应的过期时间戳。这个字典的作用类似于索引,通过它能够快速判断某个键是否已过期。

typedef struct redisDb {
    dict *dict;                 // 存储键值对
    dict *expires;             // 存储键的过期时间
    // 其他成员...
} redisDb;

定时删除 vs 惰性删除 vs 定期删除

定时删除(Active Expiration / Timer-based Deletion)

定时删除是一种“主动式”删除机制。当用户为一个键设置过期时间时,Redis 会记录该键的过期时间戳,并注册一个定时器,在指定时间到达后自动触发删除操作。虽然这种方法能及时释放内存资源,但可能会导致 CPU 使用率飙升。

// 设置键的过期时间
void setExpire(client *c, robj *key, long long when) {
    dictEntry *de;

    de = dictFind(c->db->dict,key->ptr);
    serverAssertWithInfo(c,key,de != NULL);
    dictSetVal(c->db->expires,dictAddRaw(c->db->expires,key->ptr,&de),when);
}
惰性删除(Lazy Expiration / On-access Deletion)

惰性删除是一种“被动式”机制。只有在访问某个键时才会检查其是否已经过期,如果已过期则立即删除。这种方式可以显著降低 CPU 的消耗,但代价是部分过期键会长时间保留在内存中。

int expireIfNeeded(redisDb *db, robj *key) {
    if (!keyIsInHashtable(db->expires, key)) return 0;

    mstime_t when = getExpire(db, key);
    mstime_t now = mstime();

    if (now > when) {
        deleteExpiredKey(db, key);
        return 1;
    }
    return 0;
}
定期删除(Periodic Expiration)

定期删除是 Redis 主动清理过期键的主要方式之一。Redis 在后台每隔一段时间扫描部分设置了过期时间的键,随机选取一部分进行过期检测,并删除其中已经过期的键。

void activeExpireCycle(int type) {
    static unsigned int current_db = 0;
    unsigned int j, iteration = 0;
    unsigned int dbs_per_call = CRON_DBS_PER_CALL;
    int start = ustime(), timelimit, noexpire = 0;

    if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;

    timelimit = 1000000 * ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC / 100;
    timelimit -= (ustime() - start);

    for (j = 0; j < dbs_per_call && timelimit > 0 && !noexpire; j++) {
        redisDb *db = server.db + (current_db % server.dbnum);
        current_db++;

        int num = dictSize(db->expires);
        int slots = ceil(num / 16.0);  // 随机选择 slot 数量

        while (slots--) {
            dictEntry *de;
            long long ttl;

            if ((de = dictGetRandomKey(db->expires)) == NULL) break;

            robj *key = dictGetKey(de);
            ttl = dictGetSignedIntegerVal(de) - mstime();

            if (ttl < 0) {
                dbDelete(db, key);
            }
        }

        if ((iteration++ % 10) == 0) {
            long long elapsed = ustime() - start;
            if (elapsed >= timelimit) break;
        }
    }
}

内存淘汰机制

当 Redis 使用的内存超过 maxmemory 限制时,将会根据配置的淘汰策略来决定移除哪些键值对。常见的淘汰策略包括:

  • volatile-lru:从设置了过期时间的数据集中挑选最近最少使用的数据淘汰。
  • allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰。
  • volatile-random:从设置了过期时间的数据集中任意选择数据淘汰。
  • allkeys-random:从所有数据集中任意选择数据淘汰。

UML图解 Redis 过期键处理流程

以下是使用 UML 绘制的简化版 Redis 过期键处理流程图,包括定时删除、惰性删除和定期删除的工作流程:

定时删除:

客户端设置键并指定TTL
是否已存在?
更新过期时间
添加到 expires 字典
记录过期时间戳
创建时间事件

惰性删除:

客户端访问键
调用 expireIfNeeded 方法
是否存在于 expires 字典中?
当前时间 > 过期时间?
删除键-惰性删除
正常处理请求

定期删除:

serverCron 周期调用
调用 activeExpireCycle 方法
遍历数据库
随机取样部分键
是否过期?
删除键-定期删除
保留键

结语

Redis 通过一系列复杂的机制来管理数据的生命周期和内存使用情况,旨在提供高效的数据管理和查询服务。理解这些基本概念和机制对于优化性能、避免内存溢出等问题至关重要。希望这篇文章能为你提供有价值的参考信息,帮助你更好地利用 Redis 提供的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值