Redis的缓存策略和主键失效机制

Redis技术学习 https://www.itkc8.com

作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略.

  在Redis当中,有生存期的key被称为volatile。在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),它可能会被删除。

1、影响生存时间的一些操作

  生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,也就是说,修改key对应的value和使用另外相同的key和value来覆盖以后,当前数据的生存时间不同。

   比如说,对一个 key 执行INCR命令,对一个列表进行LPUSH命令,或者对一个哈希表执行HSET命令,这类操作都不会修改 key 本身的生存时间。另一方面,如果使用RENAME对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。

  RENAME命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。使用PERSIST命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个persistent key 。 

2、如何更新生存时间

   可以对一个已经带有生存时间的 key 执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。过期时间的精度已经被控制在1ms之内,主键失效的时间复杂度是O(1),
   EXPIRE和TTL命令搭配使用,TTL可以查看key的当前生存时间。设置成功返回 1;当 key 不存在或者不能为 key 设置生存时间时,返回 0 。

 最大缓存配置

  在 redis 中,允许用户设置最大使用内存大小

1

server.maxmemory

  默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

redis 提供 6种数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据

 其中的LRU算法即是【最近最少使用】

注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

使用策略规则:

1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
 2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random

三种数据淘汰策略:

 ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,然后取最先失效的key进行淘汰

 

redis回收算法,实际不是严谨的LRU算法,而是抽样回收数据,这样算是为了减少消耗内存使用,但是抽样回收的缓存和全部数据回收缓存差异非常小,或者根本就没有。

 

三、生产使用

(1)、先预测好系统所需要的内存高峰,部署相对应内存的缓存服务器。

(2)、设置maxmemory和相对应的回收策略算法,设置最好为物理内存的3/4,或者比例更小,因为redis复制数据等其他服务时,也是需要缓存的。以防缓存数据过大致使redis崩溃,造成系统出错不可用。牺牲一部分缓存数据,保存整体系统可用性。

 

 

 

-----------------------------------------------------------------------

 

1、设置过期时间

  • expire key time(以秒为单位)--这是最常用的方式
  • setex(String key, int seconds, String value)--字符串独有的方式

具体的使用方式:查看"java企业项目开发实践"的第九章 企业项目开发--分布式缓存Redis(1)和第十章 企业项目开发--分布式缓存Redis(2)

注意

  • 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
  • 如果没有设置时间,那缓存就是永不过期
  • 如果设置了过期时间,之后又想让缓存永不过期,使用persist key

 

2、三种过期策略

  • 定时删除
    • 含义:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除
    • 优点:保证内存被尽快释放
    • 缺点:
      • 若过期key很多,删除这些key会占用很多的CPU时间,在CPU时间紧张的情况下,CPU不能把所有的时间用来做要紧的事儿,还需要去花时间删除这些key
      • 定时器的创建耗时,若为每一个设置过期时间的key创建一个定时器(将会有大量的定时器产生),性能影响严重
      • 没人用
  • 惰性删除
    • 含义:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。
    • 优点:删除操作只发生在从数据库取出key的时候发生,而且只删除当前key,所以对CPU时间的占用是比较少的,而且此时的删除是已经到了非做不可的地步(如果此时还不删除的话,我们就会获取到了已经过期的key了)
    • 缺点:若大量的key在超出超时时间后,很久一段时间内,都没有被获取过,那么可能发生内存泄露(无用的垃圾占用了大量的内存)
  • 定期删除
    • 含义:每隔一段时间执行一次删除过期key操作
    • 优点:
      • 通过限制删除操作的时长和频率,来减少删除操作对CPU时间的占用--处理"定时删除"的缺点
      • 定期删除过期key--处理"惰性删除"的缺点
    • 缺点
      • 在内存友好方面,不如"定时删除"
      • 在CPU时间友好方面,不如"惰性删除"
    • 难点
      • 合理设置删除操作的执行时长(每次删除执行多长时间)和执行频率(每隔多长时间做一次删除)(这个要根据服务器运行情况来定了)

