• 【云原生进阶之PaaS中间件】第一章Redis-1.4过期策略


    1 设置带过期时间的 key

    1. # 时间复杂度:O(1),最常用方式
    2. expire key seconds
    3. # 字符串独有方式
    4. setex(String key, int seconds, String value)

            除了string独有设置过期时间的方法,其他类型都需依靠expire方法设置时间,若:

    • 未设置时间,则缓存永不过期
    • 设置过期时间,但之后又想让缓存永不过期,使用persist

            设置key的过期时间。超时后,将会自动删除该key。在Redis的术语中一个key的相关超时是volatile的。

            生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆写(overwrite)。这意味着,如果一个命令只是修改(alter)一个带生存时间的 key 的值而不是用一个新的 key 值来代替(replace)它的话,那么生存时间不会被改变。 如使用 INCR 递增key的值,执行 LPUSH 将新值推到 list 中或用 HSET 改变hash的field,这些操作都使超时保持不变。

    • 使用 PERSIST 命令可以清除超时,使其变成一个永久key;
    • 若 key 被 RENAME 命令修改,相关的超时时间会转移到新key;
    • 若 key 被 RENAME 命令修改,比如原来就存在 Key_A,然后调用 RENAME Key_B Key_A 命令,这时不管原来 Key_A 是永久的还是设为超时的,都会由Key_B的有效期状态覆盖;

    注意,使用非正超时调用 EXPIRE/PEXPIRE 或具有过去时间的 EXPIREAT/PEXPIREAT 将导致key被删除而不是过期(因此,发出的key事件将是 del,而不是过期)。

    1.1 刷新过期时间

            对已经有过期时间的key执行EXPIRE操作,将会更新它的过期时间。有很多应用有这种业务场景,例如记录会话的session。

    1.2 Redis 之前的 2.1.3 的差异

            在 Redis 版本之前 2.1.3 中,使用更改其值的命令更改具有过期集的密钥具有完全删除key的效果。由于现在修复的复制层中存在限制,因此需要此语义。

            EXPIRE 将返回 0,并且不会更改具有超时集的键的超时。

    1.3 返回值

    • 1,如果成功设置过期时间。
    • 0,如果key不存在或者不能设置过期时间。

    1.4 示例

            假设有一 Web 服务,对用户最近访问的最新 N 页感兴趣,这样每个相邻页面视图在上一个页面之后不超过 60 秒。从概念上讲,可以将这组页面视图视为用户的导航会话,该会话可能包含有关ta当前正在寻找的产品的有趣信息,以便你可以推荐相关产品。

            可使用以下策略轻松在 Redis 中对此模式建模:每次用户执行页面视图时,您都会调用以下命令:

    1. MULTI
    2. RPUSH pagewviews.user: http://.....
    3. EXPIRE pagewviews.user: 60
    4. EXEC

            如果用户空闲超过 60 秒,则将删除该key,并且仅记录差异小于 60 秒的后续页面视图。此模式很容易修改,使用 INCR 而不是使用 RPUSH 的列表。

    1.5 带过期时间的 key

            通常,创建 Redis 键时没有关联的存活时间。key将永存,除非用户以显式方式(例如 DEL 命令)将其删除。

            EXPIRE 族的命令能够将过期项与给定key关联,但代价是该key使用的额外内存。当key具有过期集时,Redis 将确保在经过指定时间时删除该key。

            可使用 EXPIRE 和 PERSIST 命令(或其他严格命令)更新或完全删除生存的关键时间。

    1.6 过期精度

            在 Redis 2.4 中,过期可能不准确,并且可能介于 0 到 1 秒之间。

            Redis 2.6,过期误差从 0 到 1 毫秒。

    1.7 过期和持久化

            过期信息的键存储为绝对 Unix 时间戳(Redis 版本 2.6 或更高版本为毫秒)。这意味着即使 Redis 实例不处于活动状态,时间也在流动。

            要使过期工作良好,必须稳定计算机时间。若将 RDB 文件从两台计算机上移动,其时钟中具有大 desync,则可能会发生有趣的事情(如加载时加载到过期的所有key)。

            即使运行时的实例,也始终会检查计算机时钟,例如,如果将一个key设置为 1000 秒,然后在将来设置计算机时间 2000 秒,则该key将立即过期,而不是持续 1000 秒。

    2 过期策略

            Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。你可以想象 Redis 内部有一个死神,时刻盯着所有设置了过期时间的 key,寿命一到就会立即收割。

      你还可以进一步站在死神的角度思考,会不会因为同一时间太多的 key 过期,以至于忙不过来。同时因为 Redis 是单线程的,收割的时间也会占用线程的处理时间,如果收割的太过于繁忙,会不会导致线上读写指令出现卡顿。在过期这件事上,Redis 非常小心。

    2.1 过期 key 集合

      redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key,所谓惰性策略就是在客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。定时删除是集中处理,惰性删除是零散处理。

    2.2 定时扫描策略

      Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是采用了一种简单的贪心策略。

    1. 从过期字典中随机 20 个 key;
    2. 删除这 20 个 key 中已经过期的 key;
    3. 如果过期的 key 比率超过 1/4,那就重复步骤 1;

      同时,为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

      设想一个大型的 Redis 实例中所有的 key 在同一时间过期了,会出现怎样的结果?

      毫无疑问,Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀疏,才会停止 (循环次数明显下降)。这就会导致这期间线上读写 QPS 下降明显。还有另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的 CPU 消耗。

      这里解析一下,假如单台 Redis 读写请求 QPS 是 10w,也就是每个请求需要 0.00001s 来完成,每秒执行十次过期扫描,每次过期扫描都达到上限 25ms,那么每秒过期扫描总花费 0.25s,相当于 QPS 降低了 2.5W。

      所以业务开发人员一定要注意过期时间,如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不能全部在同一时间过期。

    1. #在目标过期时间上增加一天的随机时间
    2. redis.expire_at(key, random.randint(86400) + expire_ts)

    2.3 从库的过期策略

      从库不会进行过期扫描,从库对过期的处理是被动的。主库在 key 到期时,会在 AOF 文件里增加一条 del 指令,同步到所有的从库,从库通过执行这条 del 指令来删除过期的 key。

      因为指令同步是异步进行的,所以主库过期的 key 的 del 指令没有及时同步到从库的话,会出现主从数据的不一致,主库没有的数据在从库里还存在。

    2.4 懒惰删除策略

            Redis 为什么要懒惰删除(lazy free)?

            删除指令 del 会直接释放对象的内存,大部分情况下,这个指令非常快,没有明显延迟。不过如果删除的 key 是一个非常大的对象,比如一个包含了千万元素的 hash,又或者在使用 FLUSHDB 和 FLUSHALL 删除包含大量键的数据库时,那么删除操作就会导致单线程卡顿。

            redis 4.0 引入了 lazyfree 的机制,它可以将删除键或数据库的操作放在后台线程里执行, 从而尽可能地避免服务器阻塞。

    2.4.1 unlink指令

            unlink 指令,它能对删除操作进行懒处理,丢给后台线程来异步回收内存。

    1. unlink key
    2. OK

    2.4.2 flush指令

            flushdb 和 flushall 指令,用来清空数据库,这也是极其缓慢的操作。Redis 4.0 同样给这两个指令也带来了异步化,在指令后面增加 async 参数就可以将整棵大树连根拔起,扔给后台线程慢慢焚烧。

    1. flushall async
    2. OK

    2.4.3 异步队列

            主线程将对象的引用从「大树」中摘除后,会将这个 key 的内存回收操作包装成一个任务,塞进异步任务队列,后台线程会从这个异步队列中取任务。任务队列被主线程和异步线程同时操作,所以必须是一个线程安全的队列。 不是所有的 unlink 操作都会延后处理,如果对应 key 所占用的内存很小,延后处理就没有必要了,这时候 Redis 会将对应的 key 内存立即回收,跟 del 指令一样。

    2.5 惰性删除流程

            在进行get或setnx等操作时,先检查key是否过期:

    • 若过期,删除key,然后执行相应操作;
    • 若没过期,直接执行相应操作;

    2.5.1 RDB处理过期key

            过期key对RDB无影响:

    • 从内存数据库持久化数据到RDB文件,持久化key之前,会检查是否过期,过期的key不进入RDB文件;
    • 从RDB文件恢复数据到内存数据库,数据载入数据库之前,会对Key进行过期检查,若过期,不导入数据库(主库情况);

    2.5.2 AOF处理过期Key

            过期key对AOF没有任何影响。

    2.5.2.1 从内存数据库持久化到AOF文件
    • 当key过期后,还没有被删除,此时进行执行持久化操作(该key不会进入aof文件,因为没有发生修改命令)
    • 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
    2.5.2.2 AOF重写

            重写时,会先判断key是否过期,已过期的key不会重写到aof文件

    2.5.2.3 在复制链路和 AOF 文件中处理过期的方式

            为了在不牺牲一致性的情况下获得正确行为,当key过期时,DEL 操作将同时在 AOF 文件中合成并获取所有附加的从节点。这样,过期的这个处理过程集中到主节点中,还没有一致性错误的可能性。

            但是,虽然连接到主节点的从节点不会独立过期key(但会等待来自master的 DEL),但它们仍将使用数据集中现有过期的完整状态,因此,当选择slave作为master时,它将能够独立过期key,完全充当master。

            默认每台Redis服务器有16个数据库,默认使用0号数据库,所有的操作都是对0号数据库的操作

    1. # 设置数据库数量。默认为16个库,默认使用DB 0,可使用"select 1"来选择一号数据库
    2. # 注意:由于默认使用0号数据库,那么我们所做的所有的缓存操作都存在0号数据库上,
    3. # 当你在1号数据库上去查找的时候,就查不到之前set过的缓存
    4. # 若想将0号数据库上的缓存移动到1号数据库,可以使用"move key 1"
    5. databases 16
    • memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)
    • 对于惰性删除而言,并不是只有获取key的时候才会检查key是否过期,在某些设置key的方法上也会检查(eg.setnx key2 value2:该方法类似于memcached的add方法,如果设置的key2已经存在,那么该方法返回false,什么都不做;如果设置的key2不存在,那么该方法设置缓存key2-value2。假设调用此方法的时候,发现redis中已经存在了key2,但是该key2已经过期了,如果此时不执行删除操作的话,setnx方法将会直接返回false,也就是说此时并没有重新设置key2-value2成功,所以对于一定要在setnx执行之前,对key2进行过期检查)

            可是,很多过期key,你没及时去查,定期删除也漏掉了,大量过期key堆积内存,Redis内存殆耗尽!因此内存满时,还需有内存淘汰机制!这就是 Redis 自己 主动删除 数据了!

    3 Redis 内存淘汰机制

            Redis 数据库可以通过配置文件来配置最大缓存,当写入的数据发现没有足够的内存可用的时候,Redis 会触发内存淘汰机制。Redis 为了满足多样化场景,提供了八种策略,可以在 redis.config 文件中配置:

    1. volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
    2. volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
    3. volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
    4. volatile-lfu:从已设置过期时间的数据集中挑选使用频率最低的数据淘汰
    5. allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰
    6. allkeys-lfu:从所有数据集中挑选使用频率最低的数据淘汰
    7. allkeys-random:从所有数据集中任意选择数据淘汰
    8. noenviction:不回收任何数据,返回一个写操作的错误信息。这也是默认策略

    参考链接

    Redis 详解_罗志宏的博客-CSDN博客

    Redis 的过期策略_redis key过期策略_胖虎·的博客-CSDN博客

    Redis键过期策略详解-云社区-华为云

    详细聊聊Redis的过期策略_Redis_脚本之家

    Redis的过期策略以及内存淘汰机制_redis过期策略以及内存淘汰机制_Felix-Yuan的博客-CSDN博客

    Redis 详解_王叮咚的博客-CSDN博客

  • 相关阅读:
    算法补天系列之——前缀树+贪心算法
    linux的基本工具【gcc/g++及Makefile】
    Oracle 19c RAC安装时遇到 INS-06006 错误
    c# Predicate vs Action vs Func
    数据库系统原理与应用教程(076)—— MySQL 练习题:操作题 160-167(二十):综合练习
    【C++】拷贝构造函数调用时机 ① ( 使用一个对象初始化另外一个对象 | 将一个对象赋值给另外一个对象 )
    C# 实现数独游戏
    HashMap和Hashtable的详细区别
    .NET ABP.Zero 项目疑似内存排查历程
    C++数据结构题:DS 顺序表--连续操作
  • 原文地址:https://blog.csdn.net/junbaozi/article/details/132651945