Redis 的过期策略和内存淘汰机制是 Redis 内存管理的核心内容,对于保证 Redis 的稳定性和性能至关重要。
1. 过期策略
Redis 有三种过期策略:定时删除、惰性删除和定期删除。
定时删除(Timed Expiration)
工作原理: 在设置 key 的过期时间时,创建一个定时器,当过期时间到达时,立即删除 key。
优点:
- 内存友好:过期 key 会被及时删除,不会占用内存
- 保证过期 key 不会占用内存
缺点:
- CPU 不友好:如果有过期 key,需要创建大量的定时器,消耗 CPU 资源
- 影响性能:定时器的创建和删除会消耗系统资源
Redis 实现: Redis 没有使用定时删除策略,因为这种方式对 CPU 消耗太大。
惰性删除(Lazy Expiration)
工作原理: key 过期时不立即删除,而是在访问 key 时检查是否过期,如果过期则删除。
优点:
- CPU 友好:只有在访问 key 时才检查是否过期,不会消耗额外的 CPU 资源
- 性能好:不会因为过期 key 的删除而影响系统性能
缺点:
- 内存不友好:如果过期 key 永远不被访问,就会一直占用内存
- 可能导致内存泄漏:大量过期 key 不被访问,会占用大量内存
Redis 实现: Redis 使用了惰性删除策略,在执行命令时检查 key 是否过期。
c// Redis 源码中的惰性删除实现 int expireIfNeeded(redisDb *db, robj *key) { // 获取 key 的过期时间 mstime_t when = getExpire(db, key); // 如果 key 没有设置过期时间,直接返回 if (when < 0) return 0; // 如果 key 还没有过期,直接返回 if (mstime() < when) return 0; // key 已过期,删除 key server.stat_expiredkeys++; propagateExpire(db, key, server.lazyfree_lazy_expire); notifyKeyspaceEvent(NOTIFY_EXPIRED, "expired", key, db->id); return dbDelete(db, key); }
定期删除(Periodic Expiration)
工作原理: 每隔一段时间,随机抽取一些 key 检查是否过期,如果过期则删除。
优点:
- 平衡 CPU 和内存:通过限制删除操作的频率,平衡 CPU 和内存的使用
- 避免内存泄漏:定期删除过期 key,避免内存泄漏
缺点:
- 实现复杂:需要合理设置删除频率和删除数量
- 可能遗漏:随机抽取可能会遗漏一些过期 key
Redis 实现:
Redis 使用了定期删除策略,在 serverCron 函数中定期执行。
c// Redis 源码中的定期删除实现 void activeExpireCycle(int type) { // 获取当前时间 long long start = ustime(); // 遍历所有数据库 for (int j = 0; j < server.dbnum; j++) { // 随机抽取一些 key 检查是否过期 for (int i = 0; i < num; i++) { // 随机选择一个 key dictEntry *de = dictGetRandomKey(db->dict); // 检查 key 是否过期 if (expireIfNeeded(db, de->key)) { // key 已过期,删除 expired++; } } } }
Redis 的过期策略
Redis 使用了惰性删除 + 定期删除的组合策略:
- 惰性删除:在访问 key 时检查是否过期,如果过期则删除
- 定期删除:每隔一段时间,随机抽取一些 key 检查是否过期,如果过期则删除
这种组合策略平衡了 CPU 和内存的使用,既保证了过期 key 会被及时删除,又不会因为大量的删除操作而影响系统性能。
2. 内存淘汰机制
当 Redis 内存使用达到最大内存限制时,Redis 会触发内存淘汰机制,删除一些 key 以释放内存。
内存淘汰策略
Redis 提供了 8 种内存淘汰策略:
1. noeviction(不淘汰)
描述:当内存使用达到最大内存限制时,不淘汰任何 key,新写入操作会返回错误。
适用场景:对数据完整性要求高的场景,不允许数据丢失。
配置:
bashmaxmemory-policy noeviction
2. allkeys-lru(所有 key 使用 LRU)
描述:从所有 key 中淘汰最近最少使用的 key。
适用场景:需要淘汰所有 key,使用 LRU 算法。
配置:
bashmaxmemory-policy allkeys-lru
3. allkeys-lfu(所有 key 使用 LFU)
描述:从所有 key 中淘汰最不经常使用的 key。
适用场景:需要淘汰所有 key,使用 LFU 算法。
配置:
bashmaxmemory-policy allkeys-lfu
4. allkeys-random(所有 key 随机淘汰)
描述:从所有 key 中随机淘汰 key。
适用场景:不需要考虑访问频率,随机淘汰 key。
配置:
bashmaxmemory-policy allkeys-random
5. volatile-lru(设置了过期时间的 key 使用 LRU)
描述:从设置了过期时间的 key 中淘汰最近最少使用的 key。
适用场景:只淘汰设置了过期时间的 key,使用 LRU 算法。
配置:
bashmaxmemory-policy volatile-lru
6. volatile-lfu(设置了过期时间的 key 使用 LFU)
描述:从设置了过期时间的 key 中淘汰最不经常使用的 key。
适用场景:只淘汰设置了过期时间的 key,使用 LFU 算法。
配置:
bashmaxmemory-policy volatile-lfu
7. volatile-random(设置了过期时间的 key 随机淘汰)
描述:从设置了过期时间的 key 中随机淘汰 key。
适用场景:只淘汰设置了过期时间的 key,随机淘汰。
配置:
bashmaxmemory-policy volatile-random
8. volatile-ttl(淘汰即将过期的 key)
描述:从设置了过期时间的 key 中淘汰即将过期的 key。
适用场景:优先淘汰即将过期的 key。
配置:
bashmaxmemory-policy volatile-ttl
LRU 算法实现
Redis 使用了近似的 LRU 算法,而不是精确的 LRU 算法。
为什么使用近似 LRU?
- 精确的 LRU 算法需要维护一个链表,每次访问 key 都需要更新链表,消耗大量 CPU 资源
- 近似 LRU 算法只需要记录 key 的访问时间,不需要维护链表,性能更好
Redis 的近似 LRU 实现: Redis 为每个 key 记录最后一次访问的时间,当需要淘汰 key 时,随机抽取一些 key,淘汰其中访问时间最早的 key。
c// Redis 源码中的近似 LRU 实现 unsigned int LRU_GetLRU(redisObject *obj) { unsigned int lru = obj->lru; if (lru >= LRU_CLOCK()) { lru = (lru & 0x7FFFFFFF); } else { lru = LRU_CLOCK() & 0x7FFFFFFF; } return lru; }
LFU 算法实现
Redis 4.0 之后引入了 LFU 算法,用于淘汰最不经常使用的 key。
LFU 算法实现: Redis 为每个 key 记录访问频率,当需要淘汰 key 时,淘汰访问频率最低的 key。
c// Redis 源码中的 LFU 实现 void LFU_DecrAndReturn(robj *obj) { unsigned long ldt = obj->lru >> 8; unsigned long counter = LFUGetCounterIncrAndReturn(obj->lru); if (counter) { counter--; obj->lru = (ldt << 8) | counter; } }
内存淘汰策略选择
选择建议:
- 如果所有 key 都设置了过期时间:使用
volatile-lru或volatile-lfu - 如果只有部分 key 设置了过期时间:使用
allkeys-lru或allkeys-lfu - 如果对数据完整性要求高:使用
noeviction - 如果不需要考虑访问频率:使用
allkeys-random或volatile-random
3. 监控内存使用
可以使用 INFO memory 命令查看 Redis 的内存使用情况:
bash# 查看内存使用情况 INFO memory # 查看内存使用详情 used_memory:1024000 used_memory_human:1.00M used_memory_rss:2048000 used_memory_rss_human:2.00M used_memory_peak:2048000 used_memory_peak_human:2.00M maxmemory:1073741824 maxmemory_human:1.00G
总结
Redis 的过期策略和内存淘汰机制是 Redis 内存管理的核心内容。过期策略包括定时删除、惰性删除和定期删除,Redis 使用了惰性删除 + 定期删除的组合策略。内存淘汰机制包括 8 种策略,可以根据实际场景选择合适的策略。在实际应用中,需要根据业务需求和性能要求,选择合适的过期策略和内存淘汰策略。