Redis 缓存替换策略:8 种淘汰策略与 LRU 实现剖析

文章目录

  • 引言
  • 缓存容量该设多大
  • [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 中代价太高:

  1. 维护一个全局链表,需要额外的内存空间
  2. 每次访问都要移动链表节点,高频访问下大量链表操作会拖慢 Redis

为了不让淘汰机制本身拖累 Redis 性能,Redis 实现了近似 LRU

近似 LRU 的实现

Redis 的近似 LRU 通过两点优化:

第一,每个数据对象(RedisObject)记录一个 lru 字段,存放最近一次访问的时间戳。

不再需要全局链表,只需要每个对象自带一个时间戳,访问时更新即可。

第二,淘汰时随机采样,从样本中挑出最旧的。

需要淘汰数据时:

  1. 第一次随机选 N 个数据作为候选集合
  2. 比较 N 个数据的 lru 字段值,淘汰最小的(即最久未访问的)
  3. 后续淘汰时,新进入候选集合的数据 lru 值必须小于集合中现有的最小 lru 值
  4. 候选集合满了之后,淘汰其中 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 缓存替换的关键点:

  1. 缓存容量建议设置为总数据量的 15% 到 30%,结合数据访问规律调整
  2. 8 种淘汰策略中,noeviction 不淘汰数据,不适合缓存;volatile-* 只淘汰带过期时间的,allkeys-* 淘汰所有数据
  3. Redis 的 LRU 是近似实现,通过每对象记录 lru 字段 + 随机采样的方式,避免维护全局链表
  4. Redis 不会主动回写脏数据,应用层必须保证数据修改时同步到数据库

策略选择没有标准答案,要结合业务的数据访问特征、是否有置顶需求、以及对一致性的要求综合权衡。配置完成后,建议通过监控缓存命中率来验证策略的有效性,必要时再调整。

相关推荐
IT界的老黄牛2 小时前
RocketMQ 4.x 任意秒数延迟消息工程实战:MQ 粗延迟 + Redis 补精度 + MDC 链路透传
redis·rocketmq·事务消息·延迟消息
weixin_523185323 小时前
Java面试高频题:Integer缓存机制与 equals、== 区别
java·缓存·面试
小小龙学IT3 小时前
Go 泛型深度解析:从设计哲学到工程实践
服务器·数据库·golang
weixin_394758033 小时前
CRMEB Pro 商品字段二开:为什么加一个字段会牵动 SKU、缓存和前端展示?
前端·缓存
天行健,君子而铎3 小时前
2026年通用行业数据分类分级产品排名——聚焦成本低、全链路覆盖与高性能计算的优质选型
大数据·数据库·人工智能
Tong Z4 小时前
Mysql DDL中的ALGORITHM
数据库·mysql
电商API_180079052474 小时前
Python 实现闲鱼商品列表批量采集,接口异常重试机制搭建
大数据·开发语言·数据库·爬虫·python
焦虑的说说5 小时前
redis和数据库的一致性如何保证
数据库·redis·缓存