这是《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. 定期删除
后台周期抽样过期字典:
- 每轮从设置了 TTL 的 key 中抽样;
- 删除其中已过期 key;
- 如果过期比例超过阈值,继续本轮扫描;
- 控制单轮时间,避免阻塞。
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:
- 写命令触发淘汰;
- 具体行为由
maxmemory-policy决定; - 可能返回
OOM command not allowed。
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-log-factor:计数增长速度;lfu-decay-time:访问频率衰减周期,单位分钟。
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 同一时刻到期:
- 定期删除任务压力升高;
- 缓存同时失效引发数据库雪崩;
- 主从复制和 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 告警
[ ] 混合业务评估拆实例
[ ] 复制缓冲和客户端缓冲纳入容量评估
本章小结
- Redis 使用惰性删除 + 定期删除处理过期 key;
- 从节点主要跟随主节点删除,避免双方决策不一致;
- maxmemory 满后按 policy 淘汰或拒绝写入;
- 纯缓存常用 allkeys-lru/lfu,状态数据常用 noeviction;
- volatile-* 只淘汰设置了 TTL 的 key;
- 大量集中 TTL 会带来删除压力和缓存雪崩,需要随机化。
思考题
- 为什么 Redis 不用定时器精确删除每个过期 key?
volatile-lru实例中如果所有 key 都没 TTL,内存满会发生什么?- 秒杀库存 Redis 应该使用哪种淘汰策略?为什么?