【Redis】Redis——过期键删除策略、内存淘汰8种策略、LRU/LFU实现

简介: Redis过期删除与内存淘汰是两大核心内存管理机制:前者按TTL自动清理失效键(惰性+定期组合),后者在`maxmemory`超限时主动淘汰键(8种策略,含LRU/LFU近似实现)。二者目标、触发条件与作用范围截然不同,需精准区分与配置。

Redis——过期键删除、内存淘汰、LRU/LFU实现


一、核心概念前置区分(90%使用者的混淆点)

特性 过期键删除策略 内存淘汰策略
核心目标 清理已到过期时间的键,释放无效内存 当Redis内存达到maxmemory上限时,主动选键删除,保障写入可用
触发条件 键设置了过期时间且已到期,与内存使用率无强制关联 Redis已用内存 ≥ 配置的maxmemory上限,与键是否过期无强制关联
执行优先级 前置执行,是内存管控的第一道防线 后置兜底,过期键清理后内存仍超限才会触发
处理范围 仅处理设置了过期时间的键 可配置为处理全量键/仅过期键

二、Redis 过期键删除策略

2.1 过期键的底层存储

Redis在每个数据库redisDb结构中,通过expires字典专门存储键的过期时间:

  • 字典key:指向数据库键的指针(无额外内存冗余)
  • 字典value:键的过期时间戳(毫秒级Unix时间戳)
  • 优势:判断键是否过期、获取过期时间的时间复杂度为O(1)

2.2 行业通用的3种过期删除策略

策略 核心逻辑 优点 核心缺陷
定时删除 设置过期时间时同步创建定时器,到期立即删除键 内存极致友好,到期立即释放内存 CPU极不友好,大量过期键会占用主线程CPU,严重影响Redis响应性能
惰性删除 键到期不主动删除,仅当客户端访问该键时,检查并删除过期键 CPU极致友好,仅访问时才做处理,无额外CPU开销 内存极不友好,大量冷过期键永不被访问,造成内存泄漏
定期删除 每隔固定时间,抽取部分过期键检查,删除其中已到期的键 折中CPU与内存,可通过配置调整执行频率和力度 难以精准平衡,频率过高耗CPU,频率过低内存泄漏

2.3 Redis 实际采用的组合策略(惰性删除 + 定期删除)

Redis单线程架构决定了无法使用定时删除,最终采用惰性删除为基础、定期删除为兜底的组合方案,平衡CPU与内存开销。

2.3.1 惰性删除实现细节

  1. 触发时机:所有访问键的命令(GET/MGET/HGET等)执行前,都会调用expireIfNeeded函数
  2. 核心执行逻辑
    • 检查键是否存在于expires字典,无过期时间直接放行
    • 检查键是否已过期,未过期直接放行
    • 已过期:主库同步/异步删除键(4.0+lazyfree机制对大键异步删除,避免主线程阻塞),并向AOF/从库追加DEL命令,返回nil
  3. 主从一致性约束:从库永远不会主动执行惰性删除,即使键已过期,访问时也仅返回过期状态,必须等待主库同步DEL命令后才会删除(3.2+版本从库访问过期键会返回nil,但仍不执行删除)

2.3.2 定期删除实现细节

  1. 触发频率:由redis.confhz参数控制,默认10,即每秒执行10次,单次间隔100ms;取值范围1-500,值越高清理越及时,CPU开销越大
  2. 自适应执行逻辑
    • 遍历0-15号数据库,逐个库从expires字典随机抽取20个带过期时间的键
    • 检查并删除抽取结果中已过期的键
    • 若本次抽取的键中,过期占比超过25%,立即重复抽取-检查-删除流程
    • 单次执行设置硬时间上限:不超过当前hz周期的25%(如hz=10时,单次最多执行25ms),避免阻塞主线程
    • 内存紧张时自动进入慢模式,提升清理频率和时长

