redis中的过期删除策略和内存淘汰机制

一、过期时间的设置

Redis有四个不同的命令可以用于设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除):

  • EXPIRE 命令用于将键key的生存时间设置为ttl秒。
  • PEXPIRE 命令用于将键key的生存时间设置为ttl毫秒。
  • EXPIREAT 命令用于将键key的过期时间设置为 timestamp所指定的秒数时间戳。
  • PEXPIREAT 命令用于将键key的过期时间设置 为timestamp所指定的毫秒数时间戳。

虽然有多种不同单位和不同形式的设置命令,但实际上EXPIRE、PEXPIRE、EXPIREAT三个命令都是使用PEXPIREAT命令来实现的

redis中的过期删除策略和内存淘汰机制_第1张图片

1.保存过期时间

redisDb结构的expires字典保存了数据库中所有键的过期时间,我们称这个字典为过期字典

  • 过期字典的键是一个指针,这个指针指向键空间中的某个键对象(也即是某个数据库键)。
  • 过期字典的值是一个long long类型的整数,这个整数保存了键所指向的数据库键的过期时间——一个毫秒精度的UNIX时间戳。

redis中的过期删除策略和内存淘汰机制_第2张图片

eg.键为alphabet键对象,值为1385877600000.

2.移除过期时间

PERSIST命令可以移除一个键的过期时间:

redis中的过期删除策略和内存淘汰机制_第3张图片

PERSIST命令就是PEXPIREAT命令的反操作:PERSIST命令在过期字典中查找给定的键,并解除键和值(过期时间)在过期字典中的关联

3.返回剩余时间

TTL命令以秒为单位返回键的剩余生存时间,而PTTL命令则以毫 秒为单位返回键的剩余生存时间。

redis中的过期删除策略和内存淘汰机制_第4张图片

TTL和PTTL两个命令都是通过计算键的过期时间和当前时间之间的差来实现的

4.过期键的判定

(1)检查当前UNIX时间戳是否大于键的过期时间:如果是的话,那么键已经过期;否则的话,键未过期。

(2)通过TTL和PTTL返回值查看,如果返回值大于0则未过期,小于0则过期。

二、过期键删除策略

如果一个键过期了,那么它什么时候会被删除呢?在redis中,有三种不同的删除策略:

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

其中第一种和第三种为主动删除策略,而第二种则为被动删除策略

1.定时删除

​ 优点:对内存友好。通过使用定时器,定时删除策略可以保证过期键会尽可能快地被删除,并释放过期键所占用的内存。

​ 缺点:对CPU时间不友好。在过期键比较多的情况下,删除过期键这一行为可能会占用相当一部分 CPU时间,在内存不紧张但是CPU时间非常紧张的情况下,将CPU时间用在删除和当前任务无关的过期键上,无疑会对服务器的响应时间和吞吐量造成影响。

​ 在实际中,要让服务器创建大量的定时器,从而实现定时删除策略并不现实。

2.惰性删除

​ 优点:对CPU时间友好。程序只会在取出键时才对键进行过期检查,这可以保证删除过期键的操作只会在非做不可的情况下进行,并且删除的目标仅限于当前处理的键,不会在其他无关的过期键上花费时间。

​ 缺点:对内存不友好。如果一个键已经过期,而这个键又仍然保留在数据库中,那么只要这个过期键不被删 除,它所占用的内存就不会释放。

redis中的过期删除策略和内存淘汰机制_第5张图片

3.定期删除

定期删除策略是前两种策略的一种整合和折中。

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

但是定期删除策略的难点是确定删除操作执行的时长和频率

  • 如果删除操作执行得太频繁,或者执行的时间太长,定期删除策略就会退化成定时删除策略,以至于将CPU时间过多地消耗在删除过期键上面。
  • 如果删除操作执行得太少,或者执行的时间太短,定期删除策略 又会和惰性删除策略一样,出现浪费内存的情况。

实现过程

