当前位置: 首页 > news >正文

Redis过期策略与内存淘汰机制解析

一、Redis 过期策略详解:如何处理"过期的键"?

Redis 作为高性能键值数据库,支持为键设置过期时间(通过EXPIRE、PEXPIRE等命令)。当键的过期时间到达后,Redis 需要将其从内存中清理,避免无效数据占用资源。但如果直接"实时清理"所有过期键,会严重影响 Redis 性能(比如大量键同时过期时,CPU 会被频繁占用)。因此,Redis 采用了"惰性删除 + 定期删除"的混合策略,在"性能"和"内存占用"之间取得平衡。

1.1 惰性删除(Lazy Expiration):"用的时候再检查"

核心原理

惰性删除的核心逻辑是:不主动监测键是否过期,只有当某个键被访问时(如GET、SETNX等命令),才会检查它是否过期。如果过期,则删除该键并返回nil;如果未过期,则正常返回键值。

执行流程

  1. 客户端发起访问某个键的命令(如GET key);
  2. Redis 先检查该键是否存在过期时间(expires字典中是否有记录);
  3. 若不存在过期时间,直接返回键值;
  4. 若存在过期时间,对比当前时间与过期时间:
    • 若已过期:删除该键(从dict字典和expires字典中移除),返回nil;
    • 若未过期:正常返回键值。

实际应用场景

  • 热点数据访问:经常被访问的键会及时被发现并清理
  • 低频访问数据:如后台日志缓存键可能长期不被发现

优缺点分析

优点

  • 性能优先:无需主动扫描过期键,CPU 资源消耗极低,不影响 Redis 正常读写
  • 实现简单:逻辑清晰,无需复杂的定时任务调度
  • 精准删除:只删除被访问的过期键,不会误删有效键

缺点

  • 内存泄漏风险:如果过期键长期不被访问,会一直占用内存,导致"无效内存膨胀"
  • 可能触发"批量删除":若大量过期键突然被访问(如定时任务批量查询),会集中删除键,短暂阻塞线程
  • 无法保证及时清理:冷数据可能长期驻留内存

1.2 定期删除(Periodic Expiration):"定时批量检查"

为解决惰性删除的"内存泄漏"问题,Redis 引入了定期删除策略:每隔一段时间,主动扫描部分过期键并删除。但为了避免扫描过程占用过多 CPU,Redis 做了"有限度"的扫描 —— 不扫描所有键,只扫描部分过期键。

核心原理

扫描频率

  • 由 Redis 配置hz控制(默认hz=10,即每秒扫描 10 次)
  • hz值越大,扫描频率越高,过期键清理越及时,但 CPU 占用也越高
  • 建议生产环境不超过hz=50,需要根据实际负载调整

扫描范围

  1. 每次扫描时,Redis 会:
    • 从expires字典(存储所有带过期时间的键)中随机抽取N个键(N默认是 20)
    • 检查这N个键是否过期,若过期则删除
  2. 若删除的键数占比超过25%(即删除了超过 5 个键),则继续重复上述步骤(再抽 20 个键检查)
  3. 若占比≤25%,则停止本次扫描,等待下一次hz触发

为什么要"停止扫描"?

这是 Redis 的"保护机制":如果某一时刻有大量过期键(如缓存雪崩场景),若无限循环扫描,会导致 Redis 线程一直阻塞在"删除过期键"的逻辑中,无法处理客户端请求。通过"删除占比≤25% 则停止"的规则,既能清理部分过期键,又能保证 Redis 的正常服务。

配置建议

# redis.conf配置示例
hz 10            # 默认值,每秒扫描10次
maxmemory-policy volatile-lru # 配合内存淘汰策略

优缺点分析

优点

  • 平衡内存与性能:定期清理过期键,减少无效内存占用,同时通过"有限扫描"控制 CPU 消耗
  • 避免批量阻塞:通过"25% 占比停止"规则,防止扫描逻辑长时间阻塞线程
  • 渐进式清理:分批次处理过期键,避免一次性大负载

缺点

  • 仍有漏网之鱼:无法保证所有过期键都被及时清理(比如未被扫描到的过期键,仍需等待惰性删除)
  • 扫描频率难把控:hz值设置过小,清理不及时;设置过大,CPU 占用过高
  • 随机性影响效果:随机抽样的方式可能导致某些键长期不被检查

1.3 两种策略的协同工作流程

惰性删除和定期删除并非独立存在,而是协同工作,形成 Redis 过期键处理的完整逻辑:

  1. 日常读写:依赖惰性删除,确保访问时只返回有效数据,同时避免主动 CPU 消耗
  2. 后台清理:依赖定期删除,每隔一段时间批量清理部分过期键,缓解内存膨胀问题
  3. 极端场景:若定期删除未清理的过期键,且长期不被访问,仍会占用内存 —— 这时候需要依赖"内存淘汰机制"进一步处理

典型工作流程示例

  1. 键A设置10秒过期
  2. 5秒后被访问:惰性删除检查未过期,正常返回
  3. 12秒时定期删除扫描到键A并删除
  4. 若键A未被定期扫描到,15秒时被访问:惰性删除发现已过期,立即删除
  5. 若键A既未被扫描也未被访问,最终会被内存淘汰机制处理

二、Redis 内存淘汰机制:内存不足时该删谁?

当 Redis 的内存使用达到配置的 maxmemory(最大内存限制)时,再写入新键会触发内存淘汰机制:Redis 会根据预设的规则,删除部分数据,腾出内存空间供新键使用。这个机制是 Redis 作为内存数据库的关键特性之一,它确保了 Redis 在内存有限的情况下仍能继续工作。

2.1 前置条件:开启内存限制

内存限制的重要性

首先需要明确:内存淘汰机制仅在 maxmemory 配置生效时触发。若未设置 maxmemory(默认无限制),Redis 会一直使用内存,直到操作系统触发内存溢出(OOM),导致 Redis 进程被杀死。这种情况会导致服务突然中断,可能造成严重的数据丢失和服务不可用。

生产环境配置建议

生产环境必须配置 maxmemory,建议设置为物理内存的 70%-80%(例如 8GB 内存的机器,maxmemory=6GB),避免 Redis 占用过多内存影响其他进程。这个比例考虑了:

  1. 操作系统正常运行所需的内存
  2. Redis 子进程(如 RDB 持久化或 AOF 重写)需要的额外内存
  3. 其他系统进程的内存需求

配置方式

在 redis.conf 文件中配置:

# 单位:字节(也可加单位,如1gb、512mb)
maxmemory 6442450944  # 6GB的示例

或者在运行时通过命令配置:

CONFIG SET maxmemory 6gb

2.2 8 种内存淘汰策略:从 "不淘汰" 到 "智能淘汰"

Redis 4.0+ 共支持 8 种内存淘汰策略,可通过 maxmemory-policy 配置(默认 noeviction)。根据淘汰逻辑,可分为 4 类:

第一类:不淘汰(仅拒绝写入)

策略名称:noeviction(默认)

逻辑

  • 当内存达到 maxmemory 时,拒绝所有新键的写入请求(返回 OOM command not allowed)
  • 允许读取和删除现有键
  • 不会删除任何数据

适用场景

  • 不允许数据丢失的场景(如 Redis 作为持久化数据库,而非缓存)
  • 当数据比可用性更重要时
  • 此时宁愿拒绝写入,也不删除已有数据

示例

127.0.0.1:6379> SET new_key "value"
(error) OOM command not allowed when used memory > 'maxmemory'.

第二类:淘汰 "过期键"(仅删除带过期时间的键)

这类策略仅针对设置了 expire 的键进行淘汰,未设置过期时间的键不会被删除,适合 "缓存场景"(过期键本身就是临时数据)。

策略名称淘汰逻辑适用场景示例说明
volatile-ttl淘汰剩余过期时间最短的键(TTL 最小的键)希望尽快淘汰即将过期的数据,保留仍有较长有效期的数据键A(剩余5分钟)、键B(剩余10分钟)→淘汰键A
volatile-random从所有带过期时间的键中,随机淘汰一个对淘汰的数据没有优先级要求,追求简单高效从100个过期键中随机选一个删除
volatile-lru从所有带过期时间的键中,淘汰最近最少使用(LRU)的键缓存场景的经典策略:认为"最近不用的键,未来也大概率不用"键A(1天前访问)、键B(1小时前访问)→淘汰键A
volatile-lfu从所有带过期时间的键中,淘汰最近最少频率使用(LFU)的键比 LRU 更精准:不仅考虑"是否最近使用",还考虑"使用频率"键A(过去1周访问5次)、键B(过去1周访问20次)→淘汰键A

第三类:淘汰 "所有键"(无论是否带过期时间)

这类策略会删除所有键(包括未设置过期时间的键),适合 Redis 完全作为 "缓存" 使用,且允许所有数据被淘汰的场景。

