缓存淘汰策略

3317 字
7 分钟阅读

常见面试题

在深入技术细节之前,我们先来看几个高频的面试问题,这些问题也是我们在生产环境中必须面对的:

  • 生产上你们的Redis内存设置多少?
  • 如果内存满了你怎么办?
  • Redis的过期删除和内存淘汰策略有哪些?
  • LRU和LFU算法的区别是什么?

Redis内存配置与监控

在讨论如何“节流”之前,我们先要学会如何“开源”——即如何为Redis配置和监控内存。

如何设置Redis最大内存 (maxmemory)?

Redis允许我们通过maxmemory参数来限制其最大可用内存。

  • 查看当前配置:

    127.0.0.1:6379> CONFIG GET maxmemory
    1) "maxmemory"
    2) "0" 

    默认值为0,在64位操作系统下表示不限制内存使用;在32位系统下,则最多使用3GB。

  • 修改方式:

    1. 配置文件修改 (永久生效) :打开 redis.conf 文件,添加或修改配置 maxmemory <bytes>。例如 maxmemory 1gb
    2. 命令修改 (临时生效) :通过 CONFIG SET maxmemory <bytes> 命令动态修改。例如 CONFIG SET maxmemory 1024mb
  • 生产建议: 通常推荐将Redis的maxmemory设置为物理内存的3/4,为操作系统和其他进程预留足够的空间。

使用 INFO memory 命令可以获取详细的内存使用报告。

127.0.0.1:6379> INFO memory
# Memory
used_memory:1041376
used_memory_human:1016.97K

used_memory_human 字段直观地显示了当前占用的内存大小。

内存占满会发生什么?

如果设置了maxmemory,当Redis的内存使用达到上限时,任何尝试写入新数据的命令都会返回一个错误,除非你配置了后续将要介绍的内存淘汰策略。

# 故意将最大内存设为1字节
127.0.0.1:6379> CONFIG SET maxmemory 1
(error) OOM command not allowed when used memory > 'maxmemory'

过期键的删除策略

我们经常为键设置过期时间(TTL),但Redis并不会在键过期的瞬间就立即删除它。因为它需要在CPU性能和内存占用之间做出权衡。Redis结合了两种主要的删除策略。

1. 惰性删除 (Lazy Deletion)

“等你需要时,我再检查。”

  • 工作方式:数据到达过期时间后,Redis不做任何处理。直到下一次有客户端访问这个键时,Redis会先检查其是否过期。如果已过期,则执行删除并返回nil(不存在);如果未过期,则正常返回数据。
  • 优点:对CPU极其友好,因为它只在必要时才进行检查和删除,避免了大量的CPU周期消耗在“巡逻”上。(拿时间换空间)
  • 缺点:对内存极不友好。如果大量过期键从未被再次访问,它们将永远滞留在内存中,如同“内存泄漏”,白白占用宝贵的存储空间。(拿空间换时间)

2. 定期删除 (Periodic Deletion)

“我会定期抽查,但不会检查每一个。”

  • 工作方式:惰性删除的内存风险太大,因此Redis引入了定期删除作为补充。Redis会每隔一段时间(默认100ms),从设置了过期时间的键空间中随机抽取一部分键进行检查,并删除其中的过期键。
  • 优点:这是前两种策略的折中。它通过限制执行频率和时长,减少了对CPU的影响,同时也能周期性地释放过期键占用的内存,缓解了内存压力。
  • 缺点:由于是随机抽查,它无法保证所有过期键都被及时删除。总会有一些“漏网之鱼”。

总结:Redis通过惰性删除确保最终一致性(访问时肯定会被清理),通过定期删除尽力维持内存的健康状态。但这两种策略的组合仍然存在漏洞: 如果一个键既没有在定期删除中被抽到,也再也没有被客户端访问,它依然会成为内存中的“僵尸数据” 。当这类数据堆积过多,就需要最终的兜底方案登场了。

内存淘汰策略 (Eviction Policies)

当Redis内存使用达到maxmemory上限时,如果此时仍有写入操作,内存淘汰策略就会被触发,决定应该“牺牲”哪些数据来为新数据腾出空间。

Redis提供了8种不同的淘汰策略,可以从键的范围(所有键 vs. 仅设置了过期时间的键)和淘汰算法两个维度来划分:

