{T}

过期策略

0. 引言

EXPIRE/TTL 是 Redis 最常用的命令之一,但"过期键到底何时被删除"的机制常被误解。Redis 采用惰性删除 + 定期删除的组合策略,兼顾 CPU 与内存。本章解析两种策略的原理、源码级行为(activeExpireCycle)、默认值演进与工程注意点。

1. 为什么会话/缓存会"过期"

bash
> set session:1001 "token" ex 3600
> ttl session:1001
(integer) 3599
> expire order:1001 300
(integer) 1
> pexpireat key <unix-time-ms>     # 指定绝对过期时间
  • EX/PX(SET 选项)与 EXPIRE/PEXPIRE/EXPIREAT 设置相对/绝对过期;
  • 过期信息以毫秒时间戳保存在 key 的元数据中(dict entry 的 expire 字段),不额外占用结构;
  • 7.x 中 SET ... EXSETEX 等价且原子。

2. 策略一:惰性删除(lazy expire)

访问 key 时才检查是否过期

图表渲染中…
  • 实现:每次命令执行前调用 expireIfNeeded() 检查;
  • 优点:CPU 开销最小(只在访问时检查);
  • 缺点:过期但长期不被访问的 key 会常驻内存——所以必须配合定期删除。

注意区分:这里的"惰性删除"是指过期键的删除时机(访问时删),与第 16 章"惰性删除(lazy free 异步回收内存)"是两个概念,不要混淆。

3. 策略二:定期删除(activeExpireCycle)

3.1 工作原理

Redis 主循环的时间事件周期性调用 activeExpireCycle(),每次抽样删除一批过期键:

text
每 100ms(hz 10)执行一次 activeExpireCycle:
1. 从过期字典随机抽样 20 个 key(ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP)
2. 删除其中已过期的
3. 若过期比例 > 25%,继续抽样删除(循环)
4. 每次执行有时间预算(约 1ms 比例),防止阻塞主线程
图表渲染中…

3.2 关键参数

参数默认说明
hz10时间事件频率(7.0 起默认 10,动态调整)
ACTIVE_EXPIRE_CYCLE_KEYS_PER_LOOP20每轮抽样数量
时间预算约 25% 的周期每轮最多耗时,防止拖慢命令处理

3.3 7.x 的改进

Redis 7.0 重写了 activeExpireCycleexpire.c),引入动态频率:当过期键比例高时自动提高执行频率/延长预算,低时降低——削峰填谷,避免"定时删除跟不上过期洪峰"导致内存异常增长。

4. 内存淘汰(maxmemory)与过期的关系

过期 ≠ 淘汰,两者是完全独立的机制:

机制触发目的
过期删除key 到达 TTL释放业务设定的生命周期
内存淘汰内存达到 maxmemory按策略强制驱逐 key 释放内存

只有"过期但未删除"的 key 才可能被淘汰策略(volatile-* 系列)优先选中。详见下一章《内存淘汰 LRU 与 LFU》。

5. 过期键的复制语义

  • 过期由 master 主导:master 删除过期键后,向 replica 传播 DEL 命令;
  • replica 不自主删除:replica 收到读请求时若 key 已过期但未收到 DEL,会返回 nil(逻辑过期)但不删除,等待 master 的 DEL;
  • 主从一致性的代价:replica 上可能短暂存在"逻辑已过期"的 key,占用内存直到 master 同步 DEL——这是设计取舍,避免主从删除时机不一致。

6. 键空间通知(Keyspace Notifications)

7.x 支持过期事件的订阅通知(需开启):

text
# redis.conf
notify-keyspace-events Ex    # 启用过期事件(E=keyspace 事件,x=expired)
bash
# 客户端订阅过期事件
> psubscribe __keyevent@0__:expired

注意:过期通知是惰性触发的——key 真正被删除时(惰性或定期)才发布,不是 TTL 到达时刻。高精度延时任务不可依赖此机制(延迟可达秒级)。

7. 工程实践

7.1 缓存过期设计

  • 固定 TTL 的惊群问题:大量 key 同时过期 → 缓存雪崩。解法:TTL 加随机抖动(TTL + random(0, 300));
  • 热点 key 过期:加锁回源或"逻辑过期"(过期时间存 value,后台异步刷新);
  • 监控INFO keyspace 查看 expires(带 TTL 的 key 数)与 expired_keys(累计删除数),观察删除速率。

7.2 常见误区

  1. 认为过期的 key 立即消失:惰性删除下,不被访问的 key 由定期删除兜底,存在窗口期;
  2. TTL 判断“将要过期”TTL 返回 -2(不存在)/-1(无过期)需区分;
  3. 依赖过期通知做任务调度:通知有延迟、可能丢失,请用 Stream/延时队列;
  4. 一次性写入大量同 TTL key:触发定期删除洪峰,建议错峰写入。

7.3 过期机制监控

bash
> info keyspace
db0:keys=1000000,expires=500000,avg_ttl=86400000
> info stats | grep -i expired
expired_keys:12345
指标含义关注点
keys当前 key 总数与 expires 对比看过期占比
expires带 TTL 的 key 数突增→大量 key 即将集中过期
avg_ttl平均剩余 TTL持续走低说明缓存命中率在下降
expired_keys累计删除的过期 key 数单位时间增量过快→过期风暴

实践:监控 expired_keys增速(而非总量),结合 INFO statsexpired_stale_perc(定期删除中过期键占比)判断定期删除是否打满;若长期偏高,说明过期 key 存量过大,需检查 TTL 策略。

8. 小结

  • 过期删除 = 惰性(访问时删)+ 定期(抽样批量删),兼顾 CPU 与内存;
  • 7.x 的动态 activeExpireCycle 让过期洪峰更平滑;
  • 复制场景过期由 master 主导,replica 被动同步;
  • 缓存 TTL 加随机抖动防雪崩,业务"准实时"任务别依赖过期通知。

下一章讲解内存淘汰:maxmemory 与 LRU/LFU 近似算法的实现。