策略名称淘汰逻辑适用场景示例说明
allkeys-random从所有键中(无论是否过期),随机淘汰一个对数据没有访问优先级,追求极致性能(随机淘汰的开销最低)从所有1000个键中随机删除一个
allkeys-lru从所有键中,淘汰最近最少使用(LRU)的键通用缓存场景:假设"最近访问的键,未来更可能被访问"键A(3天前访问)、键B(2小时前访问)→淘汰键A
allkeys-lfu从所有键中,淘汰最近最少频率使用(LFU)的键高频访问场景(如热点商品缓存):频率高的键更值得保留,比 LRU 更贴合实际访问模式键A(过去访问100次)、键B(过去访问10次)→淘汰键B

策略选择建议

  1. 如果 Redis 仅用作缓存:allkeys-lru 或 allkeys-lfu
  2. 如果部分数据不能丢失:volatile-* 策略
  3. 对性能要求极高:random 策略
  4. 数据完全不能丢失:noeviction

2.3 关键概念:LRU 与 LFU 的区别

LRU(Least Recently Used,最近最少使用)和 LFU(Least Frequently Used,最近最少频率使用)是两种最常用的淘汰算法,也是面试高频考点,必须明确区别:

LRU:基于 "访问时间"

核心逻辑

  • 判断键的 "最后一次访问时间"
  • 淘汰最后一次访问时间最早的键(即 "最久没被用" 的键)
  • Redis 使用近似 LRU 算法,通过采样来提高性能

示例

  • 键 A(10:00 访问)、键 B(10:05 访问)、键 C(10:10 访问)
  • 内存不足时,淘汰键 A

优点

  • 实现简单
  • 对大多数缓存场景表现良好

缺点

  • 无法区分 "高频但最近未用" 的键
  • 例如:一个每天访问 100 次的键,因凌晨 1 小时未访问,可能被淘汰
  • 而一个每天访问 1 次但最近刚访问的键被保留 —— 这不符合实际需求

LFU:基于 "访问频率"

核心逻辑

  • 为每个键维护一个 "访问计数器",记录一段时间内的访问次数
  • 淘汰计数器最小的键(即 "用得最少" 的键)
  • Redis 4.0 引入

示例

  • 键 A(计数器 100)、键 B(计数器 10)、键 C(计数器 5)
  • 内存不足时,淘汰键 C

优化

  • Redis 的 LFU 会 "衰减" 计数器(如每隔一段时间,计数器乘以 0.8)
  • 避免 "历史高频但当前低频" 的键一直被保留
  • 例如一个键过去高频,但最近 1 个月未使用,计数器会逐渐降低,最终被淘汰

优点

  • 更精准反映实际访问模式
  • 特别适合有热点数据的场景

缺点

  • 实现复杂度更高
  • 占用更多内存(需要维护计数器)

总结:LRU vs LFU

维度LRULFU
核心依据访问时间(最近)访问频率(次数)
适用场景访问模式较稳定,无明显高频键存在明显高频访问的场景(如热点数据)
精准度较低(易误淘汰高频但最近未用的键)较高(更贴合实际访问需求)
实现复杂度简单复杂
内存开销较大
Redis 版本所有版本4.0+

2.4 内存淘汰的执行流程

当客户端发起写入请求(如 SET、HMSET),且 Redis 内存已达 maxmemory 时,执行流程如下:

  1. 内存检查

    • Redis 检查当前内存是否超过 maxmemory
    • 若未超过,正常执行写入
    • 若已超过,进入淘汰流程
  2. 策略选择

    • 根据 maxmemory-policy 选择对应的淘汰策略
    • 如果策略是 noeviction,直接返回 OOM 错误
  3. 数据淘汰

    • 按照策略选择候选键
    • 删除选中的键
    • 可能需要重复此步骤直到释放足够内存
  4. 执行写入

    • 腾出足够内存后
    • 执行客户端的写入请求

注意事项

  • 删除数据时,Redis 会触发 "键删除事件"(如 DEL 命令的钩子)
  • 如果配置了持久化(RDB/AOF),会同步更新持久化文件(确保数据一致性)
  • 淘汰过程是同步的,会阻塞当前客户端请求直到完成
  • 在大数据量情况下,淘汰可能影响性能

性能优化建议

  1. 合理设置 maxmemory,避免频繁触发淘汰
  2. 对于大内存实例,考虑使用 allkeys-random 减少淘汰开销
  3. 监控 evicted_keys 指标(通过 INFO 命令),了解淘汰频率
  4. 在写入高峰前预热缓存,减少淘汰压力

