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 隔离开,这样就单个用户账号正常情况下是不会发生以上类似的问题了。


      
  • 相关阅读:
    LeetCode OJ String to Integer (atoi) 字符串转数字
    HDU 1005 Number Sequence(AC代码)
    HDU 1004 Let the Balloon Rise(AC代码)
    HDU 1003 Max Sum(AC代码)
    012 Integer to Roman 整数转换成罗马数字
    011 Container With Most Water 盛最多水的容器
    010 Regular Expression Matching 正则表达式匹配
    007 Reverse Integer 旋转整数
    006 ZigZag Conversion
    005 Longest Palindromic Substring 最长回文子串
  • 原文地址:https://www.cnblogs.com/liang1101/p/12932400.html
Copyright © 2011-2022 走看看