redis过期策略和内存淘汰策略(四)

过期策略

我们在使用 redis 时,一般会设置一个过期时间,当然也有不设置过期时间的,也就是永久不过期。当我们设置了过期时间,redis 是如何判断是否过期,以及根据什么策略来进行删除的。

Redis采用的是定期删除 + 懒惰删除策略。

过期策略常用的有以下三种:

定时删除

  1. 含义:在设置 key 的过期时间的同时,为该 key 创建一个定时器,让定时器在 key 的过期时间来临时,对 key 进行删除
  2. 优点:保证内存被尽快释放
  3. 缺点:若过期 key 很多,删除这些 key 会占用很多的 CPU 资源。

懒惰删除

  1. 含义:key 过期的时候不删除,每次通过 key 获取值的时候去检查是否过期,若过期,则删除,返回 null。
  2. 优点:删除操作只发生在通过 key 取值的时候发生,而且只删除当前 key,所以对 CPU 时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的 key 了)
  3. 缺点:若大量的 key 在超出超时时间后,很久一段时间内,都没有被获取过,就会占用了大量的内存空间

定期删除

  1. 含义:Redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每 100ms 进行一次过期扫描,随机抽取一些设置了过期时间的key,检查其是否过期,如果有过期就删除。
  2. 优点:通过限制删除操作的时长和频率,来减少定时删除操作对 CPU 时间的占用
  3. 缺点:在内存友好方面,不如 ”定时删除”(会造成一定的内存占用,但是没有懒汉式那么占用内存) 在 CPU 时间友好方面,不如 ”懒汉式删除”(会定期的去进行比较和删除操作,cpu 方面不如懒汉式,但是比定时好)
  4. 难点:合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了),每次执行时间太长,或者执行频率太高对 cpu 都是一种压力。每次进行定期删除操作执行之后,需要记录遍历循环到了哪个标志位,以便下一次定期时间来时,从上次位置开始进行循环遍历。Redis 设定为每次扫描添的上限时间是 25ms。如果要设置大批量的key 的过期时间,要给过期时间设置一个随机范围,而不宜全部在同一时间过期,分散过期处理的压力。

从库的过期策略

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

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

内存淘汰策略

如果定期删除漏掉了很多过期 key,然后没及时去查,也就没走惰性删除,此时会有大量过期 key 堆积在内存里,导致 redis 内存块耗尽了,咋整?

答案是:走内存淘汰机制

redis主要提供了一下八种内存淘汰策略:

volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key

allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)。

volatile-lfu:当内存不足以容纳新写入数据时,在过期密集的键中,使用LFU算法进行删除key

allkeys-lfu:当内存不足以容纳新写入数据时,使用LFU算法移除所有的key

volatile-random:当内存不足以容纳新写入数据时,在设置了过期的键中,随机删除一个key

allkeys-random:当内存不足以容纳新写入数据时,随机删除一个或者多个key

volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。

noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。默认策略。

这八种大体上可以分为4种,lru、lfu、random、ttl。redis默认使用noeviction策略。默认策略

LRU算法

Redis 使用的并不是完全 LRU 算法。不使用 LRU 算法,是为了节省内存,Redis 采用的是随机LRU算法,Redis 为每一个 key 增加了一个24 bit的字段,用来记录这个 key 最后一次被访问的时间戳。

注意 Redis 的 LRU 淘汰策略是懒惰处理,也就是不会主动执行淘汰策略,当 Redis 执行写操作时,发现内存超出 maxmemory,就会执行 LRU 淘汰算法。这个算法就是随机采样出5(默认值)个 key,然后移除最旧的 key,如果移除后内存还是超出 maxmemory,那就继续随机采样淘汰,直到内存低于 maxmemory 为止。

lru算法的缺点:一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。

标准LRU实现方式

1. 新增key value的时候首先在链表结尾添加Node节点,如果超过LRU设置的阈值就淘汰队头的节点并删除掉HashMap中对应的节点。

2. 修改key对应的值的时候先修改对应的Node中的值,然后把Node节点移动队尾。

3. 访问key对应的值的时候把访问的Node节点移动到队尾即可。

Redis的LRU实现

Redis维护了一个24位时钟,可以简单理解为当前系统的时间戳,每隔一定时间会更新这个时钟。每个key对象内部同样维护了一个24位的时钟,当新增key对象的时候会把系统的时钟赋值到这个内部对象时钟。比如我现在要进行LRU,那么首先拿到当前的全局时钟,然后再找到内部时钟与全局时钟距离时间最久的(差最大)进行淘汰,这里值得注意的是全局时钟只有24位,按秒为单位来表示才能存储194天,所以可能会出现key的时钟大于全局时钟的情况,如果这种情况出现那么就两个相加而不是相减来求最久的key。

struct redisServer {
       pid_t pid; 
       char *configfile; 
       //全局时钟
       unsigned lruclock:LRU_BITS; 
       ...
};
typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    /* key对象内部时钟 */
    unsigned lru:LRU_BITS;
    int refcount;
    void *ptr;
} robj;

Redis中的LRU与常规的LRU实现并不相同,常规LRU会准确的淘汰掉队头的元素,但是Redis的LRU并不维护队列,只是根据配置的策略要么从所有的key中随机选择N个(N可以配置)要么从所有的设置了过期时间的key中选出N个键,然后再从这N个键中选出最久没有使用的一个key进行淘汰。

下图是常规LRU淘汰策略与Redis随机样本取一键淘汰策略的对比,浅灰色表示已经删除的键,深灰色表示没有被删除的键,绿色表示新加入的键,越往上表示键加入的时间越久。从图中可以看出,在redis 3中,设置样本数为10的时候能够很准确的淘汰掉最久没有使用的键,与常规LRU基本持平。

为什么要使用近似LRU?

1、性能问题,由于近似LRU算法只是最多随机采样N个key并对其进行排序,如果精准需要对所有key进行排序,这样近似LRU性能更高

2、内存占用问题,redis对内存要求很高,会尽量降低内存使用率,如果是抽样排序可以有效降低内存的占用

3、实际效果基本相等,如果请求符合长尾法则,那么真实LRU与Redis LRU之间表现基本无差异

4、在近似情况下提供可自配置的取样率来提升精准度,例如通过 CONFIG SET maxmemory-samples <count> 指令可以设置取样数,取样数越高越精准,如果你的CPU和内存有足够,可以提高取样数看命中率来探测最佳的采样比例。

LFU算法

lru和lfu淘汰算法的核心理念是保留他们认为是热点的数据。通过不同的算法去判定什么才是热门的数据。

猜你喜欢

转载自blog.csdn.net/weixin_46217160/article/details/115747548
今日推荐