2.4 特殊场景下的过期键处理

  1. RDB持久化:生成RDB文件时,会跳过所有已过期的键;载入RDB时,主库跳过过期键,从库全量载入,等待主库同步DEL命令
  2. AOF持久化:键过期被删除时,会向AOF文件追加一条DEL命令;AOF重写时,会跳过所有已过期的键
  3. 集群模式:仅主节点执行过期键删除,从节点完全同步主节点的DEL命令,保障集群数据一致性

三、Redis 内存淘汰8种策略全解

3.1 内存淘汰的触发条件与核心配置

  1. 触发条件:客户端发送写入类命令时,Redis检查已用内存,若已用内存 ≥ maxmemory配置上限,则触发内存淘汰流程
  2. 核心配置
    • maxmemory:Redis最大可用内存上限,单位字节,生产环境建议设置为物理内存的50%-70%,避免使用swap导致性能暴跌
    • maxmemory-policy:内存淘汰策略配置,默认noeviction
    • maxmemory-samples:淘汰算法采样数,默认5,值越大淘汰越精准,CPU开销越高

3.2 8种淘汰策略的结构化分类与详解

8种策略可分为1种不淘汰策略 + 7种淘汰策略,淘汰策略按「淘汰范围」+「淘汰算法」两个维度组合划分。

3.2.1 不淘汰类策略(1种)

noeviction(默认策略)

  • 核心逻辑:内存达到maxmemory时,所有会导致内存增加的写入命令(SET/INCR/LPUSH等)全部返回OOM错误,仅只读命令(GET/INFO等)可正常执行
  • 适用场景:绝对不允许数据丢失、有独立的降级容灾策略、仅提供只读服务的场景

3.2.2 全键空间淘汰类策略(3种)

淘汰范围:数据库中所有键(无论是否设置过期时间、是否已过期),优先级最高,无键可淘汰时降级为noeviction

策略 核心逻辑 适用场景
allkeys-lru 优先淘汰最近最少使用的键(近似LRU算法) 业务有明显冷热数据区分,80%请求集中在20%热点数据,生产环境最常用的策略
allkeys-lfu 优先淘汰访问频率最低的键(近似LFU算法,4.0+新增) 业务数据访问频率差异极大,如电商爆款、热点新闻,需精准保留高频访问数据,避免冷数据污染缓存
allkeys-random 完全随机淘汰任意键 所有键访问概率完全均等的特殊场景,生产环境极少使用

3.2.3 过期键空间淘汰类策略(4种)

淘汰范围:仅针对设置了过期时间的键(无论是否已到期),若当前库无带过期时间的键,直接降级为noeviction(高频踩坑点)。

策略 核心逻辑 适用场景
volatile-lru 仅在过期键中,优先淘汰最近最少使用的键 需要永久保留核心数据(如配置、用户基础信息),仅允许淘汰带过期时间的缓存数据
volatile-lfu 仅在过期键中,优先淘汰访问频率最低的键(4.0+新增) 既要保留永久核心数据,又需精准淘汰低频访问的缓存数据
volatile-random 仅在过期键中,完全随机淘汰键 极少使用,仅适用于过期键访问概率完全均等的场景
volatile-ttl 仅在过期键中,优先淘汰剩余TTL最短的键 业务有明确的过期优先级,希望优先淘汰即将到期的键

3.3 高频踩坑点与策略选型建议

  1. 高频踩坑点
    • 使用volatile-xxx系列策略时,若库中无带过期时间的键,内存超限后会直接拒绝所有写入
    • 从库不会主动执行内存淘汰,必须与主库配置相同的maxmemory,否则会出现从库OOM
    • 未配置maxmemory时,32位Redis默认最大内存3GB,64位Redis无上限,会耗尽服务器内存触发OOM
  2. 选型优先级建议
    • 首选:allkeys-lru,绝大多数有冷热区分的业务场景最优解
    • 次选:allkeys-lfu,热点数据集中、访问频率差异大的场景
    • 特殊场景:需保留永久键时,选择volatile-lru/volatile-lfu
    • 非特殊需求,禁止使用random/ttl/noeviction策略