策略描述
noeviction默认策略。不淘汰任何数据,任何可能导致内存增加的写命令都会返回错误。
allkeys-lru所有键中使用近似LRU算法进行淘汰。
volatile-lru设置了过期时间的键中使用近似LRU算法进行淘汰。
allkeys-lfu所有键中使用近似LFU算法进行淘汰。
volatile-lfu设置了过期时间的键中使用近似LFU算法进行淘汰。
allkeys-random所有键中随机淘汰。
volatile-random设置了过期时间的键中随机淘汰。
volatile-ttl设置了过期时间的键中,淘汰剩余生存时间(TTL)最短的键。
核心算法解析:LRU vs. LFU
  • LRU (Least Recently Used - 最近最少使用)

    • 核心思想:淘汰最长时间未被访问的数据。它关注的是数据最后一次被访问的时间点
    • 优点:实现简单,能有效处理热点数据。
    • 缺点:无法很好地处理“偶发性高频访问”后长期不用的数据。比如一个数据在几分钟内被访问了100次,然后一天都未被访问,它依然可能比一个1小时前被访问过1次的数据“更不容易”被淘汰。
  • LFU (Least Frequently Used - 最近最不常用)

    • 核心思想:淘汰在一段时间内访问次数最少的数据。它关注的是数据在一段时间内的访问频率
    • 优点:能更精确地反映数据的热度,避免了LRU的上述缺陷。
    • 缺点:实现相对复杂,需要为每个键维护一个访问计数器。

举例说明
假设内存已满,需要淘汰一个页面。页面访问序列为 2, 1, 2, 1, 2, 3, 4

  • 按LRU:淘汰 页面1。因为在页面4需要调入时,页面1距离上一次被访问的时间最长。
  • 按LFU:淘汰 页面3。因为在过去一段时间内,页面3只被访问了1次,频率最低(页面2访问3次,页面1访问2次)。

生产环境配置建议

  1. 如何选择淘汰策略?

    • allkeys-lru (推荐) :如果你不确定用哪种,这是个很好的通用选择。它假设最近访问的数据未来也更可能被访问。
    • allkeys-lfu:如果你的业务场景中,数据的访问频率比访问时间更能体现其价值,那么LFU是更优选择 (Redis 4.0+)。
    • volatile-ttl:如果你能通过设置不同的过期时间来区分数据的优先级,这个策略非常有用。
    • allkeys-random:如果你的数据访问模式非常平均,没有明显的热点,随机策略性能最好。
  2. 性能优化建议

    • 避免存储Big Key:一个巨大的键(如几MB的Hash或String)在被淘汰时可能会导致Redis短暂阻塞。
    • 开启惰性释放 (Lazy Freeing) :在redis.conf中设置 lazyfree-lazy-eviction=yes。这会让Redis在淘汰键时,将内存释放操作放到后台线程执行,避免阻塞主线程,提升性能。

总结
Redis的内存管理是一个分层、立体的防御体系。它首先通过惰性删除定期删除主动清理过期数据,当内存压力触及上限时,再启动内存淘汰策略作为最终的保障。深刻理解这些机制,并根据业务场景做出合理的配置,是确保Redis服务稳定、高效运行的关键所在。

面试题回答

Q1: 生产上你们的Redis内存一般设置多少?
A: 一般设置为服务器物理内存的75% 。这样可以为操作系统本身的开销、后台任务(如RDB持久化时的fork操作)以及其他可能运行的进程预留足够的内存,防止因内存不足导致OOM Killer或严重的Swap性能下降。

Q2: 如果Redis内存满了会怎么样?
A: 这取决于你的maxmemory-policy配置。

  • 如果是默认的 noeviction,任何会导致内存增加的写命令(如 SET, LPUSH)都会立即返回一个OOM (Out of Memory) 错误。
  • 如果配置了其他淘汰策略(如 allkeys-lru),Redis会先尝试淘汰一个或多个键来腾出空间,然后再执行写命令。如果无法腾出足够空间,依然会返回错误。

Q3: Redis的过期删除策略和内存淘汰策略有什么区别?
A:

  • 目的不同过期删除是为了释放那些“逻辑上”已经无效的键所占用的内存。内存淘汰是在物理内存不足时,为了给新数据腾出空间,被迫删除某些“逻辑上”仍然有效的键。
  • 触发时机不同过期删除由“键被访问时(惰性)”或“周期性检查时(定期)”触发。内存淘汰只在内存使用达到maxmemory上限,并且有新的写入请求时才会触发。
  • 本质:过期删除是主动清理,内存淘汰是被动防御

Q4: LRU和LFU算法的区别是什么?你推荐用哪个淘汰策略?
A:

  • 区别

    • LRU (最近最少使用) 关注的是时间:淘汰最长时间没有被访问过的数据。
    • LFU (最不经常使用) 关注的是频率:淘汰在一段时间内访问次数最少的数据。LFU能更好地处理偶发性、短时高频访问后变冷的数据。
  • 推荐策略

    • allkeys-lru 是最通用、最安全的选择,适用于大多数业务场景。
    • 如果你的业务能明确区分数据的热度,并且希望长期热门的数据不被偶然访问的冷数据挤掉,allkeys-lfu (Redis 4.0+) 是更优的选择。
    • 如果数据都有明确的生命周期,volatile-ttl 是一个很好的选择。

相关文章

最后更新:2025年09月29日
分享: