Redis4-缓存过期和删除策略

2022年10月12日11:14:25

目录

为什么需要设置Redis的缓存过期?

如何设置缓存过期

maxmemory配置项

不设置的场景

设置的场景

那么如何设置内存合理呢?根据业务进行判断

expire命令

expire的使用

缓存过期的底层实现:

删除策略

定时删除(了解)

※ 为什么是随机抽取部分检测,而不是全部?

惰性删除

主动删除(重点)

maxmemory-policy 配置项

LRU

LFU

random 随机

ttl

noenviction

缓存淘汰策略的选择

其他场景对过期key的处理

为什么需要设置Redis的缓存过期?

长期使用,key会不断增加,Redis作为缓存使用,物理内存也会满

内存与硬盘交换(swap) 虚拟内存 ,频繁IO 性能急剧下降;

如何设置缓存过期

在Redis 的配置文件中 MEMORY MANAGEMENT 模块的内容就是关于缓存过期设置的相关配置

############################## MEMORY MANAGEMENT ################################
redis配置的最大内存容量
当内存满了,需要配合maxmemory-policy策略进行处理
注意slave的输出缓冲区是不计算在maxmemory内的。所以为了防止主机内存使用完,建议设置的maxmemory需要更小一些
maxmemory 122000000
 
#内存容量超过maxmemory后的处理策略。默认为: 不删除noeviction
#    volatile-lru:利用LRU算法移除设置过过期时间的key。
#    volatile-random:随机移除设置过过期时间的key。
#    volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#    allkeys-lru:利用LRU算法移除任何key。
#    allkeys-random:随机移除任何key。
#    noeviction:不移除任何key,只是返回一个写错误。
#上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误。redis将不再接收写请求,只接收get请求。
写命令包括:set setnx  setex append incr decr rpush lpush rpushx
 lpushx linsert lset rpoplpush sadd sinter sinterstore sunion sunionstore
 sdiff sdiffstore zadd zincrby zunionstore zinterstore hset hsetnx hmset 
 hincrby incrby decrby getset mset msetnx exec sort。
# maxmemory-policy noeviction
 
# lru检测的样本数。使用lru或者ttl淘汰算法,从需要淘汰的列表中随机选择sample个key,选出闲置时间最长的key移除
# maxmemory-samples 5
 
# 是否开启salve的最大内存
# replica-ignore-maxmemory yes
............

下面我们详细分析下上面的配置项的应用场景:

maxmemory配置项

获得maxmemory 的Redis命令 : CONFIG GET maxmemory

不设置的场景

Redis的key是固定的,不会增加

Redis作为DB使用,保证数据的完整性,不能淘汰 , 可以做集群,横向扩展

缓存淘汰策略:禁止驱逐 (默认)noeviction

设置的场景

Redis是作为缓存使用,不断增加Key

maxmemory : 默认为0 不限制

问题:达到物理内存后性能急剧下架,甚至崩溃

内存与硬盘交换(swap) 虚拟内存 ,频繁IO 性能急剧下降

那么如何设置内存合理呢?根据业务进行判断

1个Redis实例,保证系统运行 1 G ,剩下的就都可以设置Redis,一般设置为物理内存的3/4并且如果为slaver : 留出一定的内存

设置maxmemory后,当趋近maxmemory时,结合 maxmemory-policy 配置项设置 缓存淘汰策略,从内存中删除对象

expire命令

在Redis中可以使用expire命令设置一个键的存活时间(ttl: time to live),过了这段时间,该键就会自动被删除。

expire的使用

expire命令的使用方法如下: expire key ttl(单位秒)

127.0.0.1:6379> expire name 2 #2秒失效 
(integer) 1 
127.0.0.1:6379> get name 
(nil) 
127.0.0.1:6379> set name zhangfei 
OK
127.0.0.1:6379> ttl name # -1表示永久有效 
(integer) -1

缓存过期的底层实现:

上文中提高 关于Redis的底层数据结构,redis 中存在数据库的概念

typedef struct redisDb { 
    int id;             // id是数据库序号,为0-15(默认Redis有16个数据库) 
    long avg_ttl;       // 存储的数据库对象的平均ttl(time to live),用于统计 
    dict *dict;         // 存储数据库所有的key-value 
    dict *expires;      // 存储数据库所有key的过期时间 
    dict *blocking_keys;// blpop 存储阻塞key和客户端对象 
    dict *ready_keys;   // 阻塞后push 响应阻塞客户端 存储阻塞后push的key和客户端对象 
    dict *watched_keys; // 存储watch监控的的key和客户端对象 
}

上面的代码是Redis 中关于数据库的结构体定义,这个结构体定义中除了 id 以外都是指向字典的指针,其中我们再看看 dict 和 expires。

dict: 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对,

expires:则用于维护一个 Redis 数据库中所有设置了失效时间的键(即key与失效时间的映射)。

  • 当我们使用 expire命令设置一个key的失效时间时,Redis 首先到 dict 这个字典表中查找要设置的key是否存在,如果存在就将这个key和失效时间添加到 expires 这个字典表。
  • 当我们使用 setex命令向系统插入数据时,Redis 首先将 Key 和 Value 添加到 dict 这个字典表中,然后将 Key 和失效时间添加到 expires 这个字典表中。

简单地总结来说就是,设置了失效时间的key和具体的失效时间全部都维护在 expires 这个字典表中。

删除策略

Redis的数据删除有定时删除、惰性删除和主动删除三种方式。

Redis目前采用惰性删除+主动删除的方式。

