Skip to content

Redis 过期键删除策略:为什么你的内存打满了?

提出问题

设了 TTL(过期时间)的 key,到期后就自动消失了?没那么简单。很多开发遇到过:明明设置了 1 小时过期,Redis 内存却一直涨、甚至 OOM。这里的核心问题是:Redis 到底什么时候删除过期 key?如果删除不及时,会不会把内存打满?

分析问题

Redis 的过期删除策略是 惰性删除 + 定期删除 双管齐下,不是定时器逐个扫描。

惰性删除:读的时候顺便删

每次客户端访问一个 key 时,Redis 会先检查它的 TTL。如果已过期,直接删除并返回空。这个机制保证了你不会读到过期数据,但它的缺陷也很明显——如果没人访问,过期 key 就一直占着内存。

c
// 伪代码:Redis 惰性删除逻辑
int expireIfNeeded(redisDb *db, robj *key) {
    if (!keyIsExpired(db, key)) return 0;
    // 删除过期 key
    dbDelete(db, key);
    return 1;
}

定期删除:后台随机采样

Redis 每 100ms 运行一次 activeExpireCycle,从设置了过期时间的 dict 中随机采样 20 个 key,删除其中过期的。如果过期比例超过 25%,就继续采样,单次最多跑 25ms。

c
// 简化版定期删除流程
void activeExpireCycle() {
    do {
        sampled = 0;
        expired = 0;
        // 随机采样 20 个带过期时间的 key
        while (sampled < 20) {
            key = randomKeyWithTTL();
            if (expired(key)) {
                deleteKey(key);
                expired++;
            }
            sampled++;
        }
    } while (expired > sampled / 4); // 过期比例 > 25% 继续
}

这俩配合起来,正常情况过期 key 不会长期占用内存。但有个边界场景会出问题。

大量 key 同时过期的灾难

假设你给 100 万个 key 设了完全相同的过期时间。到期那一刻,惰性删除只删被访问的 key,定期删除在 25ms 内疯狂采样删除,但 100 万个 key 根本删不完。结果就是:未过期的新 key 被内存淘汰策略踢掉,而已过期未删除的 key 还在占着内存不释放。这就是缓存雪崩的变种。

python
# ❌ 错误做法:所有 key 同一时间过期
for user_id in range(1000000):
    redis.set(f"user:{user_id}", data, ex=3600)  # 全在 1 小时后过期

# ✅ 正确做法:加随机偏移
import random
for user_id in range(1000000):
    ttl = 3600 + random.randint(0, 600)  # 3600-4200 秒之间随机
    redis.set(f"user:{user_id}", data, ex=ttl)

排查思路

如果怀疑线上 Redis 内存异常增长,按以下步骤排查:

bash
# 1. 看 keyspace 状态
redis-cli INFO keyspace
# 输出类似:db0:keys=1000000,expires=800000,avg_ttl=123456

# 2. 用 scan 抽样看哪些 key 没有 TTL
redis-cli --scan --pattern "user:*" | head -20

# 3. 看内存使用趋势
redis-cli INFO memory | grep used_memory_human

# 4. 看是否有大量过期 key 未被删除
redis-cli --bigkeys  # 检查大 key 和 key 分布

总结

Redis 的过期删除策略是聪明的工程权衡,但你需要知道它的边界:

  1. 惰性删除 保证不读脏数据,但依赖访问才能触发
  2. 定期删除 每 100ms 只跑 25ms,过期 key 太多时删不过来
  3. 过期时间加随机偏移 是防止批量同时过期的最简单有效手段
  4. 生产环境记得配 maxmemory 和淘汰策略,这是最后一道防线

参考:Redis 源码 src/expire.csrc/evict.c、Redis 官方文档 Key eviction

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。