文章目录
- 引言
- 缓存容量该设多大
- [Redis 的 8 种淘汰策略](#Redis 的 8 种淘汰策略)
-
- noeviction:不淘汰
- [volatile-* 系列:只淘汰过期数据](#volatile-* 系列:只淘汰过期数据)
- [allkeys-* 系列:所有数据都可能被淘汰](#allkeys-* 系列:所有数据都可能被淘汰)
- [LRU 算法在 Redis 中的实现](#LRU 算法在 Redis 中的实现)
-
- [经典 LRU 的性能问题](#经典 LRU 的性能问题)
- [近似 LRU 的实现](#近似 LRU 的实现)
- 策略选择建议
- 被淘汰数据的处理
- 总结

引言
把所有数据都塞进 Redis 当缓存?听起来很爽,但完全不划算。1TB 内存大约 3.5 万元,1TB 磁盘大约 1000 元,相差 35 倍。再加上"二八原理"------80% 的请求只访问 20% 的数据,把全量数据放进内存等于花大钱办小事。
缓存容量必然有限,写满之后怎么办?这就涉及缓存替换机制。理解 Redis 的 8 种淘汰策略,以及 LRU 算法在 Redis 中的近似实现,是配置高效缓存的基础。
缓存容量该设多大
很多人遵循"二八原理",认为缓存设置为总数据量的 20% 就能拦截 80% 的访问。但实际情况比理论复杂得多。

数据访问的局部性会因业务场景不同而变化:
- 电商大促:热门商品可能只占 5%,却承载 90% 以上的访问。这时候缓存 5% 就够了
- 全量统计查询:所有商品都要被访问,缓存 20% 也只能拦截 20% 的请求
- 重尾效应场景:用户个性化需求强(如视频网站),20% 的数据可能贡献不了 80% 的访问,反而剩下的 80% 数据贡献了更多访问量
实际工程经验是,缓存容量在总数据量的 5% 到 40% 之间都有合理场景。比较稳妥的建议是 15% 到 30%,兼顾访问性能和内存成本。
设置 Redis 缓存最大容量的命令:
CONFIG SET maxmemory 4gb
容量定下来之后,写满是迟早的事。淘汰机制要解决两个问题:选谁淘汰、怎么处理被淘汰的数据。
Redis 的 8 种淘汰策略

Redis 4.0 之前提供了 6 种内存淘汰策略,4.0 又新增了 2 种 LFU 策略,共 8 种。按照是否进行数据淘汰,先分成两类:
不淘汰数据:noeviction(默认策略)
会淘汰数据:再按候选数据集范围分两类------
只在设置了过期时间的数据中淘汰:
- volatile-random:随机选择
- volatile-ttl:剩余存活时间最短的优先
- volatile-lru:使用 LRU 算法
- volatile-lfu:使用 LFU 算法(4.0 新增)
在所有数据中淘汰:
- allkeys-random:随机选择
- allkeys-lru:使用 LRU 算法
- allkeys-lfu:使用 LFU 算法(4.0 新增)
noeviction:不淘汰
内存超过 maxmemory 后,Redis 不再接受写请求,直接返回错误。Redis 用作缓存时,数据集通常远大于缓存容量,这个策略基本不适合缓存场景。
volatile-* 系列:只淘汰过期数据
这四种策略的候选范围被限制在已设置过期时间的键值对上。即使缓存没满,过期数据也会被删除。
如果业务上没给某些数据设置过期时间(比如想让它们一直驻留),volatile-* 策略不会动它们。这个特性在某些场景很有用------比如置顶新闻、置顶视频,需要长期保留的话,给它们不设过期时间,配合 volatile-lru 策略,就能保证置顶数据永远不被淘汰。
allkeys-* 系列:所有数据都可能被淘汰
候选范围扩大到所有键值对,无论是否设置过期时间。只要被策略选中,即使过期时间没到也会被删除。
LRU 算法在 Redis 中的实现

LRU(Least Recently Used,最近最少使用)的核心思想是:刚被访问的数据是热数据,未来还可能被访问;长久不访问的数据,未来也大概率不被访问。
经典 LRU 算法用双向链表实现:
- 链表头部叫 MRU 端,存放最近访问的数据
- 链表尾部叫 LRU 端,存放最久未访问的数据
- 每次访问数据时,把它移到 MRU 端
- 缓存满时,从 LRU 端淘汰
例如链表 [6, 3, 9, 20, 5],如果先访问 20,再访问 3,链表会变成 [3, 20, 6, 9, 5]。新数据 15 写入时,5 被淘汰,链表变成 [15, 3, 20, 6, 9]。
经典 LRU 的性能问题
直接实现经典 LRU 在 Redis 中代价太高:
- 维护一个全局链表,需要额外的内存空间
- 每次访问都要移动链表节点,高频访问下大量链表操作会拖慢 Redis
为了不让淘汰机制本身拖累 Redis 性能,Redis 实现了近似 LRU。
近似 LRU 的实现
Redis 的近似 LRU 通过两点优化:
第一,每个数据对象(RedisObject)记录一个 lru 字段,存放最近一次访问的时间戳。
不再需要全局链表,只需要每个对象自带一个时间戳,访问时更新即可。
第二,淘汰时随机采样,从样本中挑出最旧的。
需要淘汰数据时:
- 第一次随机选 N 个数据作为候选集合
- 比较 N 个数据的 lru 字段值,淘汰最小的(即最久未访问的)
- 后续淘汰时,新进入候选集合的数据 lru 值必须小于集合中现有的最小 lru 值
- 候选集合满了之后,淘汰其中 lru 最小的
采样数量由配置项 maxmemory-samples 控制:
CONFIG SET maxmemory-samples 100
样本越大越接近精确 LRU,但开销也越大。Redis 默认是 5。
近似 LRU 牺牲了一点精度,换来了不需要维护链表、不需要每次访问都移动节点的好处。这种工程权衡在 Redis 的实现中很常见。
策略选择建议
实际配置中如何选择?给出几条经验:
优先用 allkeys-lru
适用于业务数据有明显冷热区分的场景。LRU 是经过时间检验的算法,能把最近常访问的数据保留下来。
冷热不明显时用 allkeys-random
如果业务访问频率比较均匀,没有明显的热点,随机淘汰反而是个简单有效的选择。
有置顶需求时用 volatile-lru
把需要置顶的数据不设过期时间,其他数据设置过期时间,配合 volatile-lru。这样置顶数据永远不会被淘汰,普通数据按 LRU 规则筛选。
被淘汰数据的处理

数据被选中淘汰之后,怎么处理?这取决于数据是干净还是脏的。
干净数据:和数据库中保存的值一致,没有被修改过。直接删除即可。
脏数据:被修改过,缓存中的值比数据库的值新。理论上应该把脏数据写回数据库再删除,否则最新值会丢失。
但 Redis 不会主动把脏数据写回数据库。无论数据是否为脏,Redis 都直接删除。
这意味着使用 Redis 作为缓存时,应用程序必须自己处理数据修改:
- 只读缓存模式:修改时直接写数据库,缓存只删不改,不存在脏数据问题
- 读写缓存 + 同步直写:写缓存的同时也写数据库,不会留下脏数据
- 读写缓存 + 异步写回:缓存中存在脏数据,但 Redis 淘汰时不会回写,这种模式需要业务自己设计回写逻辑
正因如此,Redis 用作缓存时,更常见的搭配是只读缓存或读写缓存 + 同步直写。
总结
Redis 缓存替换的关键点:
- 缓存容量建议设置为总数据量的 15% 到 30%,结合数据访问规律调整
- 8 种淘汰策略中,noeviction 不淘汰数据,不适合缓存;volatile-* 只淘汰带过期时间的,allkeys-* 淘汰所有数据
- Redis 的 LRU 是近似实现,通过每对象记录 lru 字段 + 随机采样的方式,避免维护全局链表
- Redis 不会主动回写脏数据,应用层必须保证数据修改时同步到数据库
策略选择没有标准答案,要结合业务的数据访问特征、是否有置顶需求、以及对一致性的要求综合权衡。配置完成后,建议通过监控缓存命中率来验证策略的有效性,必要时再调整。