四、Redis LRU/LFU 算法深度实现

4.1 核心载体:RedisObject 结构

Redis中每个键值对的value都封装为redisObject,其中24位的lru字段是LRU/LFU算法的核心载体,无需额外数据结构,极致节省内存:

typedef struct redisObject {
   
    unsigned type:4;        // 数据类型
    unsigned encoding:4;    // 编码格式
    unsigned lru:24;        // LRU时间戳 / LFU的ldt+logc
    int refcount;           // 引用计数
    void *ptr;              // 指向实际数据的指针
} robj;

4.2 LRU(Least Recently Used,最近最少使用)算法实现

4.2.1 标准LRU算法原理与缺陷

  1. 核心原理:最近被访问的数据,未来被访问的概率更高;内存不足时,淘汰最久未被访问的数据
  2. 标准实现:哈希表 + 双向链表,哈希表记录键与链表节点的映射,链表按访问时间排序,访问时将节点移至表头,淘汰时删除表尾节点
  3. 核心缺陷
    • 额外内存开销大,需维护双向链表
    • 频繁的链表节点移动操作,单线程下CPU开销高
    • 缓存污染:一次性批量访问的冷数据,会挤出长期热点数据,导致缓存命中率暴跌

4.2.2 Redis 近似LRU实现原理

Redis放弃了严格LRU,采用随机采样+候选池的近似LRU方案,在性能和命中率之间做到极致平衡。

  1. 核心基础redisObject的24位lru字段,存储键最后一次被访问的秒级Unix时间戳(24位可覆盖194天,循环使用)
  2. 执行流程(3.0+优化版)
    • 内存超限触发淘汰时,按maxmemory-samples配置随机采样对应数量的键
    • 维护一个大小为16的候选池,池内按键的lru时间升序排序(最久未访问的在前)
    • 采样的键仅当lru时间早于池内最小时间,或池未满时,才会被加入候选池
    • 从候选池中选出lru时间最早的键,执行淘汰
    • 重复流程,直到内存降至maxmemory以下
  3. 核心优势:无需额外数据结构,内存开销极低;无链表操作,CPU开销极小;maxmemory-samples=10时,命中率已与标准LRU几乎一致

4.2.3 核心配置与优化细节

  • maxmemory-samples:默认5,生产环境追求命中率可调整至10,不建议超过10,避免CPU开销激增
  • 访问优化:键的lru时间与当前时间差小于1秒时,不会更新lru字段,避免频繁更新带来的CPU开销
  • 3.0+候选池优化:解决了旧版本单次采样的局限性,实现跨轮次的键对比,大幅提升淘汰精准度,缓解缓存污染问题

4.3 LFU(Least Frequently Used,最不经常使用)算法实现

4.3.1 标准LFU算法原理与缺陷

  1. 核心原理:访问频率越高的数据,未来被访问的概率越高;内存不足时,淘汰访问次数最少的键
  2. 标准实现:为每个键维护访问计数器,访问时计数+1,淘汰时删除计数最小的键
  3. 核心缺陷
    • 内存开销大,需为每个键维护计数器
    • 无法适配访问模式变化:历史热点数据长期不访问,计数仍居高不下,难以被淘汰
    • 缓存预热问题:新的热点数据刚进入时计数低,极易被淘汰
    • 计数排序开销大,单线程下性能瓶颈明显

4.3.2 Redis 近似LFU实现原理(4.0+新增)