注意

  • 上边所说的数据库指的是内存数据库,默认情况下每一台redis服务器有16个数据库(关于数据库的设置,看下边代码),默认使用0号数据库,所有的操作都是对0号数据库的操作,关于redis数据库的存储结构,查看 第八章 Redis数据库结构与读写原理
    # 设置数据库数量。默认为16个库,默认使用DB 0,可以使用"select 1"来选择一号数据库
    # 注意:由于默认使用0号数据库,那么我们所做的所有的缓存操作都存在0号数据库上,
    # 当你在1号数据库上去查找的时候,就查不到之前set过得缓存
    # 若想将0号数据库上的缓存移动到1号数据库,可以使用"move key 1"
    databases 16
  • memcached只是用了惰性删除,而redis同时使用了惰性删除与定期删除,这也是二者的一个不同点(可以看做是redis优于memcached的一点)
  • 对于惰性删除而言,并不是只有获取key的时候才会检查key是否过期,在某些设置key的方法上也会检查(eg.setnx key2 value2:该方法类似于memcached的add方法,如果设置的key2已经存在,那么该方法返回false,什么都不做;如果设置的key2不存在,那么该方法设置缓存key2-value2。假设调用此方法的时候,发现redis中已经存在了key2,但是该key2已经过期了,如果此时不执行删除操作的话,setnx方法将会直接返回false,也就是说此时并没有重新设置key2-value2成功,所以对于一定要在setnx执行之前,对key2进行过期检查)

 

3、Redis采用的过期策略

惰性删除+定期删除

  • 惰性删除流程
    • 在进行get或setnx等操作时,先检查key是否过期,
    • 若过期,删除key,然后执行相应操作;
    • 若没过期,直接执行相应操作
  • 定期删除流程(简单而言,对指定个数个库的每一个库随机删除小于等于指定个数个过期key)
    • 遍历每个数据库(就是redis.conf中配置的"database"数量,默认为16)
      • 检查当前库中的指定个数个key(默认是每个库检查20个key,注意相当于该循环执行20次,循环体时下边的描述)
        • 如果当前库中没有一个key设置了过期时间,直接执行下一个库的遍历
        • 随机获取一个设置了过期时间的key,检查该key是否过期,如果过期,删除key
        • 判断定期删除操作是否已经达到指定时长,若已经达到,直接退出定期删除。

注意

  • 对于定期删除,在程序中有一个全局变量current_db来记录下一个将要遍历的库,假设有16个库,我们这一次定期删除遍历了10个,那此时的current_db就是11,下一次定期删除就从第11个库开始遍历,假设current_db等于15了,那么之后遍历就再从0号库开始(此时current_db==0)
  • 由于在实际中并没有操作过定期删除的时长和频率,所以这两个值的设置方式作为疑问?

 

4、RDB对过期key的处理

过期key对RDB没有任何影响

  • 从内存数据库持久化数据到RDB文件
    • 持久化key之前,会检查是否过期,过期的key不进入RDB文件
  • 从RDB文件恢复数据到内存数据库
    • 数据载入数据库之前,会对key先进行过期检查,如果过期,不导入数据库(主库情况)

 

5、AOF对过期key的处理

过期key对AOF没有任何影响

  • 从内存数据库持久化数据到AOF文件:
    • 当key过期后,还没有被删除,此时进行执行持久化操作(该key是不会进入aof文件的,因为没有发生修改命令)
    • 当key过期后,在发生删除操作时,程序会向aof文件追加一条del命令(在将来的以aof文件恢复数据的时候该过期的键就会被删掉)
  • AOF重写
    • 重写时,会先判断key是否过期,已过期的key不会重写到aof文件 

 

 

 

----------------------------------------------------------------------------

 

作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略。

>>EXPIRE主键失效机制

在Redis当中,有生存期的key被称为volatile,
在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),它可能会被删除。

(1)影响生存时间的一些操作

生存时间可以通过使用 DEL 命令来删除整个 key 来移除,或者被 SET 和 GETSET 命令覆盖原来的数据,

也就是说,修改key对应的value和使用另外相同的key和value来覆盖以后,当前数据的生存时间不同。

比如说,对一个 key 执行INCR命令,对一个列表进行LPUSH命令,或者对一个哈希表执行HSET命令,这类操作都不会修改 key 本身的生存时间。

另一方面,如果使用RENAME对一个 key 进行改名,那么改名后的 key 的生存时间和改名前一样。

