秒杀场景必须用allkeys-lfu,因其基于带衰减的访问频次淘汰,能守住预热后的爆款key;allkeys-lru依赖最近访问时间,易致冷数据误续命、爆款反复加载,引发缓存雪崩。

Redis 的 allkeys-lru 和 allkeys-lfu 不是“选哪个更好”,而是“哪个更贴合你的访问模式”。LRU 看的是「谁最久没被碰过」,LFU 看的是「谁被碰得最少」——两者底层逻辑不同,误配会导致缓存命中率断崖式下跌。
LRU 实际上是近似算法,不是链表实现
很多人以为 Redis 的 LRU 是用双向链表维护访问顺序,其实不是。它只在每个 redisObject 结构里存了一个 24-bit 的 lru 字段,记录最后一次访问的时间戳(单位是分钟级,精度有限)。淘汰时,Redis 随机采样 maxmemory-samples 个 key(默认 5),从中挑出 lru 值最小的那个淘汰。
- 这意味着:即使某个 key 真的很久没访问,如果没被抽中,就不会被淘汰;反之,刚被抽中的冷 key 可能当场被踢
-
maxmemory-samples越大(比如调到 10 或 20),越接近理想 LRU,但 CPU 开销上升;太小(如设为 1)就接近随机淘汰 - 对周期性访问敏感:比如每天凌晨 3 点刷一次的报表 key,在 LRU 下容易被误判为冷数据而挤出缓存
LFU 的计数器会衰减,不是简单累加
LFU 的 lru 字段在 LFU 模式下被复用:高 16 位存访问频次(log 计数,防溢出),低 8 位存上次衰减时间戳。每次访问 key 时,频次按对数增长(避免小 key 占满计数器);每隔一段时间(默认 1 分钟),Redis 会扫描所有 key,把频次右移(相当于乘以衰减系数),老访问记录自动“淡出”。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 所以 LFU 不是“永远记住高频”,而是“更看重近期高频”——这比静态计数合理得多
- 但如果业务里存在大量一次性扫描(比如后台导出任务遍历几千个 key),这些 key 的频次会被拉高,又因衰减慢,可能长期霸占内存,形成新的缓存污染
- LFU 对
GET、HGET等读命令计数,但对SET、DEL不计——写多读少的场景下,LFU 判定可能失真
volatile-* 和 allkeys-* 的选择比算法本身还关键
真正影响效果的,往往不是 LRU 还是 LFU,而是你选了 volatile-lru 还是 allkeys-lru。前者只淘汰带 TTL 的 key,后者连永不过期的 key 也一起扫。
- 如果你用 Redis 同时存「会话(带 TTL)」和「全局配置(永不过期)」,选
volatile-lru就安全;选allkeys-lru可能把配置干掉,服务直接崩 - 反过来,如果你所有 key 都设了 TTL(比如统一 30 分钟),那
volatile-lfu和allkeys-lfu行为几乎一样,但前者语义更清晰 -
volatile-ttl看似简单,但在 TTL 批量设置不均(有的 10s,有的 2h)时,容易提前清掉本该保留的热 key
怎么验证你选对了策略?别只看命中率
缓存命中率上升 ≠ 策略生效。真正要盯的是「被淘汰 key 的后续访问频率」:如果被踢掉的 key 在 1 小时内又被频繁查,说明策略在误杀。
- 用
redis-cli --stat观察evicted_keys每秒增量,结合业务峰值看是否集中爆发 - 开启
CONFIG SET notify-keyspace-events KEA,监听__keyevent@0__:evict事件,记录被踢 key 的类型和 TTL - 对比
INFO stats中的expired_keys和evicted_keys:如果前者远大于后者,说明过期机制已足够,根本不需要淘汰策略
复杂点在于:LRU/LFU 的效果高度依赖真实流量分布,而线上访问模式常随活动、爬虫、定时任务剧烈波动。一个在压测环境表现完美的配置,上线后可能因某次运营推送全盘失效——所以必须留出监控和快速回切能力,而不是指望一配永逸。

















