Redis的缓存过期和淘汰策略

本文介绍了Redis的缓存过期和淘汰策略。当达到`maxmemory`限制时,Redis通过不同策略删除对象,包括定时删除、惰性删除和主动删除。其中,LRU和LFU算法用于选择淘汰的键。配置选项如`volatile-lru`和`allkeys-lru`分别针对设置了过期时间的数据和所有数据进行淘汰。此外,文章还讨论了Redis如何在`expires`字典中存储键的存活时间。

目录

maxmemory

不设置的场景

设置的场景

expire数据结构

expire的使用

expire原理 

删除策略

定时删除(时间换空间)

惰性删除(空间换时间)

主动删除/定期删除

逐出算法 

LRU

LFU

random 随机

ttl

volatile-ttl

noenviction


Redis性能高: 官方数据: 读:110000次/s 写:81000次/s

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

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

这里主要涉及maxmemory、expire数据结构、删除策略和逐出算法。

maxmemory

不设置的场景

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

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

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

maxmemory : 默认为0 不限制

设置的场景

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

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

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

设置多少? 与业务有关

例如:

1个Redis实例,保证系统运行 1G ,剩下的就都可以设置

或者设置成: 物理内存的3/4

slaver : 留出一定的内存

在redis.conf中

maxmemory 1024mb

命令: 获得maxmemory数

CONFIG GET maxmemory

设置maxmemory后,当趋近maxmemory时,通过缓存淘汰策略,从内存中删除对象

不设置maxmemory 无最大内存限制 maxmemory-policy noeviction (禁止驱逐) 不淘汰

设置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 #永久有效 (integer) -1
127.0.0.1:6379> expire name 30 #30秒失效 (integer) 1
127.0.0.1:6379> ttl name #还有24秒失效 (integer) 24
127.0.0.1:6379> ttl name #失效
(integer) -2

expire原理 

typedef struct redisDb {
    dict *dict;  -- key Value
    dict *expires; -- key ttl
    dict *blocking_keys;
    dict *ready_keys;
    dict *watched_keys;
    int id;
} redisDb;

上面的代码是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目前采用惰性删除+主动删除的方式。

定时删除(时间换空间)

创建一个定时器,在key有过期时间,且过期时间到达时,立即执行对键的删除操作。

在这里插入图片描述

  • 优点:节约内存,当时立即删除,释放不必要的内存占用

  • 缺点:CPU压力较大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量

  • 总结:用处理器性能换取存储空间(时间换空间),适用于小内存,强CPU场景

惰性删除(空间换时间)

在这里插入图片描述

当我们使用惰性删除时,数据到期了也不会自动删除,那么他的删除方式是,在下一次在获取这个key值时,会做一个判断,判断这个key是否过期,如果过期了在执行删除。

调用操作数据的指令,会先执行expireIfNeeded(),该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删除它。

  • 优点:节约CPU性能,下一次操作的时候才删除

  • 缺点:内存空间压力很大,出现长期占用内存的数据

  • 总结:用存储空间换取处理器性能 (空间换时间),适用于大内存,弱CPU场景

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);

主动删除/定期删除

在上文中我们提及了2种删除方式,一种是定时删除(用空间换时间),一种是惰性删除(用时间换空间)。2种方案都是比较极端的方式。那么我们来看看定期删除的实现方案。

redis.db中的id表示数据库,每个redis默认都有16个数据库,在redis.conf里边由一个配置参数databases这个参数控制的。每个数据库都有自己的过期分区dict *expires;,里边存储就是数据地址 和 数据过期时间。

定期删除算法:

img

逐出算法 

影响逐出算法的相关配置:

  • maxmemory:redis可使用内存占物理内存的最大比例,默认为0,表示不限制redis使用内存。生产环境中根据需求设定,通常设置在50%以上

  • maxmemory-samples:每次选取待删除数据的个数,选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据

  • maxmemory-policy:达到最大内存后的,对被挑选出来的数据进行删除的算法

删除算法一共有8种:

lru

allkeys-lru

volatile-lru

lfu

allkeys-lfu

volatile-lfu

random

allkeys-random

volatile-random

ttl

volatile-ttl

noeviction

在redis.conf文件中可以配置主动删除策略,默认是no-enviction(不删除)

maxmemory-policy allkeys-lru 

LRU

LRU (Least recently used) 最近最少使用,算法根据数据的历史访问记录来进行淘汰数据,其核心思想 是“如果数据最近被访问过,那么将来被访问的几率也更高”。

最常见的实现是使用一个链表保存缓存数据,详细算法实现如下:

  1. 新数据插入到链表头部;

  2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

  3. 当链表满的时候,将链表尾部的数据丢弃。

  4. 在Java中可以使用LinkHashMap(哈希链表)去实现LRU

让我们以用户信息的需求为例,来演示一下LRU算法的基本思路:

1.假设我们使用哈希链表来缓存用户信息,目前缓存4个用户,这4个用户是按照时间顺序依次从链表右端插入的。

 

2.此时,业务方访问用户5,由于哈希链表中没有用户5的数据,我们从数据库中读取出来,插入到缓存当中。这时候,链表中最右端是最新访问到的用户5,最左端是最近最少访问的用户1。

 

3.接下来,业务方访问用户2,哈希链表中存在用户2的数据,我们怎么做呢?我们把用户2从它的前驱 节点和后继节点之间移除,重新插入到链表最右端。这时候,链表中最右端变成了最新访问到的用户 2,最左端仍然是最近最少访问的用户1。

 

4.接下来,业务方请求修改用户4的信息。同样道理,我们把用户4从原来的位置移动到链表最右侧,并 把用户信息的值更新。这时候,链表中最右端是最新访问到的用户4,最左端仍然是最近最少访问的用 户1。

 

5.业务访问用户6,用户6在缓存里没有,需要插入到哈希链表。假设这时候缓存容量已经达到上限,必须先删除最近最少访问的数据,那么位于哈希链表最左端的用户1就会被删除掉,然后再把用户6插入到最右端。

 

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

volatile-ttl

从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰 redis 数据集数据结构中保存了键值对过期时间的表,即 redisDb.expires。 TTL 数据淘汰机制:从过期时间的表中随机挑选几个键值对,取出其中 ttl 最小的键值对淘汰。

noenviction

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

缓存淘汰策略的选择

  • allkeys-lru : 在不确定时一般采用策略。 冷热数据交换

  • volatile-lru : 比allkeys-lru性能差 存 : 过期时间

  • allkeys-random : 希望请求符合平均分布(每个元素以相同的概率被访问)

  • 自己控制:volatile-ttl 缓存穿透

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值