3. Redis 过期策略与淘汰机制
在 Redis 缓存索引设计中,内存是有限且昂贵的资源。当键设置了过期时间,或内存达到上限时,系统必须决定如何回收空间。本章深入剖析 Redis 的两种核心回收机制:过期策略(针对已过期键)与淘汰机制(针对内存压力),并对比 LRU、LFU、TTL 等淘汰算法的适用场景。
3.1 过期策略:三种删除方式
Redis 对设置了 TTL(Time To Live)的键,采用三种策略配合完成过期删除,目标是平衡 CPU 开销与内存浪费。
| 策略名称 | 触发时机 | 优点 | 缺点 | Redis 默认行为 |
|---|---|---|---|---|
| 定时删除 | 创建键时启动定时器,到期立即删除 | 内存友好,过期即释放 | CPU 高,大量定时器占用资源 | ❌ 未启用 |
| 惰性删除 | 每次访问键时检查是否过期 | CPU 开销极低 | 过期键可能长期占用内存 | ✅ 默认启用 |
| 定期删除 | Redis 主循环周期性采样检查 | CPU 与内存的折中方案 | 参数调优复杂 | ✅ 默认启用 |
实践提示: 惰性删除 + 定期删除的组合是 Redis 默认配置。定期删除每秒执行 10 次(hz=10),每次随机检查 20 个键,若过期比例超过 25% 则重复执行。这种设计避免了定时删除的 CPU 峰值,同时缓解了惰性删除的内存泄漏风险。
3.2 淘汰机制:六种淘汰策略
当 Redis 内存达到 maxmemory 限制时,淘汰机制被触发。根据业务场景选择正确的淘汰策略,是缓存架构设计的关键决策。
| 策略 | 作用范围 | 淘汰依据 | 适用场景 |
|---|---|---|---|
| noeviction | 全部键 | 不淘汰,直接返回错误 | 数据库模式,禁止丢失数据 |
| allkeys-lru | 全部键 | 最近最少使用 | 通用缓存,访问模式较均匀 |
| allkeys-lfu | 全部键 | 最不经常使用 | 热点集中,冷热分明 |
| allkeys-random | 全部键 | 随机淘汰 | 访问均匀,无热点 |
| volatile-lru | 仅设置了 TTL 的键 | 最近最少使用 | 有时效性的缓存 |
| volatile-lfu | 仅设置了 TTL 的键 | 最不经常使用 | 有时效性 + 热点集中 |
| volatile-ttl | 仅设置了 TTL 的键 | 剩余 TTL 最短 | 优先淘汰即将过期的键 |
| volatile-random | 仅设置了 TTL 的键 | 随机淘汰 | 有时效性 + 无热点 |
领域边界警示: 在 DDD 聚合根缓存设计中,不要使用
allkeys-lru 淘汰聚合根对象。聚合根是领域一致性的边界,部分淘汰可能导致聚合内数据不完整。推荐对聚合根使用 volatile-lru 并设置合理的 TTL,或直接使用 noeviction 配合显式删除。
3.3 LRU 与 LFU 的近似实现
Redis 并未实现精确的 LRU/LFU,而是采用近似算法以节省内存。理解其实现差异,有助于在缓存索引设计中做出正确选择。
- 近似 LRU:维护一个采样池(默认 5 个键),从池中淘汰最久未使用的键。采样数越大(
maxmemory-samples),结果越接近精确 LRU,但 CPU 开销也越高。 - 近似 LFU:使用莫里斯计数器(Morris Counter)记录访问频率,计数器随时间衰减。LFU 包含两个核心参数:
lfu-log-factor:控制计数器增长速率,默认 10。lfu-decay-time:控制计数器衰减速率,单位分钟,默认 1。
# 配置示例:切换到 allkeys-lfu 并调整采样精度
maxmemory-policy allkeys-lfu
maxmemory-samples 10
lfu-log-factor 10
lfu-decay-time 1
性能对比: 在相同内存压力下,LRU 的淘汰决策速度比 LFU 快约 15%-20%,但 LFU 在热点数据识别上更准确。对于缓存索引场景,若索引访问模式存在明显的"冷热交替",优先选择 LFU;若访问模式相对稳定,LRU 更高效。
3.4 过期与淘汰的协同流程
下图展示了 Redis 在键访问和内存检查时,过期策略与淘汰机制的完整协同路径。
3.5 淘汰策略选择决策树
在缓存索引的领域设计中,选择淘汰策略应遵循以下决策逻辑:
- 是否允许数据丢失? 否 →
noeviction;是 → 进入下一步。 - 所有键是否都有 TTL? 是 → 优先使用
volatile-*系列;否 → 使用allkeys-*系列。 - 访问模式是否存在明显热点? 是 → 选择
LFU;否 → 选择LRU。 - 是否需要淘汰即将过期的键? 是 → 选择
volatile-ttl。
领域驱动设计建议: 在缓存索引的限界上下文中,建议为不同的聚合根类型配置独立的 Redis 实例或逻辑数据库,并分别设置淘汰策略。例如:用户会话缓存使用
volatile-ttl,商品详情缓存使用 allkeys-lfu,配置信息缓存使用 noeviction。这种隔离避免了单一策略无法满足多领域需求的问题。
3.6 监控与调优
通过以下命令和指标,可以评估当前淘汰策略是否合理:
# 查看当前淘汰策略
CONFIG GET maxmemory-policy
# 查看内存使用与淘汰统计
INFO memory
# 关注指标:
# evicted_keys:被淘汰的键数量(持续增长说明内存不足)
# expired_keys:过期的键数量
# 查看键的 TTL 分布
redis-cli --bigkeys
# 调整采样精度(影响 LRU/LFU 准确性)
CONFIG SET maxmemory-samples 10
| 监控指标 | 正常范围 | 异常信号 | 建议操作 |
|---|---|---|---|
| evicted_keys / sec | < 100 | > 1000 | 增加 maxmemory 或优化缓存命中率 |
| expired_keys / sec | < 500 | > 5000 | 检查 TTL 设置是否过短 |
| keyspace_hits / misses | 命中率 > 90% | 命中率 < 80% | 检查淘汰策略是否误淘汰热点键 |
常见陷阱: 不要在生产环境中频繁切换淘汰策略。切换策略会清空所有键的访问历史(LRU/LFU 状态),导致短时间内大量键被误淘汰。如需调整,应在业务低峰期操作,并预热缓存。