Redis Key过期策略

概述

 Redis的Key过期策略是其内存管理系统的核心组成部分,主要包括「被动过期」、「主动过期」和「内存淘汰」三个机制。其中「内存淘汰」相关内容已经在上一篇「Redis内存淘汰策略」中进行了详细的讲解,有信兴趣的同学可以在回顾上一篇文章。本文将着重讲解Redis的Key过期策略,从实现原理、工作流程到最佳实践进行全面解析。


Redis Key过期策略

 Redis采用多策略组合的方式管理Key过期,主要包括三种机制:定时删除惰性删除定期删除
 下面将分别对这三种key过期机制进行实现原理、优缺点、Redis采用的策略、以及RDB/AOF对不同过期策略的处理上的讲解。

定时删除

实现原理
  在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key过期时间来临时,对key进行删除。
优点
  1. 内存友好,可以及时清除过期的数据
缺点
  1. cpu不友好,会占用大量cpu资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  2. 定时器的创建耗时,若为每一个设置过期时间key创建一个定时器(将会产生大量的定时器),性能影响严重。
  3. Redis未采用此策略。


惰性删除

实现原理
  只有当访问key时,才会判断该key是否已经是过期(触发expireIfNeeded()检查),过期则清除。
优点
  1. cpu友好,零额外开销
  2. 删除操作精准
缺点
  1.内存泄漏风险:不访问的过期Key永远存在。极端情况下可能会出现大量过期key没有再被访问,从而不会被删除,占用大量无效内存。


定期删除

实现原理
 每隔一定时间,扫描一定数量在数据库中expires字典(过期字典)中一定数量的key,并清除其中已过期的key。

工作机制
  1. 周期性执行activeExpireCycle()
  2. 随机抽取部分过期字典中的Key检查
  3. 采用自适应算法控制CPU消耗

核心算法

def activeExpireCycle():
   while True:
    # 每次随机检查20个Key
    for i in range(20):
        key = random.choice(expires_dict)
        if key.expire_time < now:
            delete_key(key)
    # 动态退出条件
    if checked_keys < 20*25%:  # 过期率<25%则退出
        break

优点
  1.该策略是前两者的一个折中方案,通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得cpu和内存资源达到最优的平衡效果。
  2.通过限制删除的时长和频率,来减少删除操作对cpu时间的占用 --- 解决「定时删除」的缺点
  3.定期删除过期key --- 解决「惰性删除」的缺点

缺点
  1.在内存友好方面,不如「定时删除」。
  2.在cpu时间友好方面,不如「惰性删除」。

难点
  1.合理设置操作的执行时长(每次删除执行多长时间)和执行频率(每隔多久时间做一次删除),这个需要各个业务场景根据实际情况进行动态调整。


Redis采用的策略

 Redis采用「惰性删除」 + 「定期删除」的混合策略

混合策略架构

混合策略架构.png

「惰性删除」 + 「定期删除」策略

惰性删除流程
  1.在进行get或setnx等操作时,先检查key是否过期。
  2.若过期,则删除key,然后执行相应操作。
  3.若没过期,则直接执行相应操作。

定期删除流程
  1.遍历每个数据库(即redis.conf中配置的“database”数量,默认为16)。
  2.检查当前库中指定个数key(默认是每个库检查20个key,相当于循环执行20次)。
   a. 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历。
   b. 随机获取一个设置了过期时间的key,检查key是否过期,如过期,删除key。
   c. 判断定期删除操作是否已经达到指定时长,若达到,则退出定期删除。
注意事项
  定期删除,在程序中有一个全局变量current_db来记录下一个将要便利的库。假设有16个库,这一次定期删除遍历了10个,那此时的current_db就是11,下一次定期删除就从第 11库开始遍历。


持久化对过期策略的处理

RDB对过期策略的处理

说明:过期key对RDB没有影响
原理
1.从内存持久化数据到RDB文件
主节点
  a. 生成RDB快照时会主动过滤已过期的Key。
  b. 仅持久化未过期的Key-value对。
从节点
  a. 即使接收到的RDB文件包含未实际过期的Key。
  b. 加载时仍会执行过期检查(双重保障)。

关键源码

// rdb.c 源码关键逻辑
int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
    if (expiretime != -1 && expiretime < mstime()) {
        return 0; // 跳过已过期的Key
    }
    // 保存未过期的Key...
}

2.从RDB文件恢复数据到内存
全量加载流程:
  a. 清空当前数据库
  b. 解析RDB文件内容
  c. 对每个Key执行expireIfNeeded()检查
  c. 仅加载未过期的Key


AOF对过期策略的处理

说明:过期key对AOF没有影响
原理
1.从内存持久化数据到AOF文件

正常AOF追加模式
  a. AOF会将对应Key的操作一并写入文件中

操作类型 AOF记录内容
Key设置过期 PEXPIREAT key timestamp
Key自然过期 DEL key(实际由propagateExpire()生成)
手动删除 直接记录DEL key

AOF重写时
  a. 重写时,会先判断Key是否过期,已过期的Key不会重写到AOF文件

与RDB的区别
  a. 仍会记录Key的过期时间(PEXPIREAT)
  b. 但实际数据只有未过期Key会被写入


复制场景下的特殊处理

主从复制流程

节点角色 过期Key处理方式
主节点 惰性删除+定期删除
从节点 仅依赖主节点的DEL同步

关键机制:
  a. 主节点过期后会向从节点传播DEL命令
  b. 从节点不会主动删除Key(即使已过期)
  c. 3.2+版本引入replica-ignore-expire配置(默认关闭)


数据一致性风险

场景:
  1. Key在主节点过期但尚未同步到从节点
  2. 此时主节点宕机,从节点晋升后:
  3. RDB加载:会重新检查过期
  4. AOF回放:依赖记录的DEL命令


总结对比表

持久化方式 生成时处理 加载时处理 复制传播
RDB 过滤过期Key 二次检查 全量同步
AOF 记录DEL命令 按序重放 增量同步
混合模式 RDB部分过滤 组合加载 混合同步
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容