zoukankan      html  css  js  c++  java
  • redis系列之------过期策略

    前言

    我们都知道redis是常驻在内存当中的,因此他的效率比MySQL要快很多很多。但又引发了另外一个问题,内存从本质上讲,它是昂贵的,不能用于大量的长时间的存储,他是“不安全不稳定的“,并且有可能存在内存泄露,不能与磁盘相比。

    那么如果解决这种问题呢?因此我们使用redis的时候,强制的应该给每个Key加上过期时间。我们来看看redis对过期的Key是怎么处理的。

    过期键的判定

    第一个问题,redis如何知道他是一个过期键呢?又该如何判定他过期了呢?

    在数据库中, 所有键的过期时间都被保存在 redisDb 结构的 expires 字典里:

    1 typedef struct redisDb {
    2 
    3     // ...
    4 
    5     dict *expires;
    6 
    7     // ...
    8 
    9 } redisDb;

    expires 字典的键是一个指向 dict 字典(键空间)里某个键的指针, 而字典的值则是键所指向的数据库键的到期时间, 这个值以 long long类型表示。

    下图展示了一个含有三个键的数据库,其中 number 和 book 两个键带有过期时间

    我们可以看到number和book是有一个过期时间的,他是long long类型。实则他是一个unix的时间戳,因此判断他是否过期就十分的简单了。

    通过 expires 字典, 可以用以下步骤检查某个键是否过期:

    1. 检查键是否存在于 expires 字典:如果存在,那么取出键的过期时间;
    2. 检查当前 UNIX 时间戳是否大于键的过期时间:如果是的话,那么键已经过期;否则,键未过期。

    可以用伪代码来描述这一过程:

     1 def is_expired(key):
     2 
     3     # 取出键的过期时间
     4     key_expire_time = expires.get(key)
     5 
     6     # 如果过期时间不为空,并且当前时间戳大于过期时间,那么键已经过期
     7     if expire_time is not None and current_timestamp() > key_expire_time:
     8         return True
     9 
    10     # 否则,键未过期或没有设置过期时间
    11     return False

    过期键的清除

    当我们知道这个键过期了,我们该如何清除呢?基本上有以下三种策略:

    • 定时删除:在设置键的过期时间时,创建一个定时事件,当过期时间到达时,由事件处理器自动执行键的删除操作。
    • 惰性删除:放任键过期不管,但是在每次从 dict 字典中取出键值时,要检查键是否过期,如果过期的话,就删除它,并返回空;如果没过期,就返回键值。
    • 定期删除:每隔一段时间,对 expires 字典进行检查,删除里面的过期键。

    定时删除

    定时删除策略对内存是最友好的: 因为它保证过期键会在第一时间被删除, 过期键所消耗的内存会立即被释放。

    这种策略的缺点是, 它对 CPU 时间是最不友好的: 因为删除操作可能会占用大量的 CPU 时间 —— 在内存不紧张、但是 CPU 时间非常紧张的时候 (比如说,进行交集计算或排序的时候), 将 CPU 时间花在删除那些和当前任务无关的过期键上, 这种做法毫无疑问会是低效的。

    除此之外, 目前 Redis 事件处理器对时间事件的实现方式 —— 无序链表, 查找一个时间复杂度为 O(N) —— 并不适合用来处理大量时间事件。

    惰性删除

    惰性删除对 CPU 时间来说是最友好的: 它只会在取出键时进行检查, 这可以保证删除操作只会在非做不可的情况下进行 —— 并且删除的目标仅限于当前处理的键, 这个策略不会在删除其他无关的过期键上花费任何 CPU 时间。

    惰性删除的缺点是, 它对内存是最不友好的: 如果一个键已经过期, 而这个键又仍然保留在数据库中, 那么 dict 字典和 expires 字典都需要继续保存这个键的信息, 只要这个过期键不被删除, 它占用的内存就不会被释放。

    在使用惰性删除策略时, 如果数据库中有非常多的过期键, 但这些过期键又正好没有被访问的话, 那么它们就永远也不会被删除(除非用户手动执行), 这对于性能非常依赖于内存大小的 Redis 来说, 肯定不是一个好消息。

    举个例子, 对于一些按时间点来更新的数据, 比如日志(log), 在某个时间点之后, 对它们的访问就会大大减少, 如果大量的这些过期数据积压在数据库里面, 用户以为它们已经过期了(已经被删除了), 但实际上这些键却没有真正的被删除(内存也没有被释放), 那结果肯定是非常糟糕。

    定期删除

    从上面对定时删除和惰性删除的讨论来看, 这两种删除方式在单一使用时都有明显的缺陷: 定时删除占用太多 CPU 时间, 惰性删除浪费太多内存。

    定期删除是这两种策略的一种折中:

    • 它每隔一段时间执行一次删除操作,并通过限制删除操作执行的时长和频率,籍此来减少删除操作对 CPU 时间的影响。
    • 另一方面,通过定期删除过期键,它有效地减少了因惰性删除而带来的内存浪费。

    因此最终redis使用的过期键删除策略是惰性删除加上定期删除, 这两个策略相互配合,可以很好地在合理利用 CPU 时间和节约内存空间之间取得平衡。

    因此redis大致流程如下:获取key之前,会检查key是否过期,如过期,直接删除,返回null。

    并且会定期的随机的检查大约25%的key是否过期,如果超过一定比例的key被过期。那么继续循环,直至低于这个数值。

    这个定期的时间,以及数值都可以在conf文件里面配置。

  • 相关阅读:
    【Lintcode】112.Remove Duplicates from Sorted List
    【Lintcode】087.Remove Node in Binary Search Tree
    【Lintcode】011.Search Range in Binary Search Tree
    【Lintcode】095.Validate Binary Search Tree
    【Lintcode】069.Binary Tree Level Order Traversal
    【Lintcode】088.Lowest Common Ancestor
    【Lintcode】094.Binary Tree Maximum Path Sum
    【算法总结】二叉树
    库(静态库和动态库)
    从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/wenbochang/p/12436350.html
Copyright © 2011-2022 走看看