BodySense Redis 第 3 课:TTL、Expiration 与 Eviction
区分 TTL 的逻辑过期、Redis 的实际清理与内存压力下的 Eviction,并推导这些机制对 BodySense Session authority 和 Rate Limit 的不同风险。
[!info] related notes
- 课程 MOC:BodySense Redis 从零到一课程 MOC
- 前置课程:第 2 课
- 对应知识笔记:Redis Expiration 与 Eviction、Redis
- 项目:Redis Session Authority
BodySense Redis 第 3 课:TTL、Expiration 与 Eviction
[!abstract] 学习进度 第 3 / 12 课。核心问题:“Key 消失”可能有完全不同的业务原因,不能只看最终结果。
TTL 是什么:把“生命周期”直接附着在 Key 上
TTL = Time To Live,直译就是“还能活多久”。
Redis 允许给 Key 绑定一个过期时间:
SET verification:abc user-42 EX 300
这里同时做了两件事:
verification:abc = user-42
expire after 300 seconds
也可以先写再设置:
SET verification:abc user-42
EXPIRE verification:abc 300
但是两条命令之间有失败窗口;如果语义允许,优先用 SET ... EX 一次表达会更完整。
TTL / PTTL 怎么观察
TTL verification:abc
PTTL verification:abc
TTL 返回秒,PTTL 返回毫秒。
常见特殊结果:
-2 → Key 不存在
-1 → Key 存在,但没有 expiration
>=0 → 剩余 TTL
这在排查 Rate Limit 或 Session 时非常有用:
counter 还在,但 TTL = -1
往往说明原本应该有生命周期的 Key 被错误写成了永久 Key。
TTL 是 Key 的属性,不是“值里面的 expires_at 字段”
PostgreSQL 常见:
row.expires_at = 2026-09-18 20:00
应用查询时要自己判断是否过期,并考虑清理。
Redis TTL 则由 Redis 自己参与生命周期判断:
Key metadata
├─ value
└─ expire_at
到期后,应用读取时 Redis 会把它当成不存在。
一个常见坑:重新 SET 可能改变 TTL
当你重新写一个已有 Key 时,要明确自己是否希望保留旧 TTL。不要把“更新 Value”和“保留生命周期”想当然地绑定在一起。
例如某些更新方式会替换 Key 原有过期语义;需要保留时应显式使用支持 KEEPTTL 的语义,或重新设置 TTL。
所以每次设计临时状态都应问:
谁创建 TTL?
谁续期?
谁主动删除?
重写 Value 时 TTL 是否应延长?
Expiration:时间到了,应该失效
例如:
验证码 5 min
Session refresh lifetime
rate limit 10 min
idempotency 24 h
这些到期具有业务意义。
Expiration
→ 时间到了
→ 应用认为它应该死
TTL 到 0 是否“瞬间物理删除”
要区分:
逻辑过期
vs
物理内存清理
当 Key 已到期,从应用语义上:
GET expired-key
→ 不存在
即使 Redis 内部对应内存对象尚未在那一纳秒完成物理回收,也不会把过期值继续作为有效结果返回。
被动 / Lazy Expiration
访问时发现过期:
GET session:s1
→ Redis 检查 expire time
→ 已过期
→ 删除 / 视为不存在
主动 / Active Expiration
如果没人访问,Redis 也会在后台主动清理已过期 Key,避免永久占用内存。
因此:
TTL 到期定义逻辑生命周期,不等于精准垃圾回收时刻。
Eviction:内存不够,不得不提前淘汰
假设:
maxmemory = 1 GB
used = 1 GB
新的写入到来,需要根据 maxmemory-policy 决定是否淘汰已有 Key。
Eviction
→ 内存压力
→ Redis 为腾空间提前移除数据
这可能发生在 TTL 还剩 30 分钟时。
Expiration vs Eviction
| Expiration | Eviction | |
|---|---|---|
| 原因 | TTL 到期 | 内存不足 |
| 谁定义 | 业务生命周期 | Redis memory policy |
| 时间 | 可预期 | 取决于内存压力 |
| 语义 | 正常失效 | 被迫提前移除 |
| Cache | 正常 | 常可接受 |
| Session | 正常登出/过期 | 可能提前掉登录 |
| Rate Limit | 窗口结束 | 可能降低安全保护 |
一句话:
Expiration 是“应该死”;Eviction 是“不得不死”。
BodySense Session 被 Eviction 会怎样
假设:
session:s1
TTL 还有 25 分钟
由于内存压力被提前淘汰。
用户请求:
JWT signature ✓
JWT exp ✓
GET session:s1 → nil
如果 Redis 是认证 authority:
→ 不再能证明在线授权
→ fail closed
→ 用户被提前登出 / 请求失败
安全上不会因为 Redis 丢状态而自动放行,但可用性会变差。
Rate Limit 被 Eviction 更危险
login_failed:ip = 4
TTL = 10 min
本来第 5 次应该被限制。
如果 Key 被提前 eviction:
下一次 INCR
→ 1
攻击窗口被意外重置。
所以 Session、Refresh Replay Tombstone、Rate Limit 等不是普通缓存。
Eviction 不是 TTL 的另一个名字
Redis 可以同时有:
TTL = 30 min
但
memory pressure = now
如果配置允许淘汰,这个 Key 完全可能在 30 分钟结束前消失。
所以一个非常重要的公式是:
TTL
= 最晚什么时候应该失效
Eviction policy
= 内存不够时 Redis 是否允许更早删它
这两个机制必须分开推理。
maxmemory-policy 的基础分类
noeviction
内存满
→ 不自动赶走已有 Key
→ 新写入失败
对于 security-critical runtime state,通常比静默丢状态更容易推理。
allkeys-*
可以从所有 Key 中淘汰,例如 LRU / LFU 类策略。常见于纯缓存实例。
volatile-*
只从设置了 TTL 的 Key 中选择淘汰候选。
[!warning] volatile 不等于“只删已经过期的 Key” session:s1 即使 TTL 还有 25 分钟,只要它有 TTL,也可能属于候选。
为什么 Critical State 与 Disposable Cache 要考虑隔离
一个 Redis 同时:
├─ Session
├─ Refresh Token
├─ Rate Limit
├─ AI Cache
└─ Workspace Cache
如果大量可重建 Cache 占满内存,却让认证状态一起被淘汰:
不同丢失语义的数据进入了同一个 eviction / failure domain。
长期可以考虑:
redis-critical
├─ session
├─ refresh
├─ revocation
└─ critical auth rate-limit
redis-fast
├─ cache
├─ pub/sub
├─ wakeup
└─ optional queue
当前规模小时不一定立即拆实例,但必须理解这个边界。
Fail Closed
如果 Redis 是 auth authority,登录流程出现:
密码验证成功
JWT 已生成
Redis Session 写入失败
不能继续告诉客户端“登录成功”。
正确思路:
无法写入授权 authority
→ 登录流程失败 / 服务暂不可用
监控指标
两个信号不要混:
expired_keys
→ TTL 正常到期
evicted_keys
→ 内存压力导致淘汰
如果 Redis 承担 Session,evicted_keys 增长是高优先级信号。
Prediction
[!question] 先判断“为什么 Key 消失”,再判断业务影响
A. 验证码 TTL 到期后消失:Expiration 还是 Eviction?
B. Session TTL 还有 30 分钟,但 Redis 因内存压力把它删掉:是什么?用户会看到什么?
C. Session / Refresh / AI Cache 共用allkeys-lru有什么架构问题?
D. Redis Session 写入失败时,登录是否还能算成功?
E. Key 已逻辑过期,但 Redis 还没在后台完成物理内存回收,GET应看到什么?
F.TTL key返回-1,对一个本应 10 分钟后过期的 Rate Limit counter 意味着什么?
[!success]- Answer / 解析 A → Expiration。 生命周期按业务预期结束,是正常失效。
B → Eviction。 Key 在 TTL 前被迫移除;如果它是 Session authority,后续请求会得到 definitive miss,用户可能提前掉登录。安全上通常 fail closed,但可用性受损。
C → 不同丢失语义混在同一 eviction domain。 可重建 AI Cache 可以被丢,但 Session/Refresh/Rate Limit 被提前丢会改变认证和安全语义。
D → 不应该。 BodySense 把 Session Redis 状态当 revocation authority;无法建立这个 authority 时不应向客户端交付一个“登录成功但下一请求无法安全验证”的 credential。
E → 不存在。 TTL 到期决定逻辑可见性;物理清理可以稍后发生。
F → Key 存在但没有 expiration。 对本应有窗口的 limiter 来说这是异常,可能意味着
INCR成功但 TTL 没建立,或后续写入错误移除了过期语义。
Explain-back / L4 Gate
能够解释:
- TTL、Expiration、Eviction 三者关系;
- 为什么 volatile-* 仍可能提前删除未到 TTL 的 Session;
- 为什么 Cache Redis 和 Auth Redis 的内存策略不能机械共用;
- 为什么 expired_keys 与 evicted_keys 的运维含义完全不同。