Redis 过期删除与内存淘汰

过期删除策略

每当对一个key设了过期时间时Redis会把该key带上过期时间存到一个过期字典中,里面保存了数据库中所有 key 的过期时间。当查一个key时Redis先检查该key是否存在过期字典中:如果不在就正常读取;如果在则会获取过期时间然后与当前系统时间比对,如果比系统时间大就没过期,否则判定该key已过期

Redis的过期删除策略是「惰性删除+定期删除

惰性删除策略

不主动删除过期键,每次从数据库访问key时检测是否过期,过期则删除该key

【优点】:每次访问时才检查key是否过期,所以只会用很少的系统资源,对CPU时间最友好

【缺点】:如果一个key已过期,而这个key又仍留在数据库中,那么只要这个过期key一直没被访问,它所占用的内存就不会释放,造成了一定的内存空间浪费,对内存不友好

定期删除策略

隔一段时间「随机」从数据库中取一定量的key检查,并删除其中的过期key(如果「已过期key数」占比「随机抽取key数」大于25%,则继续继续抽取;否则停止继续删除过期key然后等下一轮再检查)

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

【缺点】:难确定删除操作执行的时长和频率。如果执行太频繁就会对CPU不友好;如果执行少那又和惰性删除一样了,过期key占用的内存不会及时得到释放。

对过期键的处理

  • RDB快照文件不保存,AOF文件会保存后再DEL删除
  • 主库进行过期扫描,从库不扫

Redis内存淘汰机制

共有八种,分为「不进行数据淘汰:不提供服务直接返回错误」和「进行数据淘汰」

  • random:随机淘汰任意键值
  • lru:淘汰最近最少使用,也就是最久未使用的
  • lfu:淘汰最少使用的

Redis实现LRU

原本LRU使用链表+map实现,但是存在两个问题:

  • 需要用链表管理所有缓存数据,带来额外空间开销;
  • 当有数据被访问时,需要在链表上把该数据移动到头。如果有大量数据被访问就会带来很多链表移动操作,耗时进而降低Redis缓存性能

所以在Redis对象结构体中添加一个额外字段,用于记录此数据的最后一次访问时间。当进行内存淘汰时会使用随机采样的方式来淘汰数据:随机取5个值(此值可配置),然后淘汰最久没有使用的那个。这样就不用为所有数据维护一个大链表,节省空间占用,也不用在每次数据访问时都移动链表项,提升缓存性能。

但LRU存在缓存污染问题,由LFU解决

Redis实现LFU

根据数据访问次数来淘汰数据,LFU会记录每个数据的访问次数。