3. Redis 过期策略:定时删除、惰性删除、定期删除、内存淘汰机制
Redis 作为内存数据库,其所有数据都存储在内存中。为了在有限的内存资源下高效运行,Redis 提供了两套清理机制:过期删除策略(针对设置了 TTL 的 Key)和内存淘汰机制(针对内存使用达到上限时的全局清理)。理解这两套机制是构建缓存一致性方案的基础。
3.1 过期删除策略:三种核心方式
当 Key 设置了过期时间(EXPIRE、SETEX 等命令),Redis 需要在过期后将其删除。Redis 并未采用单一的删除方式,而是结合了以下三种策略。
| 策略名称 | 触发时机 | CPU 开销 | 内存开销 | 主要风险 |
|---|---|---|---|---|
| 定时删除 | Key 过期时立即触发 | 高(频繁创建定时器) | 低(及时释放) | CPU 压力大,影响吞吐量 |
| 惰性删除 | 访问 Key 时检查是否过期 | 低(仅访问时检查) | 高(过期 Key 堆积) | 内存浪费,可能产生大量过期 Key |
| 定期删除 | Redis 主循环周期性执行 | 中等(可配置频率) | 中等(分批清理) | 清理不及时,需配合惰性删除 |
3.1.1 定时删除(Timed Deletion)
Redis 为每个设置了过期时间的 Key 创建一个定时器。当 Key 到期时,定时器立即触发删除操作。这是最“主动”的方式,能保证过期 Key 被立即清理,内存释放最及时。但 Redis 是单线程模型,大量定时器会严重抢占 CPU 资源,降低服务吞吐量。因此,Redis 并未默认采用纯定时删除,而是将其作为辅助手段。
3.1.2 惰性删除(Lazy Deletion)
这是 Redis 默认且最重要的过期删除策略。当客户端请求访问某个 Key 时,Redis 会先检查该 Key 是否已过期。如果过期,则立即删除该 Key,并返回 nil(或空结果)。这种方式对 CPU 最友好,因为只在实际访问时做检查。但缺点是:如果过期 Key 长时间不被访问,它们会一直占用内存,形成“内存泄漏”。
实战案例: 假设缓存中存储了 100 万个 5 分钟过期的 Session Key。如果用户不再活跃,这些 Key 将永远不会被访问。惰性删除无法清理它们,导致内存持续增长。此时必须依赖定期删除或内存淘汰机制。
3.1.3 定期删除(Periodic Deletion)
为了解决惰性删除的内存堆积问题,Redis 引入了定期删除。Redis 每隔 100ms(可配置)会随机抽取一部分设置了过期时间的 Key,检查并删除其中已过期的 Key。这是一个折中方案:
- 频率控制: 每次运行时间不超过 25ms,避免阻塞主线程。
- 自适应调整: 如果每次抽样的过期比例超过 25%,则继续执行删除,直到比例低于 25% 或超时。
- 随机抽样: 从
expires字典中随机抽取 20 个 Key 进行检查。
最佳实践: 定期删除 + 惰性删除是 Redis 默认的组合策略。定期删除负责主动清理,惰性删除负责兜底。对于大多数业务场景,这种组合已经足够。如果内存压力极大,则需要配合内存淘汰机制。
3.2 内存淘汰机制(Eviction Policy)
当 Redis 使用的内存达到 maxmemory 配置的上限时,Redis 会根据配置的淘汰策略,主动删除一些 Key 来释放内存。这并非针对过期 Key,而是针对所有 Key(包括未设置 TTL 的)。
Redis 8 种淘汰策略可分为三类:
| 类别 | 策略名称 | 说明 | 适用场景 |
|---|---|---|---|
| 不淘汰 | noeviction |
不淘汰任何 Key,写入操作直接返回错误 | 纯读缓存或不允许丢失数据的场景 |
allkeys-lru |
从所有 Key 中淘汰最近最少使用的 | 通用缓存,对访问模式无特殊要求 | |
| 针对所有 Key | allkeys-lfu |
从所有 Key 中淘汰最不经常使用的 | 热点数据访问频率差异极大的场景 |
allkeys-random |
从所有 Key 中随机淘汰 | 数据访问均匀分布,无热点 | |
volatile-lru |
从设置了 TTL 的 Key 中淘汰最近最少使用的 | 缓存数据都有过期时间,且希望优先淘汰冷数据 | |
| 仅针对设置了 TTL 的 Key | volatile-lfu |
从设置了 TTL 的 Key 中淘汰最不经常使用的 | 带过期时间的缓存,且访问频率差异大 |
volatile-random |
从设置了 TTL 的 Key 中随机淘汰 | 带过期时间的缓存,数据均匀访问 | |
volatile-ttl |
从设置了 TTL 的 Key 中淘汰即将过期的(剩余 TTL 最短) | 优先淘汰即将过期的数据,减少无效缓存 |
重要提示: 如果使用 volatile-* 系列策略,但所有 Key 都未设置 TTL,则 Redis 会退化为 noeviction 行为,导致写入失败。因此,使用 volatile-* 策略时,务必确保缓存数据都设置了合理的过期时间。
3.3 过期策略与淘汰机制的关系
下图展示了 Redis 在收到写入请求时,如何协同使用过期删除和内存淘汰机制:
3.4 实战配置建议
在构建缓存一致性方案时,推荐以下配置组合:
# 设置最大内存,建议为物理内存的 60%-80%
maxmemory 4gb
# 淘汰策略:优先使用 allkeys-lru,保证热点数据留存
maxmemory-policy allkeys-lru
# 定期删除频率(hz 越大,CPU 消耗越高,清理越及时)
hz 10
# 惰性删除默认开启,无需额外配置
# 定期删除默认开启,无需额外配置
选型建议:
- 通用缓存场景:
allkeys-lru是最安全的选择,能自动保留热点数据。 - 带 TTL 的 Session 缓存:
volatile-ttl或volatile-lru,优先淘汰即将过期的数据。 - 数据不可丢失场景:
noeviction,配合监控告警,防止内存写满。
3.5 总结:过期与淘汰的闭环
Redis 的过期删除和内存淘汰机制共同构成了内存管理的闭环:
- 主动层: 定期删除(定时扫描过期 Key)
- 被动层: 惰性删除(访问时检查过期)
- 兜底层: 内存淘汰(内存超限时全局清理)