4. Redis 过期策略与内存淘汰:定期删除、惰性删除、LRU/LFU/TTL 淘汰算法
Redis 作为内存型数据库,其内存资源是有限且昂贵的。当键设置了过期时间,或者内存使用达到上限时,Redis 必须通过一套精密的策略来回收空间。本章深入剖析 Redis 的两大核心回收机制:过期键删除策略(处理已到期的键)与内存淘汰策略(处理内存超限时的键驱逐),并对比不同算法的适用场景。
4.1 过期键删除策略:定时 vs 惰性 vs 定期
Redis 对设置了 EXPIRE / TTL 的键,采用 定期删除 + 惰性删除 的组合策略,而非实时扫描所有键。
| 策略 | 原理 | 优点 | 缺点 | Redis 是否采用 |
|---|---|---|---|---|
| 定时删除 | 为每个过期键创建定时器,到期立即删除 | 内存释放及时,无过期键残留 | CPU 开销极大,高并发下定时器管理成为瓶颈 | ❌ 不采用 |
| 惰性删除 | 每次访问键时检查是否过期,过期则删除 | CPU 友好,只处理被访问的键 | 过期键可能长期占用内存(若一直不被访问) | ✅ 组合使用 |
| 定期删除 | 每隔一段时间(默认 100ms)随机抽样检查部分键,删除过期键 | 平衡 CPU 与内存,避免长时间堆积 | 抽样频率和数量需要调优,存在删除滞后 | ✅ 组合使用 |
实践要点: 定期删除是 Redis 主动回收过期键的主要手段。它通过
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 参数控制每次抽样数量(默认 20),并限制删除操作耗时不超过 CPU 时间的 25%。若过期键比例超过 25%,会立即重复循环,防止堆积。
4.2 内存淘汰策略:当内存达到上限时
当 Redis 使用的内存达到 maxmemory 配置上限时,写入操作会触发内存淘汰。Redis 8 种淘汰策略可分为三大类:
| 类别 | 策略名称 | 算法 | 适用场景 |
|---|---|---|---|
| 不淘汰 | noeviction |
直接返回错误,禁止写入 | 纯缓存不可丢失的场景(配合告警) |
| 针对全体键 | allkeys-lru |
LRU:淘汰最近最少使用的键 | 业务无明确冷热区分,通用缓存 |
allkeys-lfu |
LFU:淘汰访问频率最低的键 | 访问模式存在明显热点倾斜 | |
allkeys-random |
随机淘汰 | 数据访问完全均匀,极少使用 | |
| 针对设置了 TTL 的键 | volatile-lru |
LRU:淘汰设置了过期时间中最近最少使用的键 | 区分持久键与缓存键,只淘汰缓存 |
volatile-lfu |
LFU:淘汰设置了过期时间中访问频率最低的键 | 缓存键存在热点倾斜 | |
volatile-random |
随机淘汰设置了过期时间的键 | 极少使用 | |
volatile-ttl |
TTL:淘汰剩余存活时间最短的键 | 优先保留即将过期的键(实际效果接近 LRU) |
关键区别:
volatile-* 系列策略只会在设置了过期时间的键中进行淘汰。如果所有键都没有设置 TTL,则 volatile-* 策略退化为 noeviction,导致写入失败。生产环境建议使用 allkeys-lru 或 allkeys-lfu,避免因未设置过期时间导致意外拒绝写入。
4.3 LRU 与 LFU 算法实现对比
Redis 的 LRU 和 LFU 并非精确实现,而是采用 近似算法 以节省内存:
- 近似 LRU:维护一个全局的
eviction pool(淘汰池,默认大小 16)。每次淘汰时,随机抽样maxmemory-samples(默认 5)个键,将其中最久未访问的键放入池中,最终从池中选择最优淘汰键。这种方式避免了维护全局双向链表的高昂内存开销。 - 近似 LFU:使用 Morris Counter(概率计数器)记录访问频率。计数器以对数概率递增,高频键的计数器增长变慢,且计数器会随时间衰减(通过
lfu-decay-time配置,默认 1 分钟衰减一次),从而适应访问模式的变化。
| 特性 | 近似 LRU | 近似 LFU |
|---|---|---|
| 淘汰依据 | 最近一次访问时间 | 历史访问频率 + 时间衰减 |
| 内存开销 | 每个对象 24 bit(用于存储 lru 时间戳) | 每个对象 24 bit(16 bit 计数器 + 8 bit 衰减因子) |
| 应对突发流量 | 一次突发访问可让键长期不被淘汰 | 突发访问后计数器快速衰减,更公平 |
| 适用场景 | 访问模式相对稳定,无剧烈热点变化 | 存在明显热点倾斜,且热点会随时间漂移 |
| 配置参数 | maxmemory-samples(抽样数) |
lfu-log-factor(计数器增长因子)lfu-decay-time(衰减分钟数) |
4.4 配置与监控实践
在生产环境中,合理配置淘汰策略和监控指标至关重要:
推荐配置模板(4GB 实例,通用缓存场景):
# 内存上限
maxmemory 4gb
# 淘汰策略:优先使用 allkeys-lru,若热点倾斜明显改用 allkeys-lfu
maxmemory-policy allkeys-lru
# 抽样数:增大可提高淘汰精度,但增加 CPU 开销
maxmemory-samples 10
# LFU 参数(仅当使用 LFU 时)
lfu-log-factor 10
lfu-decay-time 1
监控命令:
INFO memory:查看used_memory、maxmemory、evicted_keys(累计淘汰键数)。INFO stats:查看expired_keys(累计过期键数)。MEMORY USAGE <key>:估算单个键的内存占用。OBJECT idletime <key>:查看键的空闲时间(LRU 相关)。OBJECT freq <key>:查看键的逻辑访问频率(LFU 相关,需开启 LFU 策略)。
避坑指南:
- 不要使用
volatile-*策略且未设置 TTL:会导致所有键无法淘汰,写入失败。 - 避免频繁全量扫描:
KEYS *或SCAN不会触发惰性删除,但会消耗大量 CPU。 - LFU 计数器溢出:
lfu-log-factor过小会导致高频键计数器快速饱和,失去区分度。 - 淘汰抖动:
maxmemory-samples过小(如 1)会导致淘汰近似随机,建议至少 5。
4.5 本章小结
- Redis 过期键删除采用 定期删除 + 惰性删除 组合,兼顾 CPU 与内存。
- 内存淘汰策略分为 不淘汰、全体键淘汰、仅淘汰 TTL 键 三大类,推荐
allkeys-lru或allkeys-lfu。 - LRU 关注 最近访问时间,LFU 关注 历史访问频率,两者均为近似算法以降低内存开销。
- 合理配置
maxmemory-samples、lfu-log-factor等参数,并通过INFO命令持续监控淘汰与过期情况。