Redis复用redisObject的24位lru字段,拆分为两部分实现LFU,同时通过对数计数器+周期衰减机制解决标准LFU的核心缺陷。

  1. 24位字段拆分
    • 高16位(ldt):Last Decrement Time,单位分钟,记录上一次计数器衰减的时间戳
    • 低8位(logc):Logistic Counter,访问频率计数器,最大值255
  2. 核心机制1:对数增长计数器
    • 解决8位计数器无法区分高频访问的问题,采用基于概率的对数增长逻辑,而非线性增长
    • 核心规则:计数器值越小,增长越快;值越大,增长越慢。lfu_log_factor越大,计数器增长越慢,对访问频率的区分度越高
    • 源码级增长逻辑:每次访问时生成0-1的随机数,仅当随机数小于1/(counter * lfu_log_factor + 1)时,计数器才+1;默认lfu_log_factor=10时,计数器达到255需要百万次以上访问
  3. 核心机制2:周期衰减机制
    • 解决历史热点数据无法淘汰的问题,计数器会随时间周期衰减
    • 核心规则:每次访问/采样时,计算当前时间与ldt的差值,除以lfu_decay_time得到衰减次数,计数器max(counter - 衰减次数, 0),同时更新ldt为当前时间
    • 默认lfu_decay_time=1,即每分钟衰减1次,10分钟未访问的键,计数器至少衰减10
  4. 新键友好优化:新创建的键,计数器初始值为5(LFU_INIT_VAL),而非0,避免新键刚进入就因计数过低被淘汰,解决缓存预热问题
  5. 淘汰执行流程
    • 内存超限触发淘汰时,按maxmemory-samples随机采样对应数量的键
    • 对采样键执行计数器衰减,更新logcldt
    • 将采样键加入候选池,按计数器值升序排序
    • 从候选池中选出计数器值最小的键,执行淘汰
    • 重复流程,直到内存降至maxmemory以下

4.3.3 核心配置与优化细节

  • lfu-log-factor:默认10,取值范围0-100,高频访问场景可适当调大,提升频率区分度
  • lfu-decay-time:默认1,单位分钟,访问模式变化快的场景可适当调小,加快衰减速度,快速适配新的热点数据

4.4 LRU vs LFU 核心对比与选型

维度 近似LRU 近似LFU
核心判断依据 最后一次访问的时间 历史访问频率 + 时间衰减
缓存污染抗性 弱,批量冷数据易挤出热点数据 强,单次访问几乎不会提升计数器,冷数据难以污染缓存
新数据友好度 高,新访问数据直接更新lru时间,不易被淘汰 中,初始值5,需积累访问次数才能避免被淘汰
访问模式适配 适合冷热区分明显、访问模式稳定的场景 适合访问频率差异大、访问模式频繁变化的场景
Redis版本支持 全版本支持 4.0及以上版本支持

五、全体系实战最佳实践

  1. 基础配置必做:必须设置maxmemory,生产环境不超过物理内存的70%;hz参数默认10,高并发场景可调至20-50,禁止超过100
  2. 淘汰策略选型:绝大多数场景首选allkeys-lru;热点集中、访问频率差异大的场景选allkeys-lfu;需保留永久键的场景选volatile-lru/volatile-lfu
  3. 性能优化maxmemory-samples默认5,追求命中率可调至10;开启lazyfree-lazy-eviction/lazyfree-lazy-expire,避免大键删除阻塞主线程
  4. 主从集群规范:主从节点maxmemory、淘汰策略、过期相关配置必须完全一致;禁止在从节点执行大量写入,避免从库OOM
  5. 过期键规范:避免大量键设置相同的过期时间,导致定期删除集中执行阻塞主线程,建议给过期时间增加随机偏移量

六、高频面试考点总结

  1. 请区分Redis过期键删除策略和内存淘汰策略的核心差异
  2. Redis过期键删除为什么采用惰性+定期的组合策略,而不用定时删除?
  3. Redis的8种内存淘汰策略分别是什么,生产环境如何选型?
  4. Redis为什么不实现严格的LRU,而是用近似LRU?3.0+对LRU做了什么优化?
  5. Redis的LFU是如何实现的?如何解决标准LFU的历史热点和缓存预热问题?
  6. 主从架构下,Redis的过期键删除和内存淘汰有什么特殊约束?
  7. 使用volatile-lru策略时,内存满了但没有淘汰任何键,反而拒绝写入,是什么原因?
相关文章
|
6天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23385 5
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
15天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
5401 25
|
11天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
3918 12
|
10天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
3203 10
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
27天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
21295 64
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)