每当 Redis的服务器周期性操作redis.c/serverCron函数执行时, activeExpireCycle函数就会被调用,它在规定的时间内,分多次遍历服务器中的各个数据库,从数据库的expires字典中随机检查一部分键的过期时间,并删除其中的过期键。


在实际中,Redis服务器使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。

三、内存淘汰机制

定期删除+惰性删除存在的问题:

​ 如果某个key过期后,定期删除没删除成功(没抽取到),然后也没再次去请求key,也就是说惰性删除也没生效。这时,如果大量过期的key堆积在内存中,redis的内存会越来越高,导致redis的内存块耗尽。那么就应该采用内存淘汰机制

redis中的过期删除策略和内存淘汰机制_第6张图片

1.noeviction

​ 不进行淘汰数据。一旦缓存被写满,再有写请求进来,Redis就不再提供服务,而是直接返回错误。

2.volatile-random

​ 在设置了过期时间的键值对中,随机移除某个键值对。

3.volatile-ttl

​ 在设置了过期时间的键值对中,移除即将过期的键值对。

4.volatile-lru

​ 在设置了过期时间的键值对中,移除最近最少使用的键值对。

5.volatile-lfu

​ 在设置了过期时间的键值对中,移除最不经常使用的键值对(历史访问频率)。

6.allkeys-random

​ 在所有键值对中,随机移除某个key。

7.allkeys-lru

​ 在所有的键值对中,移除最近最少使用的键值对。

8.allkeys-lfu

​ 在所有的键值对中,移除最不经常使用的键值对。

通常情况下优先使用 allkeys-lru 策略。这样可以充分利用 LRU 这一经典缓存算法的优势,把最近最常访问的数据留在缓存中,提升应用的访问性能。

如果业务数据中有明显的冷热数据区分,建议使用 allkeys-lru 策略。

如果业务应用中的数据访问频率相差不大,可以使用 allkeys-random随机选择淘汰的数据就行。

四、Redis中的LRU和LFU

在redis中,一般是通过LRU和LFU来实现淘汰的,但是这两种算法在redis中与正常实现略有不同。

1.LRU

​ Redis维护了一个24位时钟,可以理解为当前系统的时间戳,每隔一定时间会更新这个时钟。每个key对象内部同样维护了一个24位的时钟,当新增key对象的时候会把系统的时钟赋值到这个内部对象时钟。比如我现在要进行LRU,那么首先拿到当前的全局时钟,然后再找到内部时钟与全局时钟距离时间最久的(差最大)进行淘汰

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?

(1)如果精准LRU则需要对所有key进行排序,这样近似LRU性能更高。

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

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

(4)可以通过配置的取样率来提升精准度,例如通过 CONFIG SET maxmemory-samples 指令可以设置取样数,取样数越高越精准。

2.LFU

​ LFU是在Redis4.0后出现的,它的核心思想是根据key的最近被访问的频率进行淘汰,很少被访问的优先被淘汰,被访问的多的则被留下来LFU算法能更好的表示一个key被访问的热度

​ 假如你使用的是LRU算法,一个key很久没有被访问到,只刚刚是偶尔被访问了一次,那么它就被认为是热点数据,不会被淘汰,而有些key将来是很有可能被访问到的则被淘汰了。如果使用LFU算法则不会出现这种情况,因为使用一次并不会使一个key成为热点数据。

LFU (Least Frequently Used) :最近最不频繁使用,跟使用的次数有关,淘汰使用次数最少的。

LRU (Least Recently Used):最近最少使用,跟使用的最后一次时间有关,淘汰最近使用时间离现在最久的。

​ LFU算法在Redis中是通过一个计数器来实现的,每个key都有一个计数器,访问频度越高,计数器的值就越大,Redis就根据计数器的值来淘汰key,当然计数器的值也是会随着时间减少的

用户可配置的LFU算法参数一共有两个分别是:lfu-log-factorlfu-decay-time

  • lfu-log-factor:设置的越大,key的计数值就越难增长,因此就要求key的访问频度较高才能避免被淘汰。
  • lfu-decay-time:是表示隔多久将计数器的值减一。

你可能感兴趣的:(redis,redis,数据库,缓存)