三、生产环境配置建议

3.1 过期策略相关配置

hz 参数配置优化

Redis 默认的 hz 值为 10,表示每秒执行 10 次后台任务。建议根据业务场景调整:

  1. 缓存为主场景

    • 典型场景:电商商品缓存、社交平台热点数据缓存
    • 当业务中存在大量过期键(如 TTL<24h 的临时数据)时,建议将 hz 调整为 20-30
    • 效果:加快过期键清理速度,减少内存碎片(实测可降低内存占用 5-15%)
  2. 持久化存储场景

    • 典型场景:用户会话存储、订单状态存储
    • 若服务器 CPU 利用率常高于 70%,建议保持 hz=10
    • 警告:禁止设置 hz>50,测试显示当 hz=100 时 CPU 占用可能飙升 300%

过期时间分散策略

批量设置过期键时,建议采用时间偏移算法:

# 示例:基础过期时间3600秒,随机增加0-600秒偏移
expire_time = 3600 + random.randint(0, 600)
redis_client.expire("key", expire_time)

问题规避:某社交平台曾因百万级键在同一秒过期,导致请求延迟从 2ms 飙升至 1200ms

3.2 内存淘汰相关配置

maxmemory 设置规范

部署方式计算公式示例(总内存)建议值
物理机总内存×75%8GB → 6GB留出25%给系统/kernel
Docker容器限制×80%4GB限制 → 3.2GB需考虑监控组件开销

淘汰策略选型指南

  1. 缓存场景

    • allkeys-lfu:适用于有20%热点数据(如短视频热门内容)
    • allkeys-lru:适合均匀访问(如企业OA系统文档)
    • 性能对比:LFU 比 LRU 多消耗约 5% CPU,但命中率高 8-12%
  2. 持久化场景

    • noeviction 必须配合监控告警,当内存达阈值时:
      # 紧急处理命令示例
      redis-cli --bigkeys
      redis-cli --hotkeys
      

  3. 特殊场景

    • 测试环境建议 allkeys-random,某金融测试显示淘汰速度比 LRU 快 40%

3.3 监控与调优

关键监控指标

# 过期键监控(建议设置<1000/秒)
redis-cli INFO stats | grep -E "expired_keys|expire_cycle_cpu"# 内存健康检查(建议每周一次)
redis-cli INFO memory | grep -E "used_memory|peak|fragmentation"

调优决策矩阵

指标异常可能原因解决方案
expired_keys突增批量键同时过期增加时间随机偏移
内存碎片率>1.5频繁修改大key重启实例或使用jemalloc
命中率<90%策略不当/内存不足切换LFU或扩容20%

实战案例:某游戏公司通过调整 hz=25 + allkeys-lfu,使缓存命中率从 88% 提升至 96%,月节省 CDN 费用 $12k

四、常见问题与解决方案

Q1:为什么设置了过期时间的键,过期后仍能被访问到?

详细原因分析:

  1. 惰性删除机制延迟

    • Redis采用惰性删除策略,只有当某个键被访问时才会检查其是否过期
    • 示例:一个设置了10秒过期的键,如果之后20秒内都没有被访问过,它就会一直存在于内存中
  2. 定期删除扫描遗漏

    • Redis默认每100ms(HZ=10)随机抽取部分键检查过期情况
    • 当数据库键数量庞大时,可能存在扫描遗漏的情况
    • 实际测试案例:在100万键的环境中,过期键可能滞留30秒以上
  3. 主从同步特性

    • 从节点不会主动删除过期键,依赖主节点的DEL命令同步
    • 网络延迟或同步异常时,从节点可能保留过期键长达数分钟
    • 典型场景:主节点宕机切换时,新主节点可能包含原主节点未同步的过期键

优化解决方案:

  1. 主动维护方案

    • 定期执行SCAN+TTL命令扫描过期键
    • 使用Redis的CONFIG SET hz 100提高定期删除频率(需权衡CPU消耗)
  2. 架构层面改进

    • 对从节点设置replica-serve-stale-data no,避免返回过期数据
    • 考虑使用Redis Cluster,分散键空间压力
  3. 监控预警

    • 通过INFO命令监控expired_keys指标异常增长
    • 设置内存使用率超过80%的告警阈值

Q2:内存淘汰时,Redis 会阻塞客户端请求吗?

