RedisNotes

第 14 章:过期与内存淘汰

zjc 于 2026-01-14 发布

这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Redis 是内存系统,所有设计最终都会碰到两个问题:key 什么时候过期,内存满了淘汰谁。本章把 TTL、删除策略、八种淘汰策略与生产风险讲透。

14.1 TTL 的存储

设置过期:

EXPIRE key 60
PEXPIRE key 60000
EXPIREAT key 1735000000
SET key value EX 60
PERSIST key

Redis 维护一个过期字典:

主字典:    key -> value
过期字典:  key -> long 过期时间毫秒

到期后,key 进入“逻辑过期”状态,访问时按删除策略处理。

14.2 三种过期删除策略

1. 定时删除

每个 key 创建定时器,到期精确删除。内存友好,但大量 key 同时到期会占用 CPU 和定时器资源。

2. 惰性删除

访问 key 时检查是否过期:

GET key
  -> 已过期 -> 删除并返回 nil
  -> 未过期 -> 返回 value

CPU 友好,但冷 key 过期后仍占内存。

3. 定期删除

后台周期抽样过期字典:

  1. 每轮从设置了 TTL 的 key 中抽样;
  2. 删除其中已过期 key;
  3. 如果过期比例超过阈值,继续本轮扫描;
  4. 控制单轮时间,避免阻塞。

Redis 实际使用:惰性删除 + 定期删除

相关配置:

hz 10
dynamic-hz yes

hz 越高,后台任务越频繁,过期清理更快,但 CPU 消耗越高。

14.3 从节点如何处理过期

副本不会自己删除大多数过期 key,而是等待主节点删除后同步删除命令,从而保持主从数据一致。

因此可能出现:

主节点已删除 -> 从节点短暂仍存在

从节点读取逻辑上已过期的数据时,返回逻辑过期结果;复制同步后会一致。业务若对 TTL 极度敏感,应读主或使用业务时间戳校验。

14.4 maxmemory 与淘汰策略

maxmemory 4gb
maxmemory-policy allkeys-lru

查看:

CONFIG GET maxmemory
CONFIG GET maxmemory-policy
INFO memory

used_memory 超过 maxmemory

14.5 八种淘汰策略

策略 范围 算法 适合
noeviction 不淘汰 内存满拒绝写入 事实数据/锁/队列
allkeys-lru 全部 key 近似 LRU 纯缓存
allkeys-lfu 全部 key 近似 LFU 长期热点缓存
allkeys-random 全部 key 随机 少用
volatile-lru 有 TTL 的 key 近似 LRU 混合实例
volatile-lfu 有 TTL 的 key 近似 LFU 混合实例
volatile-random 有 TTL 的 key 随机 少用
volatile-ttl 有 TTL 的 key 剩余时间短优先 快速释放

某些版本还提供别名或细节差异,具体以官方文档为准。核心选择只有三点:淘汰范围、频率依据、是否允许写入失败。

14.6 近似 LRU/LFU

Redis 不是维护全局双向链表的精确 LRU,那会带来额外指针与锁开销。它采用采样:

maxmemory-samples 5

每次淘汰随机采样若干 key,从中选择最久未使用的。采样数越大越接近精确 LRU,CPU 开销也越高。

LFU 使用概率计数:

lfu-log-factor 10
lfu-decay-time 1

LFU 适合“曾经的旧热点不应永远占据内存”的缓存。

14.7 不同业务的策略选择

纯缓存

maxmemory-policy allkeys-lru

所有 key 都可重建,内存满时淘汰最久未访问数据。

访问频率更稳定的热点

maxmemory-policy allkeys-lfu

适合每日/长期热点明显的商品、话题、配置缓存。

混合业务

如果同一实例既有缓存也有业务状态:

maxmemory-policy volatile-lru

但要注意:没有 TTL 的 key 永远不会被 volatile-* 淘汰。如果全部写满,仍可能 OOM。

更可靠的做法是业务拆实例,不把缓存和事实数据混放。

状态类实例

锁、队列、库存、订单超时任务等数据不能被随机淘汰:

maxmemory-policy noeviction

内存满时写入失败,业务立刻感知并熔断/扩容,比悄悄丢锁或丢任务安全。

必须配套:

14.8 过期 key 集中过期的问题

大量 key 同一时刻到期:

  1. 定期删除任务压力升高;
  2. 缓存同时失效引发数据库雪崩;
  3. 主从复制和 CPU 出现尖刺。

TTL 随机化

long ttl = 300 + ThreadLocalRandom.current().nextInt(60);
redis.opsForValue().set(key, value, Duration.ofSeconds(ttl));

分批预热

活动开始前分批写入热点缓存,而不是零点一次性灌入。

逻辑过期

value 里包含业务过期时间,物理 TTL 可更长,后台异步刷新,避免同步等待重建。

14.9 内存满的观测

INFO memory
INFO stats

关注:

used_memory
maxmemory
maxmemory_policy
mem_fragmentation_ratio
evicted_keys
expired_keys

症状:

指标 说明
evicted_keys 增长 正在淘汰
写命令 OOM noeviction 或无候选 key
expired_keys 波峰 大量过期
碎片率异常 分配器碎片或 COW

14.10 生产检查清单

[ ] maxmemory 显式设置,不超过机器安全水位
[ ] maxmemory-policy 与数据类型匹配
[ ] 缓存 key 尽量设置随机 TTL
[ ] 状态 key 明确保留策略与容量
[ ] 大 key 定期扫描
[ ] evicted_keys / expired_keys / OOM 告警
[ ] 混合业务评估拆实例
[ ] 复制缓冲和客户端缓冲纳入容量评估

本章小结

思考题

  1. 为什么 Redis 不用定时器精确删除每个过期 key?
  2. volatile-lru 实例中如果所有 key 都没 TTL,内存满会发生什么?
  3. 秒杀库存 Redis 应该使用哪种淘汰策略?为什么?