过期与淘汰
EXPIRE、TTL、缓存雪崩/穿透概念。
TTL 与过期淘汰:数据的保质期
这一课你将能做到
- 熟练使用
EXPIRE/PEXPIRE/PERSIST,并准确解释TTL返回 -1 和 -2 的区别 - 说清 Redis 删除过期 key 的两条路径(惰性删除 + 定期删除),以及内存满时 8 种淘汰策略怎么选
- 用「TTL 抖动 + 空值缓存 + 互斥重建」三个手段讲清并预防缓存穿透、击穿、雪崩
为什么要学
给缓存设过期时间,看似一行 EXPIRE 的事,实际上藏着三个经典生产事故:
- 雪崩:500 个缓存 key 设了同一时刻过期,那一秒所有请求打到 MySQL,数据库直接被压垮
- 击穿:某个热点 key 过期瞬间,上千并发同时 miss,全部涌向数据库重建同一份缓存
- 穿透:有人恶意请求根本不存在的 id,缓存里永远没有,每次都打到数据库
这三个问题的解药全部和 TTL 设计有关。本站第 12 课的实战会逐条落地这些策略,这一课先把原理和命令打牢。另外本站限流器「首次 INCR 时才 EXPIRE」的设计,也只有理解 TTL 机制才能看懂。
先建立图像
每个 key 可以挂一个「电子保质期标签」:
SET coderoadmap:tracks:list '...' EX 300
│
└─ 300 秒后标签归零
TTL 返回值的三种状态:
≥ 0 → 还剩多少秒
-1 → key 存在,但永不过期(缓存场景的危险信号)
-2 → key 不存在
「过期了」不等于「立刻从内存消失」。Redis 用两条路径清理过期 key:
惰性删除:下次有人摸这个 key 时顺手删(长期没人摸 → 一直占内存)
定期删除:后台周期性随机抽检一批 key,删掉已过期的(抽样,扫不全)
两条路径都不保证及时 → 内存仍可能一直涨 → 需要淘汰策略兜底
人话:TTL 像「便签上的保质期」。保质期全印成同一天,仓库(MySQL)会在那天被挤爆。
跟着做
步骤 1:TTL 三态实验
docker exec -it redis-dev redis-cli
SET demo:a "1" EX 30
TTL demo:a # (integer) ≥0,倒计时
SET demo:b "2"
TTL demo:b # (integer) -1 —— 存在但永不过期
TTL demo:nope # (integer) -2 —— 不存在
PERSIST demo:a # 摘掉保质期
TTL demo:a # (integer) -1
EXPIRE demo:a 10 # 再挂 10 秒
步骤 2:观察「过期 ≠ 瞬间删」
SET demo:c "3" EX 2
# 等 3 秒
EXISTS demo:c # (integer) 0 —— 业务视角已不存在
MEMORY USAGE demo:c # (nil) —— 空间已被回收或将被回收
Redis 不维护全局倒计时表,过期 key 在被访问或被抽检前可能仍占内存——这就是需要内存淘汰策略兜底的原因。
步骤 3:看淘汰策略配置
CONFIG GET maxmemory-policy
CONFIG GET maxmemory
默认 noeviction(内存满后所有写命令报错)。8 种策略一览:
noeviction 内存满,写命令报错(默认)
allkeys-lru 全库范围 LRU 淘汰 ← 纯缓存实例首选
volatile-lru 只在设了 TTL 的 key 里 LRU
allkeys-lfu 全库 LFU(按访问频率)
volatile-lfu 带 TTL 的 key 里 LFU
allkeys-random 全库随机淘汰
volatile-random 带 TTL 的 key 里随机
volatile-ttl 优先淘汰剩余 TTL 最短的