RENAME命令的另一种可能是,尝试将一个带生存时间的 key 改名成另一个带生存时间的 another_key ,这时旧的 another_key (以及它的生存时间)会被删除,然后旧的 key 会改名为 another_key ,因此,新的 another_key 的生存时间也和原本的 key 一样。
使用PERSIST命令可以在不删除 key 的情况下,移除 key 的生存时间,让 key 重新成为一个persistent key 。

(2)如何更新生存时间

可以对一个已经带有生存时间的 key 执行EXPIRE命令,新指定的生存时间会取代旧的生存时间。
过期时间的精度已经被控制在1ms之内,主键失效的时间复杂度是O(1),
EXPIRE和TTL命令搭配使用,TTL可以查看key的当前生存时间
设置成功返回 1;当 key 不存在或者不能为 key 设置生存时间时,返回 0 。

>>最大缓存配置

在 redis 中,允许用户设置最大使用内存大小

1

server.maxmemory

默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。
redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。

redis 提供 6种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据

注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,
后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。

使用策略规则:

(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru。
(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random。

三种数据淘汰策略:

ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,然后取最先失效的key进行淘汰。

>>失效的内部实现

Redis 删除失效主键的方法主要有两种:

消极方法(passive way),在主键被访问时如果发现它已经失效,那么就删除它
积极方法(active way),周期性地从设置了失效时间的主键中选择一部分失效的主键删除
主键具体的失效时间全部都维护在expires这个字典表中。

1

2

3

4

5

6

7

8

typedef struct redisDb {

    dict *dict; //key-value

    dict *expires;  //维护过期key

    dict *blocking_keys;

    dict *ready_keys;

    dict *watched_keys;

    int id;

} redisDb;

 

(1)passive way 消极方法

在passive way 中, redis在实现GET、MGET、HGET、LRANGE等所有涉及到读取数据的命令时都会调用 expireIfNeeded,它存在的意义就是在读取数据之前先检查一下它有没有失效,如果失效了就删除它。
expireIfNeeded函数中调用的另外一个函数propagateExpire,这个函数用来在正式删除失效主键之前广播这个主键已经失效的信息,这个信息会传播到两个目的地:
一个是发送到AOF文件,将删除失效主键的这一操作以DEL Key的标准命令格式记录下来;
另一个就是发送到当前Redis服务器的所有Slave,同样将删除失效主键的这一操作以DEL Key的标准命令格式告知这些Slave删除各自的失效主键。从中我们可以知道,所有作为Slave来运行的Redis服务器并不需要通过消极方法来删除失效主键,它们只需要执行Master的删除指令即可。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

int expireIfNeeded(redisDb *db, robj *key) {

   // 获取主键的失效时间

    long long when = getExpire(db,key);

    //假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0

    if (when < 0) return 0;

   // 假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0

    if (server.loading) return 0;

   // 假如当前的Redis服务器是作为Slave运行的,那么不进行失效主键的删除,因为Slave

  //  上失效主键的删除是由Master来控制的,但是这里会将主键的失效时间与当前时间进行

   // 一下对比,以告知调用者指定的主键是否已经失效了

    if (server.masterhost != NULL) {

        return mstime() > when;

    }

    //如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键

   // 还未失效就直接返回0

    if (mstime() <= when) return 0;

   // 如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失

   // 效的信息进行广播,最后将该主键从数据库中删除

    server.stat_expiredkeys++;

    propagateExpire(db,key);

    return dbDelete(db,key);

}

 

void propagateExpire(redisDb *db, robj *key) {

    robj *argv[2];

   // shared.del是在Redis服务器启动之初就已经初始化好的一个常用Redis对象,即DEL命令

    argv[0] = shared.del;

    argv[1] = key;

    incrRefCount(argv[0]);

    incrRefCount(argv[1]);

  //  检查Redis服务器是否开启了AOF,如果开启了就为失效主键记录一条DEL日志

    if (server.aof_state != REDIS_AOF_OFF)

        feedAppendOnlyFile(server.delCommand,db->id,argv,2);

    //检查Redis服务器是否拥有Slave,如果是就向所有Slave发送DEL失效主键的命令,这就是

   // 上面expireIfNeeded函数中发现自己是Slave时无需主动删除失效主键的原因了,因为它

  //  只需听从Master发送过来的命令就OK了

    if (listLength(server.slaves))

        replicationFeedSlaves(server.slaves,db->id,argv,2);

    decrRefCount(argv[0]);

    decrRefCount(argv[1]);

}

 

(2)Active Way 积极方法

消极方法的缺点是,如果key 迟迟不被访问,就会占用很多内存空间,所以就出现了积极的方式(Active Way),

此方法利用了redis的时间事件,即每隔一段时间就中断一下完成一些指定操作,其中就包括检查并删除失效主键。

A.时间事件

创建时间事件, 回调函数就是serverCron,它在Redis服务器启动时创建,每秒的执行次数由宏定义REDIS_DEFAULT_HZ来指定,默认每秒钟执行10次。

1

2

3

4

5

//该代码在redis.c文件的initServer函数中。实际上,serverCron这个回调函数不仅要进行失效主键的检查与删除,还要进行统计信息的更新、客户端连接超时的控制、BGSAVE和AOF的触发等等,这里我们仅关注删除失效主键的实现,也就是函数activeExpireCycle。

if(aeCreateTimeEvent(server.el, 1, serverCron, NULL, NULL) == AE_ERR) {

        redisPanic("create time event failed");

        exit(1);

}

B.使用activeExpireCycle 清除失效key

其实现原理是从Redis中每个数据库的expires字典表中,随机抽样REDIS_EXPIRELOOKUPS_PER_CRON(默认值为10)个设置了失效时间的主键,检查它们是否已经失效并删除掉失效的主键,如果失效主键个数占本次抽样个数的比例超过25%,它会继续进行下一轮的随机抽样和删除,直到刚才的比例低于25%才停止对当前数据库的处理,转向下一个数据库。

注意,activeExpireCycle函数不会试图一次性处理Redis中的所有数据库,而是最多只处理REDIS_DBCRON_DBS_PER_CALL(默认值为16),此外activeExpireCycle函数还有处理时间上的限制,不是想执行多久就执行多久,凡此种种都只有一个目的,那就是避免失效主键删除占用过多的CPU资源。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

void activeExpireCycle(void) {

    /*因为每次调用activeExpireCycle函数不会一次性检查所有Redis数据库,所以需要记录下

        每次函数调用处理的最后一个Redis数据库的编号,这样下次调用activeExpireCycle函数

        还可以从这个数据库开始继续处理,这就是current_db被声明为static的原因,而另外一

        个变量timelimit_exit是为了记录上一次调用activeExpireCycle函数的执行时间是否达

        到时间限制了,所以也需要声明为static

    */

    static unsigned int current_db = 0;

    static int timelimit_exit = 0;

    unsigned int j, iteration = 0;

 

    /**

        每次调用activeExpireCycle函数处理的Redis数据库个数为REDIS_DBCRON_DBS_PER_CALL

        unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;

        long long start = ustime(), timelimit;

        如果当前Redis服务器中的数据库个数小于REDIS_DBCRON_DBS_PER_CALL,则处理全部数据库,

        如果上一次调用activeExpireCycle函数的执行时间达到了时间限制,说明失效主键较多,也

        会选择处理全部数据库

    */

    if (dbs_per_call > server.dbnum || timelimit_exit)

        dbs_per_call = server.dbnum;

 

    /*

        执行activeExpireCycle函数的最长时间(以微秒计),其中REDIS_EXPIRELOOKUPS_TIME_PERC

        是单位时间内能够分配给activeExpireCycle函数执行的CPU时间比例,默认值为25,server.hz

        即为一秒内activeExpireCycle的调用次数,所以这个计算公式更明白的写法应该是这样的,即

            (1000000 * (REDIS_EXPIRELOOKUPS_TIME_PERC / 100)) / server.hz

    */

    timelimit = 1000000*REDIS_EXPIRELOOKUPS_TIME_PERC/server.hz/100;

    timelimit_exit = 0;

    if (timelimit <= 0) timelimit = 1;

 

    //遍历处理每个Redis数据库中的失效数据

    for (j = 0; j < dbs_per_call; j++) {

        int expired;

        redisDb *db = server.db+(current_db % server.dbnum);

      // 此处立刻就将current_db加一,这样可以保证即使这次无法在时间限制内删除完所有当前

      // 数据库中的失效主键,下一次调用activeExpireCycle一样会从下一个数据库开始处理,

       //从而保证每个数据库都有被处理的机会

        current_db++;

       // 开始处理当前数据库中的失效主键

        do {

            unsigned long num, slots;

            long long now;

           // 如果expires字典表大小为0,说明该数据库中没有设置失效时间的主键,直接检查下

          // 一数据库

            if ((num = dictSize(db->expires)) == 0) break;

            slots = dictSlots(db->expires);

            now = mstime();

          //  如果expires字典表不为空,但是其填充率不足1%,那么随机选择主键进行检查的代价

           //会很高,所以这里直接检查下一数据库

            if (num && slots > DICT_HT_INITIAL_SIZE &&

                (num*100/slots < 1)) break;

            expired = 0;

            //如果expires字典表中的entry个数不足以达到抽样个数,则选择全部key作为抽样样本

            if (num > REDIS_EXPIRELOOKUPS_PER_CRON)

                num = REDIS_EXPIRELOOKUPS_PER_CRON;

            while (num--) {

                dictEntry *de;

                long long t;

              //  随机获取一个设置了失效时间的主键,检查其是否已经失效

                if ((de = dictGetRandomKey(db->expires)) == NULL) break;

                t = dictGetSignedIntegerVal(de);

                if (now > t) {

           // 发现该主键确实已经失效,删除该主键

                    sds key = dictGetKey(de);

                    robj *keyobj = createStringObject(key,sdslen(key));

                    //同样要在删除前广播该主键的失效信息

                    propagateExpire(db,keyobj);

                    dbDelete(db,keyobj);

                    decrRefCount(keyobj);

                    expired++;

                    server.stat_expiredkeys++;

                }

            }

           // 每进行一次抽样删除后对iteration加一,每16次抽样删除后检查本次执行时间是否

          // 已经达到时间限制,如果已达到时间限制,则记录本次执行达到时间限制并退出

            iteration++;

            if ((iteration & 0xf) == 0 &&

                (ustime()-start) > timelimit)

            {

                timelimit_exit = 1;

                return;

            }

        //如果失效的主键数占抽样数的百分比大于25%,则继续抽样删除过程

        while (expired > REDIS_EXPIRELOOKUPS_PER_CRON/4);

    }

}

  

>>Redis 的主键失效机制对系统性能的影响

Redis 会定期地检查设置了失效时间的主键并删除已经失效的主键,但是通过对每次处理数据库个数的限制、activeExpireCycle 函数在一秒钟内执行次数的限制、分配给 activeExpireCycle 函数CPU时间的限制、继续删除主键的失效主键数百分比的限制,Redis 已经大大降低了主键失效机制对系统整体性能的影响,但是如果在实际应用中出现大量主键在短时间内同时失效的情况还是会产生很多问题,
也就是缓存穿透的情况。

>>如何避免大量主键在同一时间同时失效造成数据库压力过大

合理的配置缓存可以增加系统的健壮性,避免缓存失效造成的事故。
1.在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2.可以通过缓存reload机制,预先去更新缓存.
2.不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
3.做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

>>Memcached删除失效主键的方法与Redis有何异同?

Memcached 在删除失效主键时采用的消极方法,即 Memcached 内部不会监视主键是否失效,而是在通过 Get 访问主键时才会检查其是否已经失效。
其次,Memcached 与 Redis 在主键失效机制上的最大不同是,Memcached 不会像 Redis 那样真正地去删除失效的主键,而只是简单地将失效主键占用的空间回收。

这样当有新的数据写入到系统中时,Memcached 会优先使用那些失效主键的空间。
如果失效主键的空间用光了,Memcached 还可以通过 LRU 机制来回收那些长期得不到访问的空间,因此 Memcached 并不需要像 Redis 中那样的周期性删除操作,这也是由 Memcached 使用的内存管理机制决定的。
同时, Redis 在出现 OOM时同样可以通过配置 maxmemory-policy 这个参数来决定是否采用 LRU 机制来回收内存空间。

Redis技术学习 https://www.itkc8.com

 

 

你可能感兴趣的:(Redis)