Redis过期删除与内存淘汰

97 阅读6分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第16天,点击查看活动详情

过期删除策略

redis可以对key设置过期时间,过期键值删除策略就是将已过期的key删除掉的机制。

被设置了过期时间的key会被放进过期字典(expires dict)

当查询一个key时,会检查是否存在于过期字典中

  • 不存在,即没有过期时间,直接读
  • 存在,比对系统时间,如果比系统时间大则没有过期

redis有两种过期删除策略:

  • 惰性删除
  • 定期删除

惰性删除

简单来说,就是不主动删除过期的key,每次访问key时,都检测key是否过期,如果过期就删除

优点:因为每次访问才检查,所以使用很少的系统资源,该策略对CPU时间友好

缺点:如果一个key已经过期,但是一直没有被访问,就会一直保存在数据库中,所以造成内存空间浪费,对内存不友好。

定期删除

简单来说,就是每隔一段时间,就从数据库中取出一定数量的key进行检查,过期就删除。

流程:

  1. 从过期字典中随机抽取20个key
  2. 检查是否过期,过期就删除
  3. 如果过期的个数超过5个(20/4),也就是过期的/ 随机抽取的大于25%,则重复取,小于则停止。

可以发现,定期删除是一个循环的过程,如果不加限制,就有可能一直循环,导致线程卡死。所以有定期删除循环流程的上限一般不超过25ms

优点:

通过限制删除操作执行的时长和频率,来减少删除操作对CPU的影响,同时也能删除一部分过期的数据减少了过期键对空间的无效利用

缺点:

难以确定删除操作执行的时长和频率。如果执行的太频繁,就会对CPU不友好,如果太少和惰性删除也没确保,需要找到平衡点。

Redis 选择「惰性删除+定期删除」这两种策略配和使用

内存淘汰策略

过期删除策略,就是删除已过期的key。

内存淘汰策略就是当redis的运行内存已经超过redis设置的最大内存之后,使用制度删除符合条件的key,以此来保障redis高效的运行。

如何设置redis最大运行内存

在redis.conf中,通过设置maxmemory <bytes>来设置。

  • 在64位操作系统中,maxmemory的默认值是0,表示没有内存大小限制,不管怎样,都不会进行内存淘汰
  • 在32位操作系统中,maxmemory的默认值是3G,因为32位机器最大只支持4GB内存。而且系统本身就需要一定内存资源。如果系统内存资源不足,可能导致redis实例崩溃。

redis内存淘汰策略有哪些?

有8种,策略大体分为不进行数据淘汰和进行数据淘汰的两类策略

  1. 不进行数据淘汰的策略

noeviction,当运行内存超过最大设置内存时,不淘汰任何数据,而是不再提供服务,直接返回错误

  1. 进行数据淘汰的策略

针对进行数据淘汰的策略,可以细分为,在设置了过期时间的数据中进行淘汰和在所有数据范围内进行淘汰这两类策略。

在设置了过期时间的数据中进行淘汰:

  • volatile-random:随机淘汰设置了过期时间的任意键值;
  • volatile-ttl:优先淘汰更早过期的键值。
  • volatile-lru(Redis3.0 之前,默认的内存淘汰策略):淘汰所有设置了过期时间的键值中,最久未使用的键值;
  • volatile-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰所有设置了过期时间的键值中,最少使用的键值;

在所有数据范围内进行淘汰:

  • allkeys-random:随机淘汰任意键值;
  • allkeys-lru:淘汰整个键值中最久未使用的键值;
  • allkeys-lfu(Redis 4.0 后新增的内存淘汰策略):淘汰整个键值中最少使用的键值。

config get maxmemory-policy,可以查看当前redis的内存淘汰策略

设置“maxmemory-policy <策略>”不会丢失,config set maxmemory-policy会丢失

LRU 算法和 LFU 算法 有什么区别

LFU内存淘汰算法是为了解决LRU算法的问题。

什么是LRU算法?

Least Recently Used 最近最少使用算法,淘汰最近最少使用的数据

传统LRU算法是通过链表实现的。按操作顺序排序的链表,最新操作会被移动到表头。当需要内存淘汰,删除表尾元素,链表尾元素代表最久未被使用。

由于存在以下两个问题:

  • 需要用到链表管理内存数据,额外的空间开销
  • 被访问需要移动到表头,会带来很多的链表移动操作,降低性能

redis如何实现LRU算法?

redis实现的是一种近似LRU算法,目的是更好的节约内存。实现方式是在redis的对象结构体中添加一个额外的字段。用于记录此数据的最后一次访问时间。

进行内存淘汰时,会使用随机采样的方式,随机取5个值(可配置),然后淘汰最久没有使用的那个。

redis实现的优点:

  • 不用维护链表,节省空间
  • 不用移动链表项,提高性能

但是LRU算法有一个问题,无法解决缓存污染问题,如果一次读了很多数据,但是有些数据只被读了一次,但会存在很长时间,造成缓存污染。

所以引入LFU算法解决问题。

什么是LFU算法?

根据“如果数据过去被访问多次,那么将来被访问的频率也会更高”,LFU算法根据访问次数来淘汰数据,淘汰最近最不常用的算法。

通过记录每个数据的访问次数来实现。能够解决上述LRU算法的问题。

redis是如何实现LFU算法的?

LFU算法相比于LRU算法多记录了数据的访问频次。

redis对象头中的lru字段,在不同算法下的使用方式不同:

  • 在LRU算法中,redis对象头的24bit的lru字段用来记录key的访问时间戳。
  • 在LFU算法中,lru分成两段16bit存储ldt(访问时间戳),8bit存储logc(访问频次)\

logc不是单纯的访问次数,而是频次,会随者时间推移而衰减。

redis访问key时,对logc的操作是这样的:

  1. 先按照上次访问时间距离当前时间的对logc进行衰减
  2. 按照一定概率增加logc的值

redis.conf提供两个配置项,调整增长和衰减

  • lfu-decay-time 用于调整 logc 的衰减速度,它是一个以分钟为单位的数值,默认值为1,lfu-decay-time 值越大,衰减越慢;
  • lfu-log-factor 用于调整 logc 的增长速度,lfu-log-factor 值越大,logc 增长越慢