定时删除(了解)

在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。

需要创建定时器,而且消耗CPU,一般不推荐使用。

定期删除是指Redis默认每隔 100ms 就 随机抽取 一些设置了过期时间的key,检测这些key是否过期,如果过期了就将其删除。

※ 100ms怎么来的?

在Redis的配置文件redis.conf中有一个属性"hz",默认为10,表示1s执行10次定期删除,即每隔100ms执行一次,可以修改这个配置值。

※ 随机抽取一些检测,一些是多少?

同样是由redis.conf文件中的maxmemory-samples属性决定的,默认为5。

※ 为什么是随机抽取部分检测,而不是全部?

因为如果Redis里面有大量key都设置了过期时间,全部都去检测一遍的话CPU负载就会很高,会浪费大量的时间在检测上面,甚至直接导致redis挂掉。

所有只会抽取一部分而不会全部检查。

正因为定期删除只是随机抽取部分key来检测,这样的话就会出现大量已经过期的key并没有被删除,这就是为什么有时候大量的key明明已经过了失效时间,

但是redis的内存还是被大量占用的原因 ,为了解决这个问题,Redis又引入了“惰性删除策略”。

惰性删除

在key被访问时如果发现它已经失效,那么就删除它。

调用expireIfNeeded函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删除它。

int expireIfNeeded(redisDb *db, robj *key) { 
    // 获取主键的失效时间 get当前时间-创建时间>ttl 
    long long when = getExpire(db,key); 
    // 假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0 
    if (when < 0) return 0; 
    // 假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0 
    if (server.loading) return 0; 
    ... 
    // 如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键还未失效就直接返回0 
    if (mstime() <= when) return 0; 
    // 如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失效的信息进行广播,最后将该主键从数据库中删除 
    server.stat_expiredkeys++; 
    propagateExpire(db,key); 
    return dbDelete(db,key); 
}

"定期删除+惰性删除"就能保证过期的key最终一定会被删掉 ,但是只能保证最终一定会被删除,要是定期删除遗漏的大量过期key,我们在很长的一段时间内也没有再访问这些key,那么这些过期key不就一直会存在于内存中吗?不就会一直占着我们的内存吗?这样不还是会导致redis内存耗尽吗?由于存在这样的问题,所以redis又引入了“内存淘汰机制”来解决。也就是主动删除

主动删除(重点)

maxmemory-policy 配置项

Redis目前共提供了8种内存淘汰策略,含Redis 4.0版本之后又新增的两种LFU模式:volatile-lfu和allkeys-lfu。

LRU

LRU (Least recently used) 最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,

其核心思想是:如果数据最近被访问过,那么将来被访问的几率也更高”。

最常见的实现是使用一个链表保存缓存数据,

详细算法实现如下:

  1. 新数据插入到链表头部;
  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
  3. 当链表满的时候,将链表尾部的数据丢弃。
  4. 在Java中可以使用LinkHashMap(哈希链表)去实现LRU,头插法实现

Redis的LRU 数据淘汰机制

在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,

server.lrulock 的值是根据 server.unixtime 计算出来的。

另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。

可以想象的是,每一次访问数据的时候,会更新 redisObject.lru。

LRU 数据淘汰机制是这样的:在数据集中随机挑选几个键值对,取出其中 lru 最大的键值对淘汰。不可能遍历key 用当前时间-最近访问 越大 说明 访问间隔时间越长

配置项值:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

LFU

LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。

配置项值:

  • volatile-lfu
  • allkeys-lfu

random 随机

配置项值:

  • volatile-random: 从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • allkeys-random: 从数据集(server.db[i].dict)中任意选择数据淘汰

ttl

TTL 数据淘汰机制:从过期时间的表中随机挑选几个键值对,取出其中 ttl 最小的键值对淘汰。

配置项值:

volatile-ttl: 从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

noenviction

禁止驱逐数据,不删除 默认

缓存淘汰策略的选择

  • allkeys-lru : 在不确定时一般采用策略。 冷热数据交换
  • volatile-lru : 比allkeys-lru性能差 存 : 过期时间
  • allkeys-random : 希望请求符合平均分布(每个元素以相同的概率被访问)
  • 自己控制:volatile-ttl 缓存穿透

其他场景对过期key的处理

1、快照生成RDB文件时

    过期的key不会被保存在RDB文件中。

2、服务重启载入RDB文件时

    Master载入RDB时,文件中的未过期的键会被正常载入,过期键则会被忽略。Slave 载入RDB 时,文件中的所有键都会被载入,当主从同步时,再和Master保持一致。

3、AOF 文件写入时

    因为AOF保存的是执行过的Redis命令,所以如果redis还没有执行del,AOF文件中也不会保存del操作,当过期key被删除时,DEL 命令也会被同步到 AOF 文件中去。

4、重写AOF文件时

    执行 BGREWRITEAOF 时 ,过期的key不会被记录到 AOF 文件中。

5、主从同步时

    Master 删除 过期 Key 之后,会向所有 Slave 服务器发送一个 DEL命令,Slave 收到通知之后,会删除这些 Key。

    Slave 在读取过期键时,不会做判断删除操作,而是继续返回该键对应的值,只有当Master 发送 DEL 通知,Slave才会删除过期键,

这是统一、中心化的键删除策略,保证主从服务器的数据一致性。

  • 作者:都要好好的O
  • 原文链接:https://blog.csdn.net/ming13849012515/article/details/124870716
    更新时间:2022年10月12日11:14:25 ,共 5537 字。