阻塞机制深度解析:

  1. 不同淘汰策略的阻塞时间

    策略类型阻塞时间关键影响因素
    allkeys-random10-50μs哈希表查找速度
    volatile-lru1-5ms采样数量(pool-size)
    allkeys-lfu2-10ms计数器衰减频率
  2. 极端情况分析

    • 当内存超限超过50%时,Redis可能需要进行多轮淘汰
    • 测试数据:100万键的环境下,allkeys-lru策略可能导致100ms级别的阻塞
  3. 内核参数影响

    • Linux的Transparent Huge Pages(THP)会导致更大的阻塞波动
    • 建议设置echo never > /sys/kernel/mm/transparent_hugepage/enabled

生产环境优化建议:

  1. 策略选择优先级

    • 首选:allkeys-lfu(Redis 4.0+)
    • 次选:volatile-ttl(如果有明确的过期时间设置)
    • 避免:noeviction(可能导致写操作完全阻塞)
  2. 参数调优方案

    # 设置内存淘汰阈值(建议物理内存的70-80%)
    config set maxmemory 16gb
    # 调整LRU采样精度(默认5,可提升至10)
    config set maxmemory-samples 10
    

  3. 监控指标

    • evicted_keys:监控淘汰键数量突变
    • blocked_clients:关注阻塞客户端数量

Q3:LFU 策略的计数器如何衰减?

计数器工作机制详解:

  1. 衰减算法实现

    • 衰减公式:new_counter = current_counter * 0.8
    • 特殊处理:计数器最大值为255,衰减后小于5则直接置0
    • 源码示例(简化版):
      if (counter > 0) {counter = (counter * 4) / 5; // 等价于乘以0.8if (counter < 5) counter = 0;
      }
      

  2. 访问频率权重计算

    • 新建键初始计数器=5(避免立即被淘汰)
    • 每次访问时的增长公式:
      增量 = 1 / (0.1 * counter + 1)
      新值 = min(当前值 + 增量, 255)
      

  3. 实际应用场景

    • 热点数据:频繁访问的计数器可维持在200+
    • 温数据:周期性访问的计数器在50-100波动
    • 冷数据:计数器会快速衰减至0

运维管理建议:

  1. 监控调优方案

    # 查看LFU相关统计信息
    redis-cli --lfu-stats
    # 手动触发计数器衰减
    redis-cli INFO | grep lfu
    

  2. 参数调整方法

    # 调整计数器对数因子(默认10,数值越大衰减越快)
    config set lfu-log-factor 12
    # 调整计数器初始值(默认5)
    config set lfu-decay-time 1
    

  3. 最佳实践

    • 对突发流量场景,可适当降低lfu-log-factor
    • 对长期稳定业务,建议保持默认参数
    • 避免频繁执行INFO命令,建议间隔至少60秒
http://www.dtcms.com/a/450028.html

相关文章:

  • Transformer(四)---解码器部分实现、输出部分实现及模型搭建
  • 网站开发毕业设计任务书注册推广赚钱一个30元
  • 邯郸网络广播电视台北京网站seo技术厂家
  • leetcode 695 岛屿的最大面积
  • LLaVA-NeXT-Interleave论文阅读
  • 邢台企业网站制作公司中建国际建设有限公司网站
  • 长春火车站防疫要求做网站都要用到框架吗
  • 集合进阶 - HashMap 篇
  • 从原图到线图再到反推:网络图几何与拓扑的结合分析
  • Lua下载和安装教程(附安装包)
  • JAVA实验课程第五次作业分析与代码示例
  • 龙口网站制作公司深圳知名设计公司有哪些
  • 网站数据修改网页界面设计的起源
  • 东莞建设网站官网住房和城乡wordpress 如何修改like和dislike
  • Gopher二次编码原因解析
  • 【ARM汇编语言基础】-数据处理指令(七)
  • 汇编与反汇编
  • 福州建设网站shopee怎么注册开店
  • 建立网站站点的目的贵州二级站seo整站优化排名
  • 阳江做网站多少钱企业网站推广方法有哪些
  • sm2025 模拟赛11 (2025.10.5)
  • python镜像源配置
  • 4.寻找两个正序数组的中位数-二分查找
  • 理解CC++异步IO编程:Epoll入门
  • wordpress房屋网站模板微信小程序
  • 阿里网站建设视频教程WordPress云媒体库
  • SpringCloud 入门 - Nacos 配置中心
  • Windows 下使用 Claude Code CLI 启动 Kimi
  • 网站推广的基本方式抖音特效开放平台官网
  • 湖南网站排名wordpress插件seo