zoukankan      html  css  js  c++  java
  • redis-11 redis清除过期 key 详解

    始因

      有时候线上可能会遇到这样的问题:

      明明我设置了对应的 key 以及超时时间,但是在使用的过程当中发现对应的 key 丢失了,尤其是在用户账号登录状态保持有效期的场景下,会越发的明显。即:一个用户正常登录会产生一个有效期为一天的 token,这样用户再次进入网站是不需要登录的。但是发生 key 丢失问题就会导致用户需要频繁的重新登录,用户体验相当不好。导致这种问题的原因一般有以下两种情况:

      1. token 生成时出现逻辑问题

      2. 验证 token 时出问题了

      对于上线稳定的项目来说,发生 1 的概率基本为 0。那么会立马定位到 2 的情况。这种情况就会引发我们今天讨论的问题:

      redis 如何自动清理过期 key,以及对应 key 没有过期但是也会被清理掉呢?说人话:redis 内部如何清理过期 key?

    常见删除策略(抛开 redis)

    1. 定时删除:在设置键的过期时间的同时,创建一个定时器 timer。让定时器在键的过期时间来临时,立即执行对键的删除操作。
    2. 惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
    3. 定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。

      在上述的三种策略中 定时删除 和 定期删除 属于不同时间粒度的 主动删除,惰性删除属于 被动删除

      以上三种策略都有各自的优缺点:

        1. 定时删除 对内存使用率有优势,但是对 CPU 不友好;

        2. 惰性删除 对内存不友好,如果某些键值对一直不被使用,那么会造成一定量的内存浪费;

        3. 定期删除 是 定时删除 和 惰性删除 的折中。

    正常情况下 redis 中的实现

      Reids 采用的是 惰性删除 和 定时删除 的结合,一般来说可以借助 最小堆 来实现 定时器,不过 Redis 的设计考虑到时间事件的有限种类 和 数量,使用了 无序链表 存储时间事件,这样如果在此基础上实现定时删除,就意味着 O(N) 遍历获取最近需要删除的数据。

    定期删除策略

    Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每 100ms 进行一次过期扫描:

    1. 随机抽取 20 个 key

    2. 删除这 20 个key中过期的key

    3. 如果过期的 key 比例超过 1/4,就重复步骤 1,继续删除。

    为什不扫描所有的 key?

      Redis 是单线程,全部扫描岂不是卡死了。而且为了防止每次扫描过期的 key 比例都超过 1/4,导致不停循环卡死线程,Redis 为每次扫描添加了上限时间,默认是 25ms。

      如果客户端将超时时间设置的比较短,比如 10ms,那么就会出现大量的链接因为超时而关闭,业务端就会出现很多异常。而且这时你还无法从 Redis 的 slowlog 中看到慢查询记录,因为慢查询指的是逻辑处理过程慢,不包含等待时间。

      如果在同一时间出现大面积 key 过期,Redis 循环多次扫描过期词典,直到过期的 key 比例小于 1/4。这会导致卡顿,而且在高并发的情况下,可能会导致缓存雪崩。

    为什么 Redis 为每次扫描添的上限时间是 25ms,还会出现上面的情况?

      因为 Redis 是单线程,每个请求处理都需要排队,而且由于 Redis 每次扫描都是 25ms,也就是每个请求最多 25ms,100 个请求就是 2500ms。

      如果有大批量的 key 过期,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力。

    从库的过期策略

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

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

    懒惰删除策略

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

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

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

    unlink

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

    > unlink key
    OK
    

    flush

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

    > flushall async
    OK
    

    内存淘汰通过近似 LRU来实现

      在解释近似LRU之前,先来简单了解一下LRU。

      当 Redis 的内存占用超过我们设置的 maxmemory 时,会把长时间没有使用的key清理掉。按照 LRU算法,我们需要对所有key(也可以设置成只淘汰有过期时间的key)按照空闲时间进行排序,然后淘汰掉空闲时间最大的那部分数据,使得Redis的内存占用降到一个合理的值。

      LRU算法的缺点:

        1. 我们需要维护一个全部(或只有过期时间)key的列表,还要按照最近使用时间排序。这会消耗大量内存

        2. 每次操作 key 时更新对应维护列表的排序也会占用额外的CPU资源。

      对于Redis这样对性能要求很高的系统来说是不被允许的。

      因此,Redis采用了一种 近似LRU 的算法。当 Redis 接收到新的写入命令,而内存又不够时,就会触发 近似LRU 算法来强制清理一些key。

      具体清理的步骤是:

        1. Redis会对 key 进行采样,通常是取5个,然后会把过期的key放到我们上面说的“过期池”中

        2. 过期池中的 key 是按照空闲时间来排序的,Redis 会优先清理掉空闲时间最长的 key,直到内存小于 maxmemory。

      其中 Redis首先是采样了一部分key,这里采样数量 maxmemory_samples 通常是5,我们也可以自己设置,采样数量越大,结果就越接近LRU算法的结果,带来的影响是:性能随之变差。

    清理策略

      最后我们来看一下Redis支持的几种清理策略

        1. noeviction:不会继续处理写请求(DEL可以继续处理)。

        2. allkeys-lru:对所有key的近似LRU

        3. volatile-lru:使用近似LRU算法淘汰设置了过期时间的key

        4. allkeys-random:从所有key中随机淘汰一些key

        5. volatile-random:对所有设置了过期时间的key随机淘汰

        6. volatile-ttl:淘汰有效期最短的一部分key

      Redis4.0 开始支持了 LFU 策略,和 LRU 类似,它分为两种:

        7. volatile-lfu:使用LFU算法淘汰设置了过期时间的key

        8. allkeys-lfu:从全部key中进行淘汰,使用LFU

    最后

      针对文章开始提到的问题,最好的解决办法是将使用内存量较大的业务 和 用户账号服务 使用的 redis 隔离开,这样就单个用户账号正常情况下是不会发生以上类似的问题了。


      
  • 相关阅读:
    Luogu1053 NOIP2005篝火晚会
    BZOJ2151 种树(贪心+堆+链表/wqs二分+动态规划)
    Luogu1155 NOIP2008双栈排序(并查集)
    Luogu1092 NOIP2004虫食算(搜索+高斯消元)
    Codeforces Round#516 Div.1 翻车记
    Luogu1731 NOI1999生日蛋糕(搜索)
    洛谷 P1379 八数码难题 解题报告
    洛谷 P2501 [HAOI2006]数字序列 解题报告
    洛谷 P3143 [USACO16OPEN]钻石收藏家Diamond Collector 解题报告
    洛谷 P2894 [USACO08FEB]酒店Hotel 解题报告
  • 原文地址:https://www.cnblogs.com/liang1101/p/12932400.html
Copyright © 2